本文试图讨论一下I/O模型中阻塞模式和异步模式的区别。
为了说明问题,我们首先讨论Linux read()/write()系统调用的流程。
以块设备为例,其I/O栈如下图所示(引自《深入理解Linux内核》)。当系统调用read()时, 调用进程会:
1. 切换到内核态,找到VFS层的file对象中的f_op操作数组,调用其read()方法。
2. 调用进程会依次调用磁盘缓存系统找页面,找到了直接返回,没找到继续下面的步骤。
3. 调用映射层把读取的数据的文件偏移转换成卷内偏移。
4. 调用通用块层为每次读取生成一个数据结构BIO块并发送到I/O调度层进行调度(合并优化处理)。
5. 在定时器超时或者磁盘中断发生时,I/O调度层把收到的请求发到磁盘驱动程序,使用DMA从硬件读取数据。
6. 当数据传送完毕,硬件触发中断,磁盘驱动的中断处理函数会被调用。
7. 中断处理函数检查是否所有传送完毕,如果没有继续传剩下的数据;否则通知读取进程返回。
阻塞/非阻塞模式:
如果在第1步中系统发现文件不可读(这通常会出现在socket通信中,实际文件系统不太会有这种情况),那么就进入等待状态直到文件可读,这就叫阻塞模式。
如果在第1步中系统发现文件不可读就直接返回,那就叫非阻塞模式。
无论是Windows还是Linux,都可以使用poll()和select()对非阻塞I/O进行多路复用。一旦poll()/select()返回,则意味着相应文件可以读/写,调用进程可以同步读写文件。
同步/异步模式:
如果调用进程在上面的第4步之后进入等待状态,直到数据传送完成才返回,那么这种模式就是同步模式。
如果调用进程在上面的第4步之后立即返回处理其他事情,等检查到数据传送完成后取回数据,那么这种模式就是异步模式。
上面的定义在Windows和Linux上基本相同,只不过:
Windows上异步完成之后会有各种异步通知方式通知用户态进程解除阻塞模式(比如让文件句柄有信号,用户态APC以及完成端口等),从而开始处理数据。
Linux上只能用户态进程使用aio_error()来轮训操作是否完成。
参考资料:
1. 《深入理解Linux内核》
2. 《Linux驱动程序》
3. 《Windows Internals》(第四、五版)