epoll
recv 阻塞
操作系统维护有 运行进程队列
创建一个socket后,这个socket包括数据发送和接收的缓冲区、进程等待队列
当调用
recv(...)时,当前进程从运行队列 改变到 该socket的等待队列,不会阻塞 cpu,同时等待着收到数据后,重新进入运行队列当socket接收到数据后,操作系统将该socket等待队列上的进程重新放回到工作队列,该进程变成运行状态,继续执行代码。也由于socket的接收缓冲区已经有了数据,recv可以返回接收到的数据。
网卡接收网络数据
网卡收到网线传输来的网络数据,会先写到内存里
把数据写入到内存后,网卡向cpu发出一个中断信号,通知cpu有数据到达,cpu执行中断程序。(ps: 中断的优先级是很高的)
此处的中断程序主要有两项功能:
先将网络数据写入到对应socket的接收缓冲区里面
再唤醒进程,重新将进程放入工作队列中
操作系统如何知道网络数据对应于哪个socket?因为一个socket对应着一个端口号,而网络数据包中包含了ip和端口的信息,内核可以通过端口号找到对应的socket
如何同时监视多个socket的数据?select、poll、epoll。
select
流程
设置所有需要监听的
socket fd先遍历一遍,如果某个 socket 有数据,直接返回,不会阻塞。
如果都没有数据,把当前进程加入到设置的 各个socket的等待队列,把进程从运行队列移除,当前进程阻塞。
当任何一个socket收到数据后,中断程序将唤起进程,将进程从所有socket的等待队列中移除,加入到工作队列里面。
当进程A被唤醒后,它知道至少有一个socket接收了数据,但还不知道具体是哪个。程序还需遍历一遍socket列表,就可以得到就绪的socket。
缺点
每次调用select都需要将进程加入到所有监视socket的等待队列,每次唤醒都需要从每个队列中移除。这里涉及了两次遍历,而且每次都要将整个fds列表传递给内核,有一定的开销
因为遍历操作开销大,出于效率的考量,才会规定select的最大监视数量,默认只能监视1024个socket。
进程被唤醒后,程序并不知道哪些socket收到数据,还需要遍历一次。
epoll
epoll要解决的就是 select中的问题,减少遍历,保存就绪socket
流程
epoll初始化用epoll_create创建一个event_poll对象,这个对象有就绪列表(双向链表),红黑树,等待列表。就绪列表存放就绪的socket,红黑树存放所有正在监听的socket引用,等待列表放正在等待的进程。
用epoll_ctl添加或删除所要监听的socket,内核会将eventpoll添加到所有设置的socket的等待队列中。
当socket收到数据后,中断程序会操作eventpoll对象(而不是直接操作进程,这是和select的重要区别),给eventpoll的就绪列表添加socket引用
eventpoll对象相当于是socket和进程之间的中介,socket的数据接收并不直接影响进程,而是通过改变eventpoll的就绪列表来改变进程状态。
当程序执行到epoll_wait时,
如果就绪队列已经引用了socket,那么epoll_wait直接返回。
如果就绪队列为空,把进程放到eventpoll的等待队列,阻塞进程。
当socket接收到数据,中断程序一方面修改就绪队列,另一方面唤醒eventpoll等待队列中的进程,进程再次进入运行状态。
参考:
最后更新于