Linux epoll模型实现初步探讨

Linux 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

       #include <sys/select.h>

       int pselect(int nfds, fd_set *restrict readfds,
              fd_set *restrict writefds, fd_set *restrict errorfds,
              const struct timespec *restrict timeout,
              const sigset_t *restrict sigmask);
       int select(int nfds, fd_set *restrict readfds,
              fd_set *restrict writefds, fd_set *restrict errorfds,
              struct timeval *restrict timeout);
2. 随后,出现了poll/ppoll,去掉select的FD_SETSIZE的限制,并使用struct pollfd描述感兴趣的事件,粒度更细。

       #include <poll.h>

       int poll(struct pollfd fds[], nfds_t nfds, int timeout);
       int ppoll(struct pollfd *fds, nfds_t nfds,
               const struct timespec *timeout_ts, const sigset_t *sigmask);

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

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

       #include <sys/epoll.h>
       int epoll_create(int size); //只要size>=0即可。内核会忽略掉size的值
       int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
       int epoll_wait(int epfd, struct epoll_event *events,
                      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。

你可能感兴趣的:(epollIO多路复用)