Ghost打饭问题——那些令人头大的IO模型

Ghost饿了,想去食堂吃饭。所以进入食堂后,一定是经历了两个过程:

  • 食堂准备好菜和饭
  • 食堂阿姨给Ghost盛饭

在Linux的缓存I/O机制中,也会分为两个阶段:

  • 数据准备阶段:操作系统会将I/O的数据缓存在文件系统的页缓存中,即数据会先被拷贝到操作系统内核的缓冲区中
  • 数据拷贝阶段:从操作系统内核的缓冲区拷贝到应用程序的地址空间。

这两个步骤上的不同,造成了多种多样的IO模型,今天我们来讲讲关于五大IO模型。

图片

在了解IO模型前,我们需要了解一些名词:

文件描述符(fd)

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

内核空间与用户空间

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

进程切换

为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的,并且进程切换是非常耗费资源的。

进程阻塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得了CPU资源),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。

I/O多路复用(multiplexing)

本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符(多个网络连接),一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。与传统的多线程/多进程模型比,I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降底了系统的维护工作量,节省了系统资源。
图片

五大IO模型

阻塞IO模型

使用recv的默认参数从数据准备到数据拷贝阶段,这段时间内进程始终阻塞。比如,Ghost食堂吃饭,在食材准备和打饭的过程中,一直等待一直盯着,有点傻。
图片### 非阻塞IO模型

这个模型在整个阶段属于朝三暮四型,还是用食堂打饭的例子。在食材准备和打饭的过程中,Ghost发现还没有准备好,先离开;过一会又拿着盆过来向窗口询问,准备好了没?还没有再离开,特别勤快。直到打到饭,不再去窗口询问。
在这里插入图片描述### IO复用模型

这个模型分成了两段。在调用recv前会先调用select或者poll,这两个系统调用可以在内核准备好数据,所以紧接着调用recv一定是数据准备好的状态。

IO复用模型一共有三种模式,select、poll、epoll
比如,Ghost在打饭前派了一个人去看打饭窗口有没有开启,过一会儿那个人跑回来说可以打饭了。select和poll相当于,Ghost跑到食堂,开始一个窗口一个窗口询问可不可以打饭。而epoll则是Ghost已经得知哪一个窗口可以打饭,直奔窗口即可。

图片
select运行机制

底层结构为数组。从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

select 只有一个系统调用,每次要监听都要将其从用户态传到内核,有事件时返回整个集合,需要拷来拷去。如果 fd 集合很大,用户态和内核态之间数据复制的花销是很大的,所以 select 一般限制 fd 集合最大1024。

poll运行机制

底层结构为链表。poll运行机制和select本质上没有区别,都是监听多个文件描述符并根据描述符的状态进行处理,但是由于存储结构的不同,poll最大fd数量的没有限制。

epoll运行机制

底层结构为红黑树。epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的fd子集合就行了。

epoll除了提供select/poll那种IO事件的水平触发外,还提供了边缘触发,这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

  • 水平触发(LT):默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件。
  • 边缘触发(ET):当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时只通知一次)。

LT和ET原本应该是用于脉冲信号的,可能用它来解释更加形象。Level和Edge指的就是触发点,Level为只要处于水平,那么就一直触发,而Edge则为上升沿和下降沿的时候触发。比如:0->1 就是Edge,1->1 就是Level。

select与epoll主要区别

  • select 一般限制 fd 集合最大1024,而epoll 监听的 fd集合是常驻内核的,所以fd集合没有上限。
  • select返回的是fd全集,需要通过遍历查询,复杂度为O(n),而epoll 监听的文件描述符集合是常驻内核的,通过 epoll_wait 可以多次监听同一个 fd 集合,只返回可读写那部分(fd子集),复杂度为O(1)。

尽管如此,epoll 的性能并不必然比 select 高,对于 fd 数量较少并且 fd IO 都非常繁忙的情况 select 在性能上有优势。

信号驱动IO模型

这个模型乍一看和IO复用差不多,只不过这个模型在第一个阶段用信号处理代替了select/poll/epoll。
图片

异步IO模型

调用aio_read,让内核等数据准备好,并且复制到用户进程空间后执行事先指定好的函数。比如,Ghost让食堂把饭打包好并通知他来取,整个过程Ghost可以刷剧、打游戏、写代码几不误,这就是真正的异步IO。

图片

总结

图片

阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动IO模型都属于同步IO,因为阶段2是阻塞的。只有异步IO模型是符合异步IO操作含义的,不管在阶段1还是阶段2都可以干别的事。

  • 17
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值