IO 多路复用是通过一种机制,同时监视多个文件描述符,一旦某个文件描述符就绪(比如读就绪或写就绪),会通知应用程序进行相应的处理。 select、poll、epoll 本质上都是同步 IO,它们都需要在事件就绪后,由应用程序自己进行读写。而异步 IO 不需要应用程序自己进行读写,异步 IO 实现负责把数据从内核拷贝到用户空间。
Linux 提供 select、poll、epoll,来实现 IO 多路复用机制,下面将对它们进行简要的介绍。
如果想了解 select、poll 的原型和详细说明,请点击该链接。这里只对 select 和 poll 的执行过程,进行简要的说明:
将文件描述符集合从用户空间拷贝到内核空间
对每个描述符进行轮询,记录临时结果
select 或 poll 返回后,需要逐一检查关注的描述符是否有事件发生
如果想了解 epoll,请点击该链接。
epoll 的使用过程如下:
epoll_create
创建 epoll 描述符epoll_ctl
向 epoll 描述符中,添加、修改或删除事件epoll_wait
收集发生事件的描述符与 select、poll 相比,epoll 有如下特点:
epoll_ctl
添加文件描述符后,它就与 epoll 描述符关联起来了epoll_wait
检查是否有事件发生时,只需要检查就绪事件链表即可epoll 有水平触发和边缘触发两种模式。水平触发是默认模式,边缘触发是高速模式。在水平触发模式下,如果一个描述符有数据可读,那么每次调用 epoll_wait
都会返回该描述符上发生的事件。而在边缘触发模式下,只会提醒一次,直到有新数据流入。因此在边缘触发模式下,读描述符一定要读空它的 buffer,即一直读到 read 的返回值小于它的请求值,或遇到 EAGAIN 错误(读非阻塞 socket 时,如果输入缓冲区中没有数据,那么会出现 EAGAIN 错误,意思是告诉应用程序再读一次或许就有数据了)。
当进程打开或新建文件时,内核会返回一个文件描述符,文件描述符是一个非负整数,取值范围是 0 到 max open files。
内核会为每个进程维护一个保存该进程打开的文件的记录表,文件描述符是这个记录表中的索引值,内核利用文件描述符来访问文件。
通常标准输入的文件描述符是 0,标准输出的文件描述符是 1,标准错误输出的文件描述符是 2。
Python 标准库的 select 模块,提供访问操作系统的 IO 多路复用接口的功能。select 的文档在:https://docs.python.org/3/library/select.html。
下面主要介绍其中关于 epoll 的部分:
select.epoll([sizehint=-1])
返回 epoll 对象。事件掩码的含义如下:
epoll.close()
关闭epoll对象的控制文件描述符
epoll.fileno()
返回控制描述符的数字表示
epoll.fromfd(fd)
从给定的文件描述符创建 epoll 对象
epoll.register(fd, [eventmask])
把文件描述符注册到 epoll 对象。注意:注册已注册的文件描述符会引发 IOError
epoll.modify(fd, eventmask)
修改已注册的文件描述符
epoll.unregister(fd)
从 epoll 对象中移除已注册的文件描述符
epoll.poll([timeout=-1[, maxevents=-1]])
收集发生事件的文件描述符,timeout 的单位是秒