我们通过比较select、poll和epoll处理I/O的过程来剖析其中的原因:
1. 用户态将文件描述符传入内核的方式:
select:创建3个文件描述符集并拷贝到内核中,分别监听读、写、异常动作。这里受到单个进程可以打开的fd数量限制,默认是1024。
poll:将传入的struct pollfd
结构体数组拷贝到内核中进行监听。
epoll:执行epoll_create
会在内核的高速cache区中建立一颗红黑树以及就绪链表(该链表存储已经就绪的文件描述符)。接着用户执行的epoll_ctl
函数添加文件描述符会在红黑树上增加相应的结点。
2. 内核态检测文件描述符是否可读可写的方式:
select:采用轮询方式,遍历所有fd,最后返回一个描述符读写操作是否就绪的mask掩码,根据这个掩码给fd_set赋值。
poll:同样采用轮询方式,查询每个fd的状态,如果就绪则在等待队列中加入一项并继续遍历。
epoll:采用回调机制。在执行epoll_ctl
的add
操作时,不仅将文件描述符放到红黑树上,而且也注册了回调函数,内核在检测到某文件描述符可读/可写时会调用回调函数,该回调函数将文件描述符放在就绪链表中。
3. 如何找到就绪的文件描述符并传递给用户态:
select:将之前传入的fd_set拷贝传出到用户态并返回就绪的文件描述符总数