epoll模型的实现原理

一、epoll模型概念与比较

select、poll、epoll一样都是I/O多路复用技术。网络编程还有其他常用模型,如每连接一进程(PPC, 在Apache服务器中采用)、每连接一线程(TPC)。还有Windows中的IOCP

select/pselect, poll/ppoll与epoll的比较:

1. 历史上,select最先出现,pselect是POSIX定义的pselect变体的版本,可以指定信号屏蔽字。select可以侦听的文件描述符数收到FD_SETSIZE,Linux的值为1024

[cpp]  view plain  copy
  1. #include <sys/select.h>  
  2.   
  3. int pselect(int nfds, fd_set *restrict readfds,  
  4.        fd_set *restrict writefds, fd_set *restrict errorfds,  
  5.        const struct timespec *restrict timeout,  
  6.        const sigset_t *restrict sigmask);  
  7. int select(int nfds, fd_set *restrict readfds,  
  8.        fd_set *restrict writefds, fd_set *restrict errorfds,  
  9.        struct timeval *restrict timeout);  
2. 随后,出现了poll/ppoll,去掉select的FD_SETSIZE的限制,并使用struct pollfd描述感兴趣的事件,粒度更细。

[cpp]  view plain  copy
  1. #include <poll.h>  
  2.   
  3. int poll(struct pollfd fds[], nfds_t nfds, int timeout);  
  4. int ppoll(struct pollfd *fds, nfds_t nfds,  
  5.          const struct timespec *timeout_ts, const sigset_t *sigmask);  

3. 在后,Linux2.5开始引入了epoll模型,该模型的使用需要一组系统调用,将注册感兴趣的事件(epoll_ctl)与获取时间通知操作(epoll_wait)解耦,解决了每次调用select和poll都需要在内核态和用户态之间来回拷贝文件描述符列表,且在返回后,调用者还需要手动遍历整个列表进行判断所带来的性能问题。

注:尽管epoll号称异步事件通知,但仍需调用者将事件poll out,而非像IOCP那样自动在独立的工作线程中完成用户回调的方式。

[cpp]  view plain  copy
  1. #include <sys/epoll.h>  
  2. int epoll_create(int size); //只要size>=0即可。内核会忽略掉size的值  
  3. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
  4. int epoll_wait(int epfd, struct epoll_event *events,  
  5.              int maxevents, int timeout); // maxevents不可大于#define EP_MAX_EVENTS (INT_MAX / sizeof(struct epoll_event))  

二、epoll的实现

epoll是基于eventpollfs文件系统实现的。

1. epoll_create实际上会在内核中创建一个eventpollfs文件系统的文件和一个struct eventpoll的实例,后者拥有一个描述已注册侦听事件列表(每一项用struct epitem描述,基于红黑树实现)、一个ready列表(维护待通知事件)和一个等待队列(用于对epoll_wait的进程阻塞)。

2. epool_ctl用户注册侦听事件,简单来讲,就是创建一个epitem,加入已注册侦听事件列表(内部使用红黑树组织),同时会为item注册一个回调函数(ep_ptable_queue_proc)到被侦听描述符,描述符上有事件到达(如socket接收缓冲区中到来数据)时会回调本函数。回调函数的主要作用就是将item加入ready列表,并唤醒阻塞在epoll_wait调用上的进程,这样就完成了所谓的“异步事件”通知机制。

3. epoll_wait主要内容为:首先判断ready列表是否有待通知事件,若无,则会阻塞在eventpoll上对应的等待队列。等待通知到达后,会将到达通知对应数据从内核态拷贝到用户态的events数组中。

注:网上有人说从内核态到用户态传递通知是基于共享内存(mmap)的,那应该是较老的内核或2.5版本中的实现,3.10版本并没有使用mmap。

在这里最重要的莫过于select模型和Asynchronous I/O模型。从理论上说,AIO似乎是最高效的,你的IO操作可以立即返回,然后等待os告诉你IO操作完成。但是一直以来,如何实现就没有一个完美的方案。最著名的windows完成端口实现的AIO,实际上也只是内部用线程池实现的罢了,最后的结果是IO有个线程池,你的应用程序也需要一个线程池...... 很多文档其实已经指出了这引发的线程context-switch所带来的代价。在linux 平台上,关于网络AIO一直是改动最多的地方,2.4的年代就有很多AIO内核patch,最著名的应该算是SGI。但是一直到2.6内核发布,网络模块的AIO一直没有进入稳定内核版本(大部分都是使用用户线程模拟方法,在使用了NPTL的linux上面其实和windows的完成端口基本上差不多了)。2.6内核所支持的AIO特指磁盘的AIO---支持io_submit(),io_getevents()以及对Direct IO的支持(即:就是绕过VFS系统buffer直接写硬盘,对于流服务器在内存平稳性上有相当的帮助)。 所以,剩下的select模型基本上就成为我们在linux上面的唯一选择,其实,如果加上no-block socket的配置,可以完成一个"伪"AIO的实现,只不过推动力在于你而不是os而已。不过传统的select/poll函数有着一些无法忍受的缺点,所以改进一直是2.4-2.5开发版本内核的任务,包括/dev/poll,realtime signal等等。 最终,Davide Libenzi开发的epoll进入2.6内核成为正式的解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值