IO 多路复用
IO 多路复用实现了代替进程监控大量描述符什么时候就绪,然后进程可以根据就绪的描述符作出相应的操作。
IO 过程分为等待和拷贝,等待完毕可以拷贝的时候,这个状态称为就绪。
常见的三种 IO 多路复用为 select、poll和 epoll。
select
原理:(已 C/C++ 中系统调用 select 函数为例)
- 用户将需要监控的文件描述符添加到一个集合中。
- select 系统调用将集合中的文件描述符拷贝到内核中进行监控,在内核中对所有的描述符进行遍历操作,且只关注是否有描述符就绪。
- 若有描述符就绪,则内核会把未就绪的描述符移除,然后返回就绪的文件描述符。
- 用户遍历返回的描述符,对比这些描述符是否在自己监控的集合中,若存在则对其进行处理。(对比操作使用 FD_ISSET 系统调用来执行)
内核中,用类似位图的方式来存放描述符,每一个 bit 表示一个描述符。
缺点:
- 需要手动设置描述符集合,使用不方便。
- 每次都需要把描述符拷贝到内核中,当描述符量大的时候,系统开销也会增加。
- 因为内核对描述符是遍历判断,所以随着描述符的增多,该遍历操作会逐步变慢,影响效率。
- select 支持的文件描述符数太小。
- select 并不会告诉用户哪个描述符就绪了,需要用户手动判断。
- select 会修改文件描述符集合(有描述符就绪时,新返回的集合就只包含就绪的描述符),因此每次监控都需要用户重新向内核中拷贝集合。
优点:
- 可跨平台。
- 监控的超时等待时间可以精确到微妙。
poll
原理
- 定时描述符事件结构,添加用户监控的描述符以及相应的事件
- 将事件结构信息拷贝到内核中进行监控,poll 同样采用轮询遍历的方式进行监控(性能随着描述符数量的增多而下降)。
- 当有描述符就绪,poll将就绪的事件写入到事件结构的revents成员变量中,然后调用返回。
- poll也并没有告诉用户具体哪些描述符就绪,需要用户对事件结构信息中revents进行判断,从而得知描述符是否就绪;进而对描述符进行相应事件操作revents&POLLIN/POLLOUT
优点:
- poll采用事件结构形式对描述符关心的事件进行监控,简化了select三种集合操作的流程。
- poll没有描述符上限设置。
缺点:
- 不能跨平台。
- 在内核中进行轮询遍历判断,性能随着描述符事件增多而下降。
- 也不会告诉用户具体哪一个描述符就绪,需要用户轮询遍历判断事件中的revents 。
- 需要每次都向内核中拷贝监控信息。
- 监控的超时等待时间只能精细到毫秒。
epoll
监控流程
红黑树保存需要监控的描述符和相应的监控事件,双链表保存就绪的描述符对应的事件结构体。
- epoll对描述符对应的事件监控是一个异步操作;
- epoll_wait发起调用,让操作系统对描述符进行相应事件监控;
- 操作系统对每个要监控的描述符都定义了就绪事件回调函数;
- 当描述符相应事件就绪的时候,触发事件,调用回调函数(ep_poll_callback)(将描述符事件结构信息指针添加到eventpoll的双向链表中)
- 但是epoll_wait并没有直接返回(是一个阻塞操作),每隔一会就看一下event_poll中双向链表是否为空
来判断是否有描述符就绪; - 若为空则没有描述符就绪,则等待一会重新查看;
- 若双向链表不为空,表示有描述符事件就绪,将这个描述符对应的事件结构信息拷贝到epoll_wait传入的事件结构数组中后,调用返回。
优点
- epoll采用事件结构方式对描述符进行监控,简化了select集合操作流程。
- epoll描述符监控无上限。
- 每个epoll监控的描述符事件信息,只需要向内核靠背一次。
- epoll_wait使用异步阻塞操作在内核中完成事件监控。
事件监控是操作系统通过事件回调的方式将就绪描述符事件信息添加到双向链表中;
而epoll_wait只是每隔一段时间看一下双向链表是否为空判断是否有描述符就绪,并非轮询,性能不会随着描述符增多而降低 - eppoll直接通过epoll_wait传入的时间结构数组向用户返回就绪的时间信息,可以直接告诉用户哪些描述符就绪,不要要用户进行空遍历查找
缺点:
无法跨平台
epoll 的两种工作模式
水平触发和边缘触发
举个栗子:
假如你正在打游戏,这个时候你妈把饭做好了,你妈喊你吃饭;
喊了一次,你没理,你继续打游戏,你妈过一会还会继续喊你。—水平触发
喊了一次,你没理,你妈就不会喊你了。—边缘触发
水平触发
- 当 epoll 检测到 socket 上事件就绪的时候,可以不立即处理或者只处理一部分。
- 比如来了 10K 的数据,你只读了 1K,那么过一会还会继续通知你 socket 就绪,直到你把 10K 数据读完为止。
边缘触发
- 当 epoll 检测到 socket 就绪,必须立刻处理。
- 如果来了 10K 数据,你不处理或者只处理一部分,那么 epoll 也不会再继续通知你。也就是说,当epoll 通知你就绪后,你只有一次机会来处理。