五种IO模型详解( 五 )


同步:在保持两边数据同步的过程中 , 进程被阻塞 , 由内核抢占其CPU去完成数据同步 , 直到两边数据同步 , 进程才被唤醒
异步:在保持两边数据同步的过程中 , 由内核默默地在后台完成数据同步(如果不理解 , 可认为是单独开了一个内核线程负责数据同步) , 内核不会抢占进程的CPU , 所以进程自身不被阻塞 , 当内核完成两端数据同步时 , 通知进程已同步完成
这里阻塞和非阻塞、同步和异步都是广义的概念 , 上面所做的解释适用于所有使用这些术语的情况 , 而不仅仅是本文所专注的IO模型 。
回到阻塞、非阻塞、同步、异步的IO模型 , 再对它们啰嗦啰嗦 。
阻塞、非阻塞、IO复用、信号驱动都是同步IO模型 。 需注意 , 虽然不同IO模型在加载数据到kernel buffer的数据准备过程中可能阻塞、可能不阻塞 , 但kernel buffer才是read()函数读取数据时的对象 , 同步的意思是让kernel buffer和app buffer数据同步 。 在保持kernel buffer和app buffer同步的过程中 , CPU将从执行read()操作的进程切换到内核态 , 内核获取CPU拷贝数据到app buffer , 所以执行read()操作的进程在这个同步的阶段中是被阻塞的 。
只有异步IO模型才是异步的 , 因为它调用的是具有【神力】的异步IO函数(如aio_read()) , 调用这些函数时会请求内核 , 当数据已经拷贝到app buffer后 , 通知进程并执行指定的操作 。
需要注意的是 , 无论是哪种IO模型 , 在将数据从kernel buffer拷贝到app buffer的这个阶段 , 都是需要CPU参与的 。 只不过 , 同步IO模型和异步IO模型中 , CPU参与的方式不一样:
同步IO模型中 , 调用read()的进程会切换到内核 , 由内核占用CPU来执行数据拷贝 , 所以原进程在此阶段一直被阻塞
异步IO模型中 , 由内核在后台默默的执行数据拷贝 , 所以原进程在此阶段不被阻塞
如图:
五种IO模型详解文章插图
2.7 信号驱动IO和异步IO的区别很多人都不理解信号驱动IO和异步IO之间的区别 , 一方面是因为它们都立即返回 , 另一方面是因为它们看似都是被动的或后台的 。
但其实在前文已经分析清楚了它们的区别 , 这里仅做总结性分析 。 在此之前 , 还是借用前文使用过的类比 。
信号驱动IO模型:小姐姐在逛街 , 小姐姐本没有想过要买什么东西 , 但如果发现有合适的 , 也会去买下来 , 在逛了一段时间后 , 一件超短裙闪现到小姐姐的视线 , 小姐姐很喜欢这个款式 , 于是立即决定买下来 , 买的时候小姐姐不能再干其它事情 。
异步IO模型:小姐姐在逛街 , 她这次带上了男朋友 , 只要想买东西 , 都可以让男朋友去帮忙买 , 而小姐姐可以继续自己逛自己的 , 男朋友买好后通知小姐姐即可 。
1.异步IO
异步IO通过调用具有异步IO能力的函数来实现 。 在调用异步函数时 , 要求指定IO完成时的通知方式 。
当IO完成后 , 内核(这里的内核是广义的 , 不再局限于操作系统内核 , 它也可以是浏览器内核 , 或语言的解释器 , 或语言的虚拟机)要么通知进程 , 要么执行回调函数 。
这里所谓的IO完成 , 表示的是已经保持了两边数据的同步(比如kernel buffer和app buffer之间) 。 而异步之所以称为异步 , 就体现在完成两边数据同步的阶段中 , 它表示由内核在后台默默完成数据的同步任务 。
对于异步IO来说 , 它不在乎什么类型的文件描述符 , socket、pipe、fifo、terminal以及普通文件都可以执行异步IO 。
2.信号驱动IO
信号驱动IO是同步IO模型 。
当某个文件描述符设置了O_ASYNC标记时(前文说过 , 称呼为O_ASYNC是历史原因) , 表示该文件描述符开启信号驱动IO的功能 。
使用信号驱动IO , 要求进程注册SIGIO的信号处理程序 , 注册之后 , 进程就可以做其他任务 。
当有另一端向该描述符写入数据时 , 就意味着该文件描述符已经就绪 , 内核会发送SIGIO信号给进程 , 于是进程会去执行已经注册的SIGIO信号处理程序 。 一般来说 , 信号处理程序中 , 要么是read()类的读取函数 , 要么是为后面是否读取做判断的变量标记 。
但是 , 内核发送SIGIO信号只是通知进程数据已经就绪 , 但就绪了多少数据量 , 进程并不知道 。
而且 , 进程因为收到通知而认为可以数据已就绪 , 于是执行read() , 进程在执行read()的时候 , CPU将从用户态切换到内核态 , 由内核获取CPU来执行数据同步操作 , 所以在这个阶段中 , 进程的read()是被阻塞的 。