IO模式 select、poll、epoll

阻塞(blocking)、非阻塞(non-blocking):最常听到阻塞与非阻塞这两个词就是在函数调用中,比如waitid这个函数,通过NOHANG参数可以把waitid设置为非阻塞的,也就是问询一遍子进程,看看有没有可回收子进程,如果没有,直接返回不等待,而如果不设置NOHANG,waitid会一直问询子进程,直到有一个可回收子进程再返回,也就是说如果没有子进程可回收,waitid会一直阻塞在这条函数调用上,后面的操作就无法执行。如果把函数调用笼统的规划到一个事件的执行,那么阻塞与非阻塞的区别就是 是否仅仅只等待事件完成(事件返回)才进行下一步操作。比如 烧开水这个例子(参考资料中有提到),如果人从水壶开始烧水到水烧开这段时间一直在水壶前等待烧水,这就是阻塞,如果人比较聪明,在水壶烧水的过程中,人去看电视了或者干其他事情了,这就是不阻塞的,但是在这种情况下,人想要知道水是否烧开了,只能隔一段时间去看一下水壶“烧开了没有?”,这个隔一段时间去看一下的方法 就是 轮询。所以,再回到waitid这个例子,我们设置NOHANG参数的时候,一般都要在waitid上加一个用于轮询的循环。

同步(synchronous)、异步(asynchronous):同步和异步,一开始看很容易把同步和阻塞的概念混淆在一起,我倾向于将同步理解为一种现象,而阻塞/非阻塞作为这个现象中的某种状态存在。同步和异步的区别,参考资料中的说法我觉得虽然把同步异步区别说出来了,但是同步和阻塞还是很容易混淆。那我再对烧开水这个例子重写一下以供参考,水壶分为 普通水壶 和 智能水壶,普通水壶需要人手工操作,需要人判断水是否烧开,水烧开了需要人把水壶从火上拿下来(不然会溢出来),而智能水壶提供两个作用,水烧开后自动关火并且发出响声提示人水已经烧好。烧开水就分为了两个过程,等待水开和拿开水壶,首先说同步和异步,同步就是使用普通水壶烧水,水烧开后需要人主动拿开水壶,而等待水烧开的这个过程可以是阻塞(站那等)的和非阻塞(看电视轮询)的,整个操作需要人的全程参与;异步就是使用智能水壶烧水,等待水开的过程人可以干任何事(即异步本身就是非阻塞的,所以异步并不区分阻塞/非阻塞)并且人不用关心水是否烧开(敲重点),水烧开后,智能水壶会发出响声提示人(发出信号),这完成了等待水开这第一过程,紧接着,智能水壶会自动关火,这代替了人拿开水壶的这个动作完成了第二过程。可以发现,同步整个过程需要人全程参与,而异步整个过程是两个“人”在做,一个“人”就是这个智能水壶,一个人就是始终在做其他事的人,这个人将烧开水这件事托管给了智能水壶由智能水壶完成一切过程,最后提示人完成了。因此,判断同步只需要看,事件的监管和完成方是不是事件发起者即可。

在接下来即将要说到的IO操作中,异步IO就是进程将IO操作完全交由内核控制,由内核中的DMA控制器完成全部IO操作并在完成后通过信号提示进程IO操作已完成,这也是两个“人”的事情,进程将IO托管给DMA控制器,而进程又可以干其他的事,二者在不同的事件上各自执行操作,是并行的。

IO模式:

比如对于一个read操作发生时,它会经历下面两个阶段:

A, 等待数据准备,数据是否拷贝到内核缓冲区;

B, 将数据从内核拷贝到用户进程空间

如果把这两个阶段对应到前文中我写的烧开水的两个过程中,对于下面的几种IO模式就容易理解的多。

《Unix网络编程卷1:套接字联网API》(即UNP)中第六章对unix 系统将IO模型分为五类:阻塞IO,非阻塞IO,IO复用,信号驱动,异步IO

1、阻塞IO:A阶段阻塞等待,进程调用recvfrom询问内核数据是否准备好,直到数据准备好再返回。(注意:由内核监听数据,进程询问内核数据是否准备好)

2、非阻塞IO:A阶段非阻塞等待,即采用轮询机制(即进程不断的询问内核数据是否准备好(不断调用recvfrom),这么做往往消耗大量CPU时间)判断数据是否拷贝到内核缓冲区。

3、IO复用:IO复用技术是对阻塞IO的改进,首先非阻塞大量消耗CPU,不予考虑。在数据准备阶段,内核监听一个IO也好,监听一群IO也好,都在监听,因此复用,就是内核同时监听进程的多个IO操作,在进程询问时返回有数据到来的IO。此时进程不是阻塞在recvfrom上,而是阻塞在IO复用系统调用——select、poll、epoll上,这几个系统调用会在所监听的所有IO中某个或某几个有数据到来时返回。

多路IO共用一个同步阻塞接口,任意IO可操作都可激活IO操作,这是对阻塞IO的改进(主要是select和poll、epoll,关键是能实现同时对多个IO端口进行监听)。此时阻塞发生在select/poll的系统调用上,而不是阻塞在实际的I/O系统调用上。IO多路复用的高级之处在于:它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select等函数就可以返回。

4、信号驱动

注册一个IO信号事件,在数据可操作时通过SIGIO信号通知线程,这应该算是一种异步机制;

5、异步IO:进程通知内核完成一个IO操作,由内核完成A+B阶段后通知进程。

参考中的这张图很好的解释了上述5种IO模型。

POSIX将IO只分成了同步IO、异步IO两种模型。

同步I/O操作:实际的I/O操作将导致请求进程阻塞,直到I/O操作完成。

异步I/O操作:实际的I/O操作不导致请求进程阻塞。

由此,阻塞式I/O,非阻塞式I/O,I/O复用,信号驱动I/O模型都属于同步I/O,因为第二阶段的数据复制都是阻塞的,也可以理解为都由进程完成。而只有异步I/O模型属于这里的异步I/O操作(IO由内核完成)。

参考资料:知乎Linux分享官

bilibili视频

《Unix网络编程卷1:套接字联网API(第3版)》6.2 IO模型

原文地址:https://www.cnblogs.com/GuoYuying/p/14052899.html