为了账号安全,请及时绑定邮箱和手机立即绑定

五种 IO 模型

标签:
操作系统

IO即为Input、Output,对计算机来说,我们使用键盘鼠标给计算机指令就是一种输入,计算机将我们键盘输入的文字显示到显示器即是一种输出。或者写博客时将计算机从键盘接收到的文字信息发送到平台上即为输出,当我们查阅资料,打开某一篇博客时对计算机来说也可理解为输入。

操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。

上面所说的IO数据通常由OS缓存到内核中,而后拷贝到用户空间中,因此Input和Output的流程可以简化为:

外部输入—》OS内核空间—》用户空间—》用户进程

用户进程输入—》用户空间—》OS内核空间—》外部

阻塞IO

用户进程在获取IO数据时,最简单的方式就是串行,即阻塞IO(BIO)。

例子:好比我们在代码开发完成后,提交merge请求,提交后我们就到commitor工位,发现commitor不在工位,我们一直等到他回工位,然后看着他检视代码,直到他检视完成后我们才放心的做其他事。
file
jdk1.4前网络连接都是采用BIO的模式,当服务端Socket接收到请求后,不能再接收处理其他请求,就只能把这个请求处理完后才能接收其他请求,可以简单的理解为串行。虽然该模型简单,但是我们开发完代码后一直等着commitor检视还是有偷懒的嫌疑,可能会被说效率低,从上图中可以看出,我们不一定要一直守在commitor工位旁边等他回来,所以就出现了非阻塞IO。

非阻塞IO

鉴于BIO有串行效率低的缺点,我们做了优化,就是不再一直等待数据准备好,而是用户进程主动多次询问。

例子:好比我们在代码开发完成后,提交merge请求,提交后我们看到commitor不在工位就知道还不能合代码,此时我们不会守在commiter工位旁边等待,而是是不是去他工位看看回来没,直到某一次去看他已经回到工位,才守到他工位旁边,看着他检视代码,直到他检视完成后我们才放心的做其他事。
file

和BIO相比,NIO内核会立刻返回,返回后应用进程可以做其他事,即应用进程第一阶段不是阻塞的,但是需要主动不断去询问内核数据是否准备好;第二个阶段仍然是阻塞的。NIO虽然比BIO有所提升,但是还是需要应用进程不断去询问,因此产生了IO复用模型。

IO复用

IO复用模型去掉了应用进程主动询问的过程,而是把数据是否准备好交给了内核处理,内核通过select/poll去遍历检查数据是否准备好,或者通过epoll回调方式处理。

例子:我们此次需要提交前台和后台的功能代码,由多个commitor负责检视,commitor检视前由秘书记录由哪些代码合入的请求,我们提交merge后,发现commitor不在,秘书就先记录,我们在commitor工位一直等着,秘书遍历去各commitor工位看是否回来,一旦看到某个commitor在工位,就通知此commitor相关的merge代码的人来检视代码,程序员看着他检视代码,直到他检视完成后我们才放心的做其他事。

file

IO 多路复用(IO multiplexing) ,也称事件驱动 IO(event -driven IO),就是在单个线程里同时监控多个套接字,通过 select 或 poll 轮询所负责的所有 socket,当某个 socket 有数据到达了, 就通知用户进程 。 IO 复用同非阻塞 IO 本质一样,不过利用了新的 select 系统调用,由内核来负责本来是请求进程该做的轮询操作。看似比非阻塞 IO 还多了一个系统调用开销,不过支持多路 IO提高了效率。 进程先是阻塞在 select/poll 上,再是阻塞在读操作的第二个阶段上。

IO复用主要有select、poll、epoll三类,select通过数组记录应用进程请求,因此监听数量有限;poll使用链表的方式优化了select的监听数量缺陷,epoll为了减少内核多余的遍历调用,变主动位被动,通过回调实现,如下图:
file

epoll相较于select/poll,多了两次系统调用,其中epoll_create建立与内核的连接,epoll_ctl注册事件,epoll_wait阻塞用户进程,等待IO事件。

select、poll、epoll区别如下:

file

IO复用的缺点是应用进程请求后就会阻塞。

信号驱动IO

信号驱动 IO 与IO多路复用最大的区别就在于,在 IO 执行的数据准备阶段 ,不会阻塞用户进程 。 如图所示:当用户进程需要等待数据的时候,会向内核发送一个信号,告诉内核我要什么数据,然后用户进程 就继续做别的事情去了,而当内核中的数据准备好之后,内核立马发给用户进程一个信号,说 ”数据准备好了, 快来查收“,用户进程收到信号之后,立马调用 recvfrom去查收数据 。

例子:我们提交代码合入请求后,这次仍然有秘书记录merge请求,我们看到commitor不在工位就马上回去干自己的活了,一旦commitor回到工位,秘书就通知我去commitor工位检视代码,我就在他旁边看着他检视代码,直到他检视完成后我才放心的做其他事。
file

总的就一句话:信号驱动优化了第一阶段阻塞的情况,但是在程序员守着检视代码的阶段还是阻塞的。

异步IO

AIO,异步 IO 真正实现了 IO 全流程(两个阶段)的非阻塞。用户进程发出系统调用后立即返回,内核等待数据准备完成,然后将数据拷贝到用户进程缓冲区,然后发送信号告诉用户进程 IO 操作执行完毕 (与 SIGIO 相比,一个是发送信号告诉用户进程数据准备完毕,一个是 IO 执行完毕)。

例子:我们提交代码合入请求后,我们看到commitor不在工位就马上回去干自己的活了,commitor回到工位后看到我们提的merge,就自己开始检视代码,当检视完成后,通知我说代码已经合入了,可以发版本了。
file

总结

最后总结一下,五种IO模型,从上到下应该是逐级改进的,总体分类如下图:

file

作者:懒AI患者 | 转自:InfoQ

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Linux系统工程师
手记
粉丝
0
获赞与收藏
17

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消