int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select
:只能监控 FD_SETSIZE
个链接,libc 里这个数量设置的很小,需要在每次调用时重新构建fd_set
;
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
pollfd
的结构体定义了一个events和一个revents,它们也是两个位图,每一位代表一个事件,events 代表监控的事件,revents 代表返回时具有的事件,监控事件和返回事件分离的设计让我们不用像使用 select 一样,每次都重新构建fd_set
poll
:尽管没有链接数的限制,也就是说可以监控 RLIMIT_NOFILE
个链接, 但是每一次都要对所有监控链接从头到尾的扫描,速度O(n)
,这也降低了性能;
在这两个系统调用使用时,内核与用户空间通过内存复制来进行消息传递,进一步降低性能。
select比poll更加通用,一些unix系统不支持poll
而 epoll
没有 select
和 poll
的限制, 可以 O(1)
时间处理操作, 内核与用户空间通过 mmap
来进行消息传递,又进一步加快了速度。
在用户空间中管理所有事儿,并且我们在每次调用时都要发送所有关心的文件描述符到内核,如果我们想添加一个新的文件描述符,就要将它加到集合中并重新调用 select 和 poll。
与 epoll 相关的系统调用有:
epoll_create
:在内核中创建一个上下文 epoll instance
epoll_create1
:同上
epoll_ctl
:用来在 epoll instance 上添加、删除被监控的文件描述符(file descriptor);
epoll_wait
:获取有事件发生的文件描述符,当没有时会一直被阻塞;
当文件描述符被加入后,事件通知的方式有两种:
level triggered
:当你未及时处理时,一个事件会持续通知,直到你处理完毕;
edge triggered
:一个事件只通知一次;
epoll
在内核中维护文件描述符上下文,所以我们不用每次调用都向内核复制文件描述符列表;
epoll
中,我们不需要遍历所有我们监控的 fd,只需要遍历已经准备好的那些(特定事件)。
epoll 数据结构
epoll 数据结构主要有 epoll 事件 epoll_event
,监听系统本身 eventpoll
,被监听的对象epitem
,以及被监听对象在设备等待队列中的存在形式 eppoll_entry
;
events 可以是以下几个宏的集合:
EPOLLIN
:表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT
:表示对应的文件描述符可以写;
EPOLLPRI
:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR
:表示对应的文件描述符发生错误;
EPOLLHUP
:表示对应的文件描述符被挂断;
EPOLLET
: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的;
EPOLLONESHOT
:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里;
监听系统本身 eventpoll
被监听的对象epitem
epitem 在设备等待队列中存在 eppoll_entry
epoll 内核实现
epoll_create
系统调用内核实现
系统调用 epoll_create()
会创建一个 epoll实例并返回该实例对应的文件描述符 fd。在内核中,每个epoll实例会和一个struct eventpoll
类型的对象一一对应,该对象是epoll的核心。此对象包含红黑树 rbr
用于存储epitem
对象,等待队列wg
,就绪链表rdllist
等。构建 file
对象,同时将 eventpoll
对象ep
挂到file
对象的 private_data
对象上面。
1.首先创建一个struct eventpoll
对象;
2.分配一个未使用的文件描述符;
3.创建一个struct file
对象,将file中的struct file_operations *f_op
设置为全局变量eventpoll_fops
,将void *private
指向刚创建的eventpoll
对象ep
;
4.然后设置eventpoll
中的file
指针;
5.最后将文件描述符添加到当前进程的文件描述符表中,并返回给用户。
epoll_ctl系统调用内核实现
构建 epitem
挂到 epoll
红黑树上;构建 eppoll_entry
挂到设备等待队列,其中eppoll_entry
包含回调函数 ep_poll_callback
和对应的 epitem
。
epoll_wait系统调用内核实现
如何使用epoll系统调用
深入io到epoll与linux内核系统调用
IO多路复用模型以及select、poll、epoll系统调用
一文带你了解Linux内核epoll实现原理与机制