前言
概述
epoll
是一种当文件描述符的内核缓冲区非空的时候,发出可读信号进行通知,当写缓冲区不满的时候,发出可写信号通知的机制;
关键函数
int epoll_create(int size);
创建 eventpoll 对象,并将 eventpoll 对象放到 epfd 对应的 file->private_data 上,返回一个 epfd,即 eventpoll 句柄。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) //返回值:成功 0;失败 -1
对一个 epfd 进行操作。op 表示要执行的操作,包括 EPOLL_CTL_ADD (添加)、EPOLL_CTL_DEL (删除)、EPOLL_CTL_MOD (修改);fd 表示被监听的文件描述符;event 表示要被监听的事件,包括:
- EPOLLIN(表示被监听的fd有可以读的数据)
- EPOLLOUT(表示被监听的fd有可以写的数据)
- EPOLLPRI(表示有可读的紧急数据)
- EPOLLERR(对应的fd发生异常)
- EPOLLHUP(对应的fd被挂断)
- EPOLLET(设置EPOLL为边缘触发)
- EPOLLONESHOT(只监听一次)
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) //返回值:监听到的产生的事件数
等待 epfd 监听的 fd 所产生对应的事件。epfd 表示 epoll句柄;events 表示回传处理事件的数组;maxevents 表示每次能处理的最大事件数;timeout:等待 IO 的超时时间,-1 表示一直阻塞直到来 IO 被唤醒,大于 0 表示阻塞指定的时间后被唤醒;
数据结构
epoll机制概述
:epoll
使用红黑树
去监听并维护所有文件描述符,调用epoll_create
时,内核除了帮我们在epoll文件系统里创建file结点外,还会在内核里创建红黑树用于存储以后epoll_ctl
传来的socket,同时还会创建一个list链表
,用来存储准备就绪的事件,当epoll_wait
调用时,仅仅观察这个list链表有没有数据即可,有数据就返回,没有数据就sleep,等到timeout时间到后即使链表中没有数据也返回
;
select、poll与epoll机制对比
- cpu耗时对比
文件描述符数量 | poll() cpu time | select() cpu time | epoll() cpu time |
---|---|---|---|
10 | 0.61 | 0.73 | 0.41 |
100 | 2.9 | 3 | 0.42 |
1000 | 35 | 35 | 0.53 |
10000 | 990 | 930 | 0.66 |
- 用户态将文件描述符传入内核的方式
select:
创建3个文件描述符集并拷贝到内核中,分别监听读、写、异常动作;这里受到单个进程可以打开的fd数量限制,默认是1024。
poll:
将传入的struct pollfd结构体数组拷贝到内核中进行监听。
epoll:
执行epoll_create会在内核的高速cache区中建立一颗红黑树以及就绪链表(该链表存储已经就绪的文件描述符)。接着用户执行的epoll_ctl函数添加文件描述符会在红黑树上增加相应的结点。
- 内核态检测文件描述符读写状态的方式
select:
采用轮询方式,遍历所有fd,最后返回一个描述符读写操作是否就绪的mask掩码,根据这个掩码给fd_set赋值。
poll:
同样采用轮询方式,查询每个fd的状态,如果就绪则在等待队列中加入一项并继续遍历。
epoll:
采用回调机制。在执行epoll_ctl的add操作时,不仅将文件描述符放到红黑树上,而且也注册了回调函数,内核在检测到某文件描述符可读/可写时会调用回调函数,该回调函数将文件描述符放在就绪链表中。
- 找到就绪的文件描述符并传递给用户态的方式
select:
将之前传入的fd_set拷贝传出到用户态并返回就绪的文件描述符总数。用户态并不知道是哪些文件描述符处于就绪态,需要遍历来判断。
poll:
将之前传入的fd数组拷贝传出用户态并返回就绪的文件描述符总数。用户态并不知道是哪些文件描述符处于就绪态,需要遍历来判断。
epoll:
epoll_wait只用观察就绪链表中有无数据即可,最后将链表的数据返回给数组并返回就绪的数量。内核将就绪的文件描述符放在传入的数组中,所以只用遍历依次处理即可。这里返回的文件描述符是通过mmap让内核和用户空间共享同一块内存实现传递的,减少了不必要的拷贝。
- 重复监听的处理方式
select:
将新的监听文件描述符集合拷贝传入内核中,继续以上步骤。
poll:
将新的struct pollfd结构体数组拷贝传入内核中,继续以上步骤。
epoll:
无需重新构建红黑树,直接沿用已存在的即可。
epoll高效的原因
-
减少了用户态和内核态的文件句柄拷贝
mmap
加速了内核与用户空间的信息传递,epoll是通过内核与用户mmap同一块内存,避免了无谓的内存拷贝; -
减少了对可读可写文件句柄的遍历,IO性能不会随着监听的文件描述的数量增长而下降
使用红黑树存储fd以及对应的回调函数,其插入、查找、删除性能都不错,相比于hash,不必预先分配很多的空间;
结语
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )