epoll是Linux核心的可扩展I/O事件通知机制[1]。于Linux 2.5.44首度登场,它设计目的旨在取代既有POSIX select(2)与poll(2)系统函式,让需要大量操作档案描述子的程式得以发挥更优异的效能(举例来说:旧有的系统函式所花费的时间复杂度为O(n),epoll的时间复杂度O(log n))。epoll 实现的功能与poll 类似,都是监听多个档案描述子上的事件。
epoll与FreeBSD的kqueue类似,底层都是由可组态的作业系统核心物件建构而成,并以档案描述子(file descriptor)的形式呈现于使用者空间。epoll通过使用红黑树(RB-tree)搜寻被监视的档案描述子(file descriptor)。
在epoll 实例上注册事件时,epoll 会将该事件添加到epoll 实例的红黑树上并注册一个回呼函式,当事件发生时会将事件添加到就绪连结串列中。
在聊epoll机制之前,需要从一些概念和select/poll机制聊起,再切如到epoll中,以及epoll与其两者的区别。
一、概念须知§
1.进程切换§
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,其具备挂起暂停执行与恢复继续执行的能力,操作系统的内核通过进程切换来挂起或恢复进程状态。
2.文件描述符Fd§
Linux / Unix系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符,文件描述符就是内核为了高效管理已被打开的文件所创建的索引,用来指向被打开的文件,所有执行I/O操作的系统调用都会通过文件描述符。
3.缓存 I/O§
缓存I/O又被称作标准I/O,大多数文件系统的默认I/O操作都是缓存I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存(page cache)中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
二、I/O模型§
IO (Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作,通常用户进程中的一个完整IO分为两阶段:
- 用户进程空间<-->内核空间
- 内核空间<-->设备空间(磁盘、网络等)
三、I/O多路复用§
Linux操作系统中存在select、poll、epoll三种多路复用机制,I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,其本质上都是同步I/O。
1.select()§
/*
* maxfdp1 最大 fd 数值 +1,大于此数值的 fd 会被忽略
* readset 读变化监视 fd 集合,可为 NULL
* writeset 写变化监视 fd 集合,可为 NULL
* exceptset 错误异常变化监视 fd 集合,可为 NULL
* timeout 超时时间,NULL:阻塞,0:非阻塞,>0:等待
*/
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
const struct timeval *timeout);
- select() 能实现非阻塞数据读写,避免阻塞线程以及维护多线程带来的额外消耗。
- select() 是一个系统调用(system call),由具体操作系统实现,不难理解,因为其行为涉及到 IO、文件系统等操作,而这些服务都是操作系统提供的。
- select() 函数监视多个 fd(file descrīptor 文件描述符)的状态变化,这些 fd 可分为三大类:readset(读 fd 集合)、writeset(写 fd 集合)和 exceptset(错误异常集合)。
- select() 常用于 socket 网络编程, 但理论上适用于能用 fd 描述的一切 IO,如 stdio、pipes,甚至是 uart。