Linux:5 种 I/O 模型

本文介绍了Linux中的五种I/O模型:阻塞、非阻塞、IO复用、信号驱动和异步,详细解析了各模型的工作原理和特点,包括它们在数据读取中的两个阶段。阻塞和非阻塞模型关注进程是否被IO操作阻塞,而同步和异步关注数据拷贝时进程是否阻塞。异步IO模型是唯一不阻塞的模型,适合高性能、高并发场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

前言

1、5种IO模型

1.1、阻塞IO模型

1.2、非阻塞IO模型

1.3、IO复用模型

1.4、信号驱动IO模型

1.5、异步IO模型

2、各种 I/O 模型比较

2.1、阻塞IO调用和非阻塞IO调用、阻塞IO模型和非阻塞IO模型

2.2、同步IO和异步IO

2.3、同步、异步、阻塞、非阻塞含义


前言

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

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

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

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

        1、等待网络数据到达网卡,之后读取到内核缓冲区,数据准备好。

        2、从内核缓冲区复制数据到进程空间缓冲区。

1、5种IO模型

       《UNIX网络编程》卷1中的第六章节中罗列了 Unix 下可用的 5 种 IO 模型,分别是阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动的IO模型、异步IO模型;前 4 种为同步 IO 操作,只有异步 IO 模型是异步IO操作。下面这样些图,是它里面给出的例子:接收网络 UDP 数据的流程在 IO 模型下的分析,在它的基础上再加以简单描述,以区分这些 IO 模型。UDP类型的套接字收发接口为:recvfrom 和 sendto

1.1、阻塞IO模型

                    
       应用进程发起 IO 系统调用 recvfrom 后,进程被阻塞,此时进程由用户空间转到内核空间处理 IO请求,直到数据包到达且被复制到应用进程的缓存区中或者发生错误才返回,在内核空间 “等待” 处理 IO 操作就是阻塞,此时进程会让出CPU。整个 IO 处理完毕后进程由内核态转到用户态。应用进程根据系统调用的返回结果继续操作:1、处理已经读到用户空间缓冲区的数据;2、异常处理。

应用场景:阻塞socket(默认模型)、Java BIO;

应用举例:《socket编程:阻塞式、非阻塞 I/O模型实现》

优缺点:1、进程阻塞挂起不消耗CPU资源,及时响应每个操作;2、实现难度低、编码简单;3、适用并发量小的网络应用开发;4、不适用并发量大的应用:因为一个请求IO会阻塞进程(说明一下此处的进程指的是linux中的线程或者task),所以,得为每请求分配一个处理进程(线程)以及时响应,系统开销大。

1.2、非阻塞IO模型

                        
       进程发起IO系统调用后,如果内核缓冲区没有数据,需要到 IO 设备中读取,进程返回一个错误而不会被阻塞;进程发起 IO系统调用后,如果内核缓冲区有数据,内核就会把数据返回进程。上图所示,前三次的recvfrom调用时没有数据返回,内核直接返回 EWOULDBLOCK,第四次调用时有一个数据包准备好,它被复制到应用缓冲区(说明:在复制数据的过程中进程还是会阻塞的),于是成功返回,应用进程拿到数据后即可处理。上述进程对一个非阻塞描述符(此处为socket)进行循环读取时称之为polling(轮询),这个过程会消耗大量的CPU。

应用场景:socket是非阻塞的方式(设置为NONBLOCK),Linux下可以通过 ioctl、fcntl 两个接口来设置。

应用举例:《socket编程:阻塞式、非阻塞 I/O模型实现》

优缺点:1、进程轮询(重复)调用,消耗CPU的资源;2、实现难度低、开发应用相对阻塞IO模式较难;3、 适用并发量较小、且不需要及时响应的网络应用开发;

1.3、IO复用模型

        将多个 IO 可以注册到一个复用器(select/poll/epoll)上,然后用一个进程调用该 select, select 会监听所有注册进来的 IO,进程的阻塞位置放在select上而不是阻塞真的I/O系统调用上。

                             
       从上图中看,相比阻塞 I/O 模式,采用多路复用增加了一个select系统调用,从这一点上看I/O复用还有劣势,但是多线程阻塞式I/O模型相比,用一个 select 就可以完成对多个 I/O 进行同时监控。

       如果 select 没有监听的 IO 在内核缓冲区都没有可读数据,select 调用进程(task)会被阻塞;而当任一 IO 在内核缓冲区中有可数据时,select调用就会返回;而后 select 调用进程可以自己或通知另外的进程(注册进程)来再次发起读取IO,读取内核中准备好的数据(读取过程中,进程依然阻塞)。可以看到,多个进程注册IO后,只有一个select调用进程被阻塞。

应用场景:select、poll、epoll三种方案,nginx都可以选择使用这三个方案;Java NIO;

应用举例:《socket编程:select 实现多路复用》

优缺点:一个 task监控多路I/O,编程难度大。关于select、poll、epoll的区别参见《socket编程:select、poll、epoll 区别》

