Linux IO模型

1、IO

在这里插入图片描述
IO (Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作,通常用户进程中的一个完整IO分为两阶段:用户进程空间<–>内核空间、内核空间<–>设备空间(磁盘、网络等)。IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者。

LINUX中进程无法直接操作I/O设备,其必须通过系统调用请求kernel来协助完成I/O动作;内核会为每个I/O设备维护一个缓冲区。

对于一个输入操作来说,进程IO系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备中读取,因为设备IO一般速度较慢,需要等待;内核缓冲区有数据则直接复制到进程空间。

所以,对于一个网络输入操作通常包括两个不同阶段:

(1)等待网络数据到达网卡→读取到内核缓冲区,数据准备好;
(2)从内核缓冲区复制数据到进程空间。

2、5种IO模型

  1. 同步阻塞IO(Blocking IO)
  2. 同步非阻塞IO(Non-blocking IO)
  3. IO多路复用(IO Multiplexing)
  4. 信号驱动IO模型(signal driven IO (SIGIO) )
  5. 异步IO(Asynchronous IO)

注意以下概念:

同步:
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。

例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事

异步:
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

例如 ajax请求(异步): 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

阻塞:
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。

有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回,它还会抢占cpu去执行其他逻辑,也会主动检测io是否准备好。

非阻塞:
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

再简单点理解就是:

  1. 同步,就是我调用一个功能,该功能没有结束前,我死等结果。
  2. 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)。
  3. 阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
  4. 非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者

同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞

阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回

这里统一使用Linux下的系统调用recv作为例子,它用于从套接字上接收一个消息,因为是一个系统调用,所以调用时会从用户进程空间切换到内核空间运行一段时间再切换回来。默认情况下recv会等到网络数据到达并且复制到用户进程空间或者发生错误时返回,而第4个参数flags可以让它马上返回。

2.1 阻塞IO模型

使用recv的默认参数一直等数据直到拷贝到用户空间,这段时间内进程始终阻塞。A同学用杯子装水,打开水龙头装满水然后离开。这一过程就可以看成是使用了阻塞IO模型,因为如果水龙头没有水,他也要等到有水并装满杯子才能离开去做别的事情。很显然,这种IO模型是同步的。
在这里插入图片描述

2.2 非阻塞IO模型

改变flags,让recv不管有没有获取到数据都返回,如果没有数据那么一段时间后再调用recv看看,如此循环。B同学也用杯子装水,打开水龙头后发现没有水,它离开了,过一会他又拿着杯子来看看……在中间离开的这些时间里,B同学离开了装水现场(回到用户进程空间),可以做他自己的事情。这就是非阻塞IO模型。但是它只有是检查无数据的时候是非阻塞的,在数据到达的时候依然要等待复制数据到用户空间(等着水将水杯装满),因此它还是同步IO。
在这里插入图片描述

2.3 IO复用模型

这里在调用recv前先调用select或者poll,这2个系统调用都可以在内核准备好数据(网络数据到达内核)时告知用户进程,这个时候再调用recv一定是有数据的。因此这一过程中它是阻塞于select或poll,而没有阻塞于recv,有人将非阻塞IO定义成在读写操作时没有阻塞于系统调用的IO操作(不包括数据从内核复制到用户空间时的阻塞,因为这相对于网络IO来说确实很短暂),如果按这样理解,这种IO模型也能称之为非阻塞IO模型,但是按POSIX来看,它也是同步IO,那么也和楼上一样称之为同步非阻塞IO吧。

这种IO模型比较特别,分个段。因为它能同时监听多个文件描述符(fd)。这个时候C同学来装水,发现有一排水龙头,舍管阿姨告诉他这些水龙头都还没有水,等有水了告诉他。于是等啊等(select调用中),过了一会阿姨告诉他有水了,但不知道是哪个水龙头有水,自己看吧。于是C同学一个个打开,往杯子里装水(recv)。这里再顺便说说鼎鼎大名的epoll(高性能的代名词啊),epoll也属于IO复用模型,主要区别在于舍管阿姨会告诉C同学哪几个水龙头有水了,不需要一个个打开看(当然还有其它区别)。
在这里插入图片描述

Linux中IO复用的实现方式主要有select、poll和epoll:

Select:注册IO、阻塞扫描,监听的IO最大连接数不能多于FD_SIZE;
Poll:原理和Select相似,没有数量限制,但IO数量大扫描线性性能下降;
Epoll :事件驱动不阻塞,mmap实现内核与用户空间的消息传递,数量很大,Linux2.6后内核支持;

2.4 信号驱动IO模型

通过调用sigaction注册信号函数,等内核数据准备好的时候系统中断当前程序,执行信号函数(在这里面调用recv)。D同学让舍管阿姨等有水的时候通知他(注册信号函数),没多久D同学得知有水了,跑去装水。是不是很像异步IO?很遗憾,它还是同步IO(省不了装水的时间啊)。
在这里插入图片描述

2.5 异步IO模型

真正的异步IO需要操作系统更强的支持。
IO多路复用模型中,数据到达内核后通知用户线程,用户线程负责从内核空间拷贝数据;
而在异步IO模型中,当用户线程收到通知时,数据已经被操作系统从内核拷贝到用户指定的缓冲区内,用户线程直接使用即可。

调用aio_read,让内核等数据准备好,并且复制到用户进程空间后执行事先指定好的函数。E同学让舍管阿姨将杯子装满水后通知他。整个过程E同学都可以做别的事情(没有recv),这才是真正的异步IO。
在这里插入图片描述

相比于IO多路复用,异步IO并不常用,因为目前操作系统对异步IO的支持并不完善,IO多路复用也基本够用. 有很多做法是用IO多路复用模型模拟异步IO(IO事件触发时不直接通知用户线程,而是将数据读写完毕后放到用户指定的缓冲区中)。

3、总结

IO分两阶段:

1.数据准备阶段
2.内核空间复制回用户进程缓冲区阶段

一般来讲:阻塞IO模型、非阻塞IO模型、IO复用模型(select/poll/epoll)、信号驱动IO模型都属于同步IO,因为阶段2是阻塞的(尽管时间很短)。只有异步IO模型是符合POSIX异步IO操作含义的,不管在阶段1还是阶段2都可以干别的事。

参考资料:
https://blog.csdn.net/tjiyu/article/details/52959418
https://www.cnblogs.com/euphie/p/6376508.html
https://www.cnblogs.com/myJavaEE/p/6721127.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值