大家好 我是积极向上的湘锅锅💪💪💪
当用户应用想直接操作硬件上的数据时,是没有权限直接操作的,必须使用内核对外提供的指令或者函数
如果数据没还有准备好,必须等待数据就绪,总的来说,各种的IO模型主要是相差在俩个阶段

俩个阶段都阻塞

在整个过程中,进程都要阻塞等待数据的就绪和拷贝
非阻塞IO会在调用recvform函数立即返回结果,但是如果数据还没有准备好,跟阻塞IO区别在于第一阶段不再是阻塞等待,而是会一直调用recvform函数询问内核是否准备好

看的出来这思路跟自旋一样,都是让CPU去做无用功,去尝试获取数据,那就导致了CPU在高并发的情况下会使用率暴增
文件描述符(File Descriptor),简称FD,是一个从0开始递增的无符号整数,用来关联Linux中的一个文件,在Linux中,一切皆文件,例如常规文件,视频,硬件设备等
IO多路复用: 是利用单个线程监听多个FD,在某个FD可读,可写时候得到通知,从而避免无效的等待,充分利用CPU资源
常见的监听FD,通知的方式有三种
差异:

先从上往下看
第一阶段流程如下

用户空间所做的就是传参和调用函数,将fd_set拷贝到内核空间,内核就会遍历一遍fd_set,如果没有数据就绪,那就休眠等待,直到唤醒或者超时
如果数据就绪,select就会返回已经就的FD的个数
而内核那就会将就绪的FD保留,未就绪的从fd_set删除,然后再拷贝回用户缓冲区,把用户空间创建的rdfs给覆盖掉

因为fd_set是以01代表就绪还是未就绪,所以用户空间还得重新遍历一遍fd_set,来获取已经就绪的FD是哪些,然后读取数据
可以看得出来,其实过程相当麻烦且臃肿
与select相比,其实poll模式也就是在长度的限制上做了改进,poll模式下存储的FD个数将没有上限,但是同时也是一把双刃剑,监听的FD越多,每次遍历消耗的时间也越久,性能反而随之下降

poll函数:
pollfd:
IO流程:
相比于前俩种模式,epoll做出了相当大的改进

首先,会将需要监听的FD添加到内核空间
调用epoll_create函数,会创建一个eventpoll实例
而里面的俩个参数,分别是一颗红黑树和链表,分别代表要监听的FD和已经就绪的FD,跟前俩种模式相比,也是将监听的FD和就绪的FD分开来,不再是一个数组进行保存

而提供的epoll_ctl函数,其中参数也很明了
也就是向这颗红黑树添加节点,并且设置callback函数进行监听,如果有准备就绪的FD,自动触发,这个对应的FD就加入链表当中

然后,就需要从这个链表上面取出数据了
调用epoll_wait函数,值得注意的是,在参数里面,设置一个空的数组,并且数组设置最大长度
有什么作用?
因为我们知道需要查询的FD已经在红黑树里面,所以只需要拷贝一份链表里面的FD到空数组里面,也就是说,在这个数组里面,一定是已经就绪的FD,也就减去了需要遍历找出哪些FD就绪的的时空消耗
整个流程如下

跟前俩种模式相比
信号驱动IO表示在内核SIGIO建立信号关联回调,如果有FD就绪了,就会发出SIGIO信号通知用户,期间用户可以执行其他的业务,无需阻塞等待,但是就绪之后,还是需要调用recvform函数将数据拷贝回用户空间

问题来了,为什么信号驱动IO看起来那么的完美无缺,为什么没有广泛的使用呢?
假设一个场景,当产生大量的IO操作的时候,同一时间可能返回大量的信号到信号队列里面,那么极有可能回产生队列溢出,并发性能低
异步的概念运用的已经极其广泛,异步IO也是如此
在俩个阶段都是非阻塞

整个过程中用户只需要调用aio_read函数通知内核我想要哪些数据,然后就不管了
剩下的事情,等待数据,拷贝数据,全都交给内核去做,拷贝完了之后再通知用户数据已经就绪拷贝好
好比一个场景:
一个人想吃饭,给妈妈说要吃什么,妈妈全部给准备好,然后再通知这个人去吃饭
同样,在高并发的情况下,由于用户只需要调用aio_read,如果请求过多,内核的负担是很大的,需要进行限流