😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍 Unix / Linux系统的5种IO模型🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
🕛发布时间🕛:2024-02-01 00:09:45
本文未经允许,不得转发!!!
目录
- 🎄一、概述
- ✨1.1 什么是 I/O
- ✨1.2 五种 I/O 模型介绍
- 🎄二、阻塞I/O模型
- 🎄三、非阻塞I/O模型
- 🎄四、复用式I/O模型
- 🎄五、信号驱动式I/O模型
- 🎄六、异步I/O模型
🎄一、概述
✨1.1 什么是 I/O
I/O
是 Input/Output
的缩写,意思是输入输出,一般指操作系统与硬件设备(磁盘、网卡)的数据写入或读取。常见的IO有以下两种:
- 磁盘I/O:与磁盘的数据交互,
读取磁盘数据
或写入数据到磁盘
;在Linux编程中,可以使用open、read、write等函数来操作文件描述符实现与磁盘的数据交互。 - 网络I/O:通过网卡与其他主机或本机其他进程的数据交互;在Linux编程中,可以使用socket、read、write等函数来操作套接字描述符实现网络I/O。
✨1.2 五种 I/O 模型介绍
在了解 I/O 模型之前,先看看操作系统是怎样与磁盘或网卡交换数据(操作I/O)的,一般分为两个步骤,下面以数据输入为例:
- 1、等待数据到达网卡(或数据已存在与磁盘),将数据复制到内核的某个缓冲区;
- 2、再将数据复制到应用层的进程的缓冲区。
之所以这样要分成这两个步骤去操作IO,是因为操作系统是分层的,有内核空间 和 用户空间,操作硬件设备都需要通过内核去操作,用户空间一般不直接操作硬件。
正是因为,数据从硬件设备到应用层缓冲区需要经过内核,所以存在一个等待的过程,这个过程被称为阻塞
。由于这个阻塞的情况,再加上内核一些避免阻塞的机制,使 Unix / Linux 系统有5种 I/O 模型:
阻塞I/O模型
:一直等待数据到应用层缓冲区;非阻塞I/O模型
:不等待数据完成,如果数据没到应用层就返回错误;复用式I/O模型
:不等待数据完成,让其他系统调用帮忙等着;信号驱动式I/O模型
:不等待数据完成,让内核准备好数据后就通知进程;异步I/O模型
:告诉内核要读取哪些数据,让内读取完数据后再通知进程。
🎄二、阻塞I/O模型
阻塞I/O模型
是使用得最多的IO模型,默认情况下,大部分IO操作都是阻塞的。
阻塞是指调用该系统调用后,需要等待内核获取完数据,再把数据复制到应用层缓冲区后,该系统调用才回返回。
如下图:应用层的进程调用recvfrom
后,告诉内核要读取某个套接字的数据,内核就开始等待数据,等到数据准备好了,再将数据复制到应用层的用户空间,复制完成后,recvfrom
函数读取到数据并返回。
阻塞I/O模型,就像你要去买奶茶,但前面很多人在买,你的奶茶没做好,你就一直在奶茶店等着你的奶茶,直到做好拿走。
🎄三、非阻塞I/O模型
进程把一个描述符fd设置成非阻塞是在通知内核:当所请求的I/O操作非得把本进程投入睡眠,才能完成时,不要把本进程投入睡眠,而是返回一个错误(EWOULDBLOCK
)。EWOULDBLOCK
这个单词由would block
组成,表示告诉进程会阻塞
。
看下图,应用层的进程调用recvfrom
获取数据时,因为内核准备好的数据,直接返回EWOULDBLOCK
,紧接着,进程又调用recvfrom
获取数据,内核仍没有准备好的数据,返回EWOULDBLOCK
,前面三次都没有数据,知道第四次调用recvfrom
获取数据时,内核的数据准备好了,将数据复制到用户空间,返回给应用的调用进程。
轮询:当一个进程对一个非阻塞的描述符循环调用recvfrom
,称为轮询。应用程序持续轮询内核,会消耗大量的CPU时间,所以这样轮询访问数据的模型比较少用。轮询访问就类似下面伪代码。
while(1)
{
recvfrom(...);
usleep(1000);
}
非阻塞I/O模型,就像你要去买奶茶,但前面很多人在买,你来拿奶茶时,奶茶店没做好,你就马上走了,过一会又来拿,又没做好,你又走了,过一会又来拿。。。这样循环来查看奶茶做好了没,直到某一次,奶茶做好了,你就拿走。
🎄四、复用式I/O模型
I/O复用
(I/O multiplexing),可以让我们阻塞在其他系统调用函数上,而不是阻塞在真正的I/O系统调用上。但是阻塞在其他系统调用函数也还是阻塞啊,有什么区别吗?区别在于,如果需要对多个(大于1个)描述符进行IO操作时,select或poll系统调用可以同时监控多个描述符是否可读、可写,而阻塞IO只要阻塞在那就干不了其他事情了。
下图使用select函数演示复用I/O模型
,应用层进程调用select
系统调用,并将感兴趣的描述符通知内核,内核等待数据到达,发现select
函数感兴趣的描述符的数据准备好了,就马上让select
返回该描述符可读,然后进程再调用recvfrom
函数来获取数据,因为此时数据已准备好,所以内核直接将数据复制到用户空间,返回给recvfrom
的调用进程。
复用式I/O模型,就像你要去买奶茶,但前面很多人在买,你就花钱找了个黄牛,帮你在那里等,奶茶做好之后,黄牛就打电话让你来拿。
🎄五、信号驱动式I/O模型
信号驱动式I/O模型
,让内核在描述符的数据准备好的时候发SIGIO
信号给进程,进程再调用recvfrom
函数来获取数据。
信号驱动式IO是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程。
针对一个套接字使用信号驱动式IO (SIGIO)要求进程执行以下3个步骤。
(1)建立SIGIO信号的信号处理函数。
(2)设置该套接字的属主,通常使用fcntl的F_SETOWN命令设置。
(3)开启该套接字的信号驱动式IO,通常通过使用fcnt1的F_SETFL命令打开O_ASYNC标志完成。
下图:先建立了SIGIO信号处理函数并设置其他配置,然后使用sigaction
注册SIGIO的处理函数到内核,内核数据准备好了就会给该进程发送SIGIO信号,进程接收SIGIO信号后,进入SIGIO的信号处理函数,然后会调用recvfrom
函数,此时数据已准备好,所以内核直接将数据复制到用户空间,返回给recvfrom
的调用进程。
信号驱动式I/O模型,就像你要去买奶茶,但前面很多人在买,你不想等,于是,你把手机号码给到奶茶店,等奶茶做好了,店主给你打电话,你再去拿奶茶。
🎄六、异步I/O模型
异步I/O
(asynchronous I/O
)的工作机制是:告知内核启动某个I/O操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与信号驱动式I/O模型
的主要区别在于:信号驱动式IO是由内核通知我数据准备好了,可以读取了;而异步I/O通知到进程时,数据已经读取完了。
异步I/O模型,就像你要去买奶茶,但前面很多人在买,你不想等,就干脆点外卖,等奶茶做好了,直接送到你手里,不需要你再去店里拿了。
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁
参考资料:
《Unix网络编程卷1》
https://blog.csdn.net/qq_54015483/article/details/131013426
https://blog.csdn.net/qq_54015483/article/details/130943574
https://blog.csdn.net/Bb15070047748/article/details/124699009