前置知识

  • 网卡会把接收到的网络数据写入内存中,然后向CPU发出中断程序提示操作系统有新数据到来
  • socket在收到数据后,操作系统会把进程从(sokcet的)等待队列调入(CPU的)工作队列

select

  • select把进程加入多个socket的等待队列同时阻塞该进程,直接至少有一个socket获得数据。(如果调用select时就已经有socket得到数据了则不用阻塞)
  • 当一个socket获得数据时,操作系统会把进程从所有的socket等待队列中移除,再遍历所有socket找到需要处理的
  • 因此每次select都需要遍历三次socket列表,遍历开销较大
  • 加入等待队列,移除等待队列,查找需要处理的socket,各遍历一次。

epoll

  • epoll解决了select的两个缺点

    1. 每次select都需要反复把进程加入和移除所有的socket等待队列中。大部分情况,socket和事件是固定的。
    2. 唤醒进程时,进程并不知道哪个socket得到了数据,所以需要全部遍历一下
  • 解决方法

    1. 将维护等待队列和阻塞进程这两个步骤分开,用eventpoll对象作为socket列表和进程之间的中介。开始时eventpoll会通过epoll_ctl注册所有socket对象,将eventpoll加入所有socket的等待队列中。socket收到数据后会提醒eventpoll,eventpoll不会把自身从socket的等待队列中移除,而是会把事件加入自身维护的一个“就绪列表”中,该列表为需要处理的socket引用
    2. epoll_wait阻塞进程,唤醒后,进程通过“就绪列表”,进程无需遍历所有socket即可知道哪些socket需要处理。
  • 总而言之,epoll相较于select做了两件事,一是通过一个中介让注册(加入所有socket等待队列)操作只需要做一次,二是通过“就绪列表”引用需要的处理事件。

  • 只需要最开始epoll_ctl遍历注册一次后续维护“就绪队列”和增加和删除epoll事件时偶尔操作,而不用每次epoll_wait都需要遍历三次socket列表。节省了大量的时间。

补充

  • 开始的前置知识提到,socket在接收到数据把进程从等待队列移到工作队列,这个其实不太准确,像recv,select这种一次性的是这样,但是eventpoll不同,是长期加入,因此socket并不会把eventpoll移出等待队列。
  • eventpoll也是文件系统一部分,同样有一个等待队列,因此进程是加入eventpoll的等待队列然后阻塞,唤醒后加入工作队列中。
  • socket——eventpoll——进程。epoll通过中间组件重构了流程。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