poll

poll系统调用
poll()系统调用是System V的多元I/O解决方案。它解决了select()的几个不足,尽管select()仍然经常使用(多数还是出于习惯,或者打着可移植的名义):
用户空间调用的poll函数定义如下:
#include <sys/poll.h>
int poll (struct pollfd *fds, unsigned int nfds, int timeout);

和select()不一样,poll()没有使用低效的三个基于位的文件描述符set,而是采用了一个单独的结构体pollfd数组,由fds指针指向这个组。pollfd结构体定义如下:

#include <sys/poll.h>

struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
 

每一个pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码。内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。
合法的事件如下:
POLLIN                     有数据可读。
POLLRDNORM        有普通数据可读。
POLLRDBAND         有优先数据可读。
POLLPRI                  有紧迫数据可读。
POLLOUT                写数据不会导致阻塞。
POLLWRNORM       写普通数据不会导致阻塞。
POLLWRBAND        写优先数据不会导致阻塞。
POLLMSG                SIGPOLL消息可用。

此外,revents域中还可能返回下列事件:
POLLER                 指定的文件描述符发生错误。
POLLHUP              指定的文件描述符挂起事件。
POLLNVAL            指定的文件描述符非法。

timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时;timeout为0指示 poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。

成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:
EBADF
一个或多个结构体中指定的文件描述符无效。
EFAULT
fds指针指向的地址超出进程的地址空间。
EINTR
请求的事件之前产生一个信号,调用可以重新发起。
EINVAL
nfds参数超出PLIMIT_NOFILE值。
ENOMEM
可用内存不足,无法完成请求。


epoll系统调用

epoll的优点:
1.支持一个进程打开大数目的socket描述符(FD)
    select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

2.IO效率不随FD数目增加而线性下降
    传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。

3.使用mmap加速内核与用户空间的消息传递。
    这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。

4.内核微调
    这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小 --- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。


内核驱动对以上系统调用的支持

对于以上系统调用的支持需要来自设驱动程序的相应支持。所有三个系统调用均通过驱动程序的poll方法提供。

而在内核的文件接口poll定义如下:
在<linux/poll.h>中声明:
unsigned int (*poll) (struct file *filp, poll_table *wait);
当用户空间程序在驱动程序关联的文件描述符上执行poll、select或epoll系统调用时,该驱动方法将被调用。

驱动通过调用函数 poll_wait增加一个等待队列到 poll_table 结构:
void poll_wait (struct file *, wait_queue_head_t *, poll_table *);
poll_table在<linux/poll.h>中声明,驱动程序开发员将其当成一个不透明的对象使用,不必了解该结构细节。


2. 返回一个位掩码:描述可能不必阻塞就立刻进行的操作,几个标志(通过 <linux/poll.h> 定义)用来指示可能的操作:

 标志

                          含义

 POLLIN

 如果设备无阻塞的读取,就返回该值

 POLLRDNORM

 通常的数据已经准备好可以读了,就返回该值。一个可读设备返回(POLLLIN | POLLRDNORM)

 POLLRDBAND

 如果可以从设备读出带外数据,就返回该值,它只可在与套接字相关的的文件描述符中使用,通常不用在设备驱动程序中

 POLLPRI

 如果可以无阻塞的读取高优先级(oob带外)数据,就返回该值,返回该值会导致select报告文件发生异常,以为select把带外数据当作异常处理

 POLLHUP

 当读设备的进程到达文件尾时,驱动程序必须返回该值,依照select的功能描述,调用select的进程被告知进程时可读的。

 POLLERR

 如果设备发生错误,就返回该值。

 POLLOUT

 如果设备可以无阻塞地写入,就返回该值

 POLLWRNORM

 设备已经准备好,可以写了,就返回该值。一个可写设备返回(POLLOUT | POLLNORM)

 POLLWRBAND

 于POLLRDBAND类似

 

poll 识别3类数据 普通,优先级带,和高优先级 ,一般TCP,UDP 属于普通数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值