1.4、信号驱动IO模型

       当进程发起一个IO操作,会向内核注册一个信号处理函数,然后进程返回不阻塞,继续执行;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用 IO 读取数据。

                  

       上图所示:我们首先开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程没有阻塞继续工作,当数据报准备好读取时,内核将会向该进程抛出一个SIGIO(29号信号量)。我们随后即可以在信号处理函数中调用 recvfrom 读取数据包。需要特别说明一点当:我们在信号处理函数中循环读取数据时,假设内核的数据并没有准备好,此时进程还是会阻塞 recvfrom 调用处。一言以蔽之:信号驱动模型依然是 “阻塞模型”,因为真正的I/O操作还是会阻塞进程。真正不会让应用进程阻塞的只有异步I/O。 

应用举例:《socket编程:信号驱动式I/O》     

优缺点:回调机制,实现、开发应用难度大,我们在编写信号处理函数(拷贝内核数据时)中不要出现死循环否则进程永远无法跳出到之前运行的地方;

1.5、异步IO模型

       当进程发起一个 IO 操作,进程返回(不阻塞),但也不能返回结果;内核把整个 IO 处理完后(包括将数据从内核空间拷贝到用户空间),会通知进程结果。如果 IO 操作成功则进程直接获取到数据。该模型和信号驱动模型的主要区别在于:信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作(即何时调用 recvfrom),而异步I/O模型是由内核来告知我们I/O操作何时完成。具体模式图示如下:

                                  

       上图所示调用 aio_read 函数,给内核传递描述符、缓冲区指针、缓存区大小和文件偏移,并告诉内核当整个操作完成时通知我们,该系统调用立即返回,而且在等待I/O完成期间,我们的进程不会被阻塞。本例子中我们假设要求内核在操作完成时产生某个信号。该信号直到数据已复制到应用进程缓冲区才产生,这一点不同于信号驱动式I/O模型。

应用场景:JAVA7 AIO、高性能服务器应用

应用举例:无

优缺点:1、不阻塞,数据一步到位;Proactor模式;2、需要操作系统的底层支持,LINUX 2.5 版本内核首现,2.6 版本产品的内核标准特性;3、实现、开发应用难度大;4、非常适合高性能高并发应用;

2、各种 I/O 模型比较

                               

       上图对比了 5 种不同的 I/O 模型。可以看出,前4种模型的主要区别在于第一阶段,因为它们的第二阶段是一样的:在数据从内核复制到调用者的缓存期间也就是说这个数据复制操作正在进行时,应用进程阻塞于 recvfrom 调用处。相反,异步 I/O 模型在这两个阶段都要处理,从而区别于其它四种模型。

2.1、阻塞IO调用和非阻塞IO调用、阻塞IO模型和非阻塞IO模型

        注意这里的阻塞IO调用和非阻塞IO调用不是指阻塞IO模型和非阻塞IO模型:

        阻塞IO调用 :在用户进程(线程)中调用执行的时候,进程会等待该IO操作,而使得其他操作无法执行。

        非阻塞IO调用:在用户进程中调用执行的时候,无论成功与否,该IO操作会立即返回,之后进程可以进行其他操作(当然如果是读取到数据,一般就接着进行数据处理)。

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

        这个直接理解就好,进程(线程)IO调用会不会阻塞进程自己。所以这里两个概念是相对调用进程本身状态来讲的。

        上图 6.6 所示:阻塞IO模型:一个阻塞IO调用;非阻塞IO模型:多个非阻塞IO调用 + 一个阻塞IO调用,因为多个IO检查会立即返回错误,不会阻塞进程。

        而上面也说过了,非阻塞IO模型对于阻塞IO模型来说区别就是,内核数据没准备好需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞。

关于进程阻塞的扩展定义:进程通过系统调用( recvfrom )下沉到内核态,在内核态中等待。

2.2、同步IO和异步IO

        同步 IO 操作:导致请求进程阻塞,直到I/O操作完成。

        异步 IO 操作:不导致请求进程阻塞。

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

       上面两个定义是《UNIX网络编程 卷1:套接字联网API》给出的。这不是很好理解,我们来扩展一下,先说说同步和异步,同步和异步关注的是双方的消息通信机制:

        同步:双方的动作是经过双方协调的,步调一致的。

        异步:双方并不需要协调,都可以随意进行各自的操作。

        这里我们的双方是指,用户进程和IO设备;明确同步和异步之后,我们在上面网络输入操作例子的基础上,进行扩展定义:

        同步IO:用户进程发出 IO 调用,去获取 IO 设备数据,双方的数据要经过内核缓冲区同步,完全准备好后,再复制返回到用户进程。而复制返回到用户进程会导致请求进程阻塞,直到 I/O 操作完成。

       异步IO:用户进程发出IO调用,去获取IO设备数据,并不需要同步,内核直接复制到进程,整个过程不导致请求进程阻塞。关于这里的异步原因是:本用户进程调用aio_read过程经历了从用户态->内核态->用户态,与上文同步的差别就在本进程没有在内核态中发生阻塞 等待,那么这个数据拷贝过程必然是由另外一个内核进程所完成的。”,这一段话与兄弟们一起探讨,希望大家多提意见。

       所以, 阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动的IO模型者为同步IO模型,只有异步IO模型是异步IO。

2.3、同步、异步、阻塞、非阻塞含义

        同步:就是我调用一个功能,该功能没有结束前,我死等结果。
        异步:就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)。
        阻塞:就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
        非阻塞:就是调用我(函数),我(函数)立即返回,通过select通知调用者
        同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞
        阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回
综上可知,同步和异步,阻塞和非阻塞,有些混用,其实它们完全不是一回事,而且它们修饰的对象也不相同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值