当程序进行IO时,如果数据尚未准备好,那么IO将处于阻塞状态。当某个进程有多个打开的文件,比如socket,那么其后的所有准备好读写的文件将受到阻塞的影响而不能操作。不借助线程,单一进程无法在同一时间服务多个文件描述符。非阻挡式IO可以作为一个解决方案,但是效率并不高。首先进程需要不断发IO请求,其次,如果程序可以休眠,让出CPU将提高效率。多任务式IO是在其中任何一个文件描述符就绪时收到通知,此时IO将不会受到阻挡,其余时间处于休眠状态,将CPU资源让给别的进程。
I/O多路复用的设计遵循以下原则:
1,I/O多路复用:当任何文件描述符准备好I/O时告诉我
2,在一个或更多文件描述符就绪前始终处于睡眠状态。
3,唤醒:哪个准备好了?
4,在不阻塞的情况下处理所有I/O就绪的文件描述符。
5,返回第一步,重新开始。
多任务式IO主要有select、poll、epoll三种:
select监视一大片文件描述符,监视的范围是最大的文件描述符maxfd加1,并将所监视的文件描述符分为三组,采用FD_ISSET宏查看哪些fd对应的bitmap变为准备状态,就可以对其进行操作。由于select()在各种Unix系统中都很容易实现,相对于微秒级精度的睡眠机 制来讲,经常将select()做为一种可移植的微秒级的睡眠机制:select (0, NULL, NULL, NULL, &tv)。此外BSD还是先了pselect()系统调用。Select可以用在文件描述符数量相对比较确定的场景。
poll是System V的IO多路复用方案,采用pollfd数组来设定需要监视的文件描述符,相比select需要为每个分组检查maxfd+1个比特位,效率更高。
EPoll接口
epoll把监听注册从实际监听中分离出来,从而解决了这个问题。一个系统调用初始化一个epoll上下文,另一个从上下文中加入或删除需要监视的文件描述符,第三个执行真正的事件等待(eventwait)。如果epoll_ctl()的参数event中的events项设置为EPOLLE,fd上的监听称为边沿触发,相反的称为水平触发。对于水平触发的监听,在步骤2里对epoll_wait()的调用将立即返回,以表明pipe可读。对于边沿触发的监听,这个调用直到步骤1发生后才会返回。也就是说,即使调用epoll_wait()时管道已经可读,调用仍然会等待直到有数据写入,之后返回。水平触发是默认行为。也是poll()和select()的行为,也是大多数开发者所期望的。边沿触发需要一个不同的方式来写程序,通常利用非阻塞I/O,并需要仔细检查EAGAIN。
水平触发在一个状态发生时触发。边沿触发只有在状态改变的时候才会产生。水平触发在只关心状态时有用。边沿触发则在关心事件本身时有用。
epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它不会复用文件描述符集合来传递结果而迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。epoll的主要优点有:
1、支持一个进程打开大数目的socket描述符;
2、IO效率不随FD数目增加而线性下降;
3、使用mmap加速内核与用户空间的消息传递。
int epoll_create (int size)
epoll_create()创 建 一 个 epoll实例,返回与该实例关联的文件描述符。这个文件描述符和真正的文件没有关系,仅仅是为了后续调用使用epoll而创建的。
int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event);
在epfd实例中加入一个fd指定的监听文件参数op用来添加、删除、修改fd指定文件的事件。events结构体中的events参数列出了在给定文件描述符上监听的事件。多个事件可以使用位或运算同时指定。最常见的是读写事件的监听: EPOLLIN | EPOLLOUT。
int epoll_wait (int epfd, struct epoll_event events, int maxevents, int timeout);
返回值是事件数,出错返回-1。events要保证有足够的buffer:
events = malloc (sizeof (struct epoll_event) MAX_EVENTS);
nr_events = epoll_wait (epfd, events, MAX_EVENTS, -1);
如果timeout为 0, 即使没有事件发生 , 调用也立即返回,此时调用返回0。如果timeout为 -1,调用将一直等待到有事件发生。
原文地址:http://blog.51cto.com/13376824/2060154