type
status
date
slug
summary
tags
category
icon
password
Sub-item
Last edited time
Dec 17, 2023 01:52 AM
Parent item
领域
异步同步
都是非阻塞。同步分为阻塞非阻塞,关系如下:
内核接收网络数据全过程
- 计算机收到了对端传送的数据
- 数据经由网卡DMA传送到内存
- 网卡通过中断信号通知cpu有数据到达,cpu执行中断程序
- 中断程序先将网络数据写入到对应socket的接收缓冲区里面
- 唤醒进程A,重新将进程A放入工作队列中
阻塞IO
由系统调用read,导致线程一直等待数据返回。
非阻塞IO
系统调用read后立即返回一个状态,当数据达到内核缓冲区之前都是非阻塞的,即返回一个系统调用状态。
非阻塞IO需要不停地查询数据有没有准备好。
IO多路复用
多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄。
select
通过select,我们可以把一组文件描述符的数组发给操作系统, 让操作系统去遍历,确定哪个文件描述符可以读写, 然后告诉我们去处理,处理时也需要再遍历一次:
select流程如下:
- 程序同时监视如下图的sock1、sock2和sock3三个socket,那么在调用select之后,操作系统把进程A分别加入这三个socket的等待队列中。程序阻塞。
- kernel就会轮询检查所有select负责的fd,当找到一个client中的数据准备好了,select就会返回,中断程序将唤起进程。
所谓唤起进程,就是将进程从所有的等待队列中移除,加入到工作队列里面。如下图所示:
- 程序就会系统调用(例如recvfrom),将数据从kernel复制到进程缓冲区。当进程A被唤醒后,它知道至少有一个socket接收了数据。程序只需遍历一遍socket列表,就可以得到就绪的socket。
虽然服务器进程会被select阻塞,但是select会利用内核不断轮询监听其他客户端的IO操作是否完成。
select机制的问题
- 一旦某个socket收到数据后,需要重新select所有IO。每次调用select,都需要把
fd_set
集合从用户态拷贝到内核态,如果fd_set
集合很大时,那这个开销也很大;
- 同时,每次调用select都需要在内核遍历传递进来的所有
fd_set
,如果fd_set
集合很大时,那这个开销也很大;
- 为了减少数据拷贝带来的性能损坏,内核对被监控的
fd_set
集合大小做了限制,并且这个是通过宏控制的,大小不可改变(32个整数大小,1024bits) — — 一次最多只能select 1024个socket。
- 进程被唤醒后,程序并不知道哪些socket收到数据,还需要遍历一次。
poll
poll和select一样,但是对select进行了一些改进。但只解决了上述select的第3个问题,有了以下的改进:
- 不限制监听槽的个数。poll使用 pollfd 结构,而不是select结构fd_set结构,所以poll是链式的,没有最大连接数的限制。
- 重复通知。poll有一个特点是水平触发,也就是通知程序fd就绪后,这次没有被处理,那么下次poll的时候会再次通知同个fd已经就绪。
epoll
epoll相比poll更进一步,除了没有对描述符数目的限制,它所支持的文件描述符上限是整个系统最大可以打开的文件数目。
因为select|/poll每次有数据到来并处理完后都需要重新传递列表给内核,把是将“维护等待队列”和“阻塞进程”两个步骤合二为一,导致效率比较低。然而大多数应用场景中,需要监视的socket相对固定,并不需要每次都修改。epoll将这两个操作分开,先用epoll_ctl维护等待队列,再调用epoll_wait阻塞进程。显而易见的,效率就能得到提升。
另一个低效的原因是序不知道哪些socket收到数据,只能一个个遍历。如果内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。如下图所示,计算机共有三个socket,收到数据的sock2和sock3被rdlist(就绪列表)所引用。当进程被唤醒后,只要获取rdlist的内容,就能够知道哪些socket收到数据。
- 创建epoll对象
如下图所示,当某个进程调用epoll_create方法时,内核会创建一个eventpoll对象(也就是程序中epfd所代表的对象)。eventpoll对象也是文件系统中的一员,和socket一样,它也会有等待队列。
- 维护监视列表
创建epoll对象后,可以用epoll_ctl添加或删除所要监听的socket。以添加socket为例,如下图,如果通过epoll_ctl添加sock1、sock和sock3的监视,内核会将eventpoll添加到这三个socket的等待队列中。
- 接收数据
当socket收到数据后,中断程序会给eventpoll的“就绪列表”添加socket引用。如下图展示的是sock2和sock3收到数据后,中断程序让rdlist引用这两个socket。
socket的数据接收并不直接影响进程,而是通过改变eventpoll的就绪列表来改变进程状态。
假设计算机中正在运行进程A和进程B,在某时刻进程A运行到了epoll_wait语句。当程序执行到epoll_wait时,如果rdlist已经引用了socket,那么epoll_wait直接返回,如果rdlist为空,阻塞进程。如下图所示,内核会将进程A放入eventpoll的等待队列中,阻塞进程。
当socket接收到数据,中断程序一方面修改rdlist,另一方面唤醒eventpoll等待队列中的进程,进程A再次进入运行状态(如下图)。也因为rdlist的存在,进程A可以知道哪些socket发生了变化。