永远年轻,永远热泪盈眶!
分类: 服务器与存储
2014-09-02 10:23:48
Linux提供了三种I/O多路复用方案select(可移植性更好),poll,epoll(linux系统特有)。
一、三种I/O复用函数select、poll和epoll的定义和使用规则
1、int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
其中nfds是被监听的文件描述符总数通常设置为所有文件描述符中的最大值加1(是否可以理解为凡是小于这个描述符的都要被轮询?加1是因
为文件描述符从0开始计数),readfds、writefds、exceptfds分别指向可读、可写、异常事件对应的文件描述符集合(当事件发生时内核在线修改
该集合以此来通知应用程序),timeout指出等待时间(能精确到微秒但是不可信);若为NULL,则一直阻塞直到有事件发生,fd_set结构体能
容纳的文件描述符数目由FD_SETSIZE限制(所有事件的数目),其同时限制了select能同时处理的文件描述符总量。调用成功返回就绪事件描述符
总数,失败返回-1。宏FD_SET(int fd,fd_set* fdset)将文件描述符fd和事件集合fd_set绑定(这个fd_set可以传给select中间的三个参数之一),
宏FD_ISSET(int fd,fd_set* fdset)可以检测fd上的事件是否发生(内核在线修改这个数据机构。所以select调用后可以使用该宏判断并处理相应事件),
由于是内核在线修改事件集合,所以每次select调用后都需要重置事件集合才能进行下一次select调用。
因此select编写格式大致绑定文件描述符和事件集合,select调用,测试特定文件描述符上的事件是否发生,重新绑定文件描述符和事件集合的
关系,再次调用select。
2、int poll(struct pollfd* fds,nfds_t nfds,int timeout)
其中fds是个pollfd结构体数组(因为是数组所以才能监听多个文件描述符),pollfd结构体作用是将文件描述符fd和事件events(一些列事件的按位或,
具体事件类型可以查阅其它资料)绑定并且内核修改revents成员从而达到侦听并告知应用程序(注意select是在线修改事件集合而poll内核修改revents
所以决定了poll不用每次调用后重置事件集合,因为events并没有被修改),nfds是告诉内核fds数组的大小,timeout是超时值若为-1则永远阻塞直
到事件发生,若为0,则立即返回。
3、epoll 则有三个系统调用构成的I/O复用逻辑,epoll 将用户关心的文件描述符上的事件放在内核的一个事件表中,从而不像select和poll那样每次
调用都重复传入事件集合(一切缘于多个系统调用将select和poll的底层逻辑分开了),因此epoll需要一个额外的文件描述符标示这个事件表,这个事件
表由epoll_create函数创建:
int epoll_create(int size) //其中size只是给内核一个提示告诉内核事件表需要多大,现在已经不起作用了,返回事件表描述符,这个描述符将在后续中使用;
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event) // 该函数用来操作事件表,其中fd是需要操作的文件描述符(不是事件表描述符),op参数指定
操作类型:EPOLL_CTL_ADD往fd上添加事件,EPOLL_CTL_MOD修改fd上事件,EPOLL_CTL_DEL删除fd上的注册事件。结构体epoll_event的成员events
是事件类型(一些列事件的按位或,具体有哪些类型就部列出了,不过值得注意的是epoll事件是poll事件前面加E),成员data又是个结构体用于存储用户数据,
最常用的就是将data.fd设置为需要监听的文件描述符fd,成功返回0,失败返回-1。
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout) // 在一段超时时间内等待一组文件描述符上的事件,成功返回事件就续的文件
描述符个数,失败返回-1。其中epfd就是事件表描述符,maxevents指定最多监听多少个事件(必须大于0),timeout和poll的相同。
以下是个人详细总结:建议从多个角度(事件集、最大支持文件描述符数、工作模式、具体实现);
事件集:select的参数类型fd_set没有将文件描述符和事件绑定,仅能处理可读、可写及异常事件。由于内核对fd_set集合的在线修改,
应用程序下次调用select前不得不重置这三个fd_set集合。 poll使用pollfd结构体将文件描述符和事件定义其中,内核每次修改的是pollfd结构
体的revents成员,而events成员保持不变,故而下次调用poll无需重置pollfd类型的事件集参数。由于select和poll调用都返回整个用户注册的事
件集合(包括就绪的和未就绪的),所以就应用程序索引就绪文件描述符的时间复杂度为O(n)。而epoll采用与select和poll完全不同的方式来管
理用户注册的事件,它在内核维护一个事件表,并提供一个独立系统调用epoll_ctl来控制往其中添加、删除、修改事件。这样每次epoll_wait
系统调用都直接从该内核事件表中取得用户注册的事件,而无需反复从用户空间读入这些事件。epoll_wait系统调用的events参数仅用来返回
就绪的事件,这使得应用程序索引就绪文件描述符的时间复杂度达到O(1)。
最大支持的文件描述符:poll和epoll_wait分别用nfds和maxevents参数指定最多监听多少个文件描述符和事件,这两个数值都能达到系统
允许打开的的最大文件描述符数目,即65535(cat /proc/sys/fs/file-max)。而select允许监听的最大文件描述符数量通常有限制。虽然用户可以修
改这个限制,但可能导致不可预期的后果。 (附加:操作系统通过宏FD_SETSIZE来声明在一个进程中select所能操作的文件描述符的最大
数目,在4.4BSD的头文件中我们可以看到:#define FD_SETSIZE 1024,在红帽Linux的头文件
#define FD_SETSIZE __FD_SETSIZE 定义FD_SETSIZE为1024。)
工作模式:select和poll都只能工作在相对低效的LT模式,而epoll则可以工作在ET高效模式。并且epoll还支持EPOLLONESHOT事件。
该事件能进一步减少可读、可写和异常等事件被触发的次数。
实现原理:select和poll采用轮询方式,即每次调用都要扫描整个注册文件描述符集合,并将就绪的文件描述符返回给用户程序,因此检
测就绪事件的算法的时间复杂度为O(n)。epoll_wait采用回调方式,内核检测到就绪的文件描述符时,将触发回调函数,回调函数将该文件
描述符上对应的事件插入内核就绪队列。内核最后在适当的时机将该就绪事件队列中的内容拷贝到用户空间。因此epoll_wait无需轮询整个文
件描述符集合来检测哪些事件就绪,其算法时间复杂度是O(1)。
注意:epoll机制相关概念(epoll与select、poll机制区别),这个概念许多互联网公司的面试官都会问起。
epoll模型及优缺点?(这个年年必考,希望我的总结可以帮助到大家)
推荐一本书《Linux高性能服务器编程》。