五种I/O模型
- 阻塞I/O
- 非阻塞I/O
- 信号驱动I/O
- 多路转接(多路复用) I/O
- 异步I/O
前面四种属于同步IO模型。
预备知识
I/O的两个步骤:
- (1)等待I/O事件就绪。
就是等待内核将数据准备好 - (2)执行I/O操作。
将数据从内核缓冲区读入用户缓冲区,或者将数据从用户缓存区写入内核缓冲区。所谓的读入和写入本质是将数据拷贝过来、拷贝过去。
阻塞的文件描述符称为阻塞I/O;
I/O阻塞执行的系统调用,如果相关事件没有就绪,意味着不能被立即完成,此时就会被操作系统挂起,直到被等待事件就绪。
- 如果内核还没有将数据准备好,系统调用仍然会直接返回,并且设置
EWOULDBLOCK
错误码。
所有的套接字创建的时候默认都是阻塞的,套接字API中,可能被阻塞的系统调用包括accept、send、recv、connect。
非阻塞的文件描述符称为非阻塞I/O;
非阻塞I/O执行的系统调用,不管相关事件是否发生,总是立即返回。往往需要程序员使用循环的方式返回尝试读写文件描述符,这个过程称为轮询。
如果等待的事件没有立即发生,这些系统调用会以出错的形式返回的。其实它不是真正地调用失败,事件未就绪
和真正的调用出错
都是以出错的形式返回,区分这两个情况的关键在于错误码:
- 非阻塞I/O事件未就绪设置错误码
(1)accept、send、recv而言,错误码通常被设置为EAGAIN
(“再来一次”)/EWOULDBLOCK
(“期望阻塞”)。
(2)connect而言,错误码通常被设置为EINPROGRESS
(“在处理中”)。
显然,非阻塞I/O很容易浪费CPU资源,通常不单独使用,而是和其他I/O通知机制一起使用
信号驱动I/O
可以为目标文件描述符指定宿主进程,这个进程将捕获到SIGIO
信号。当内核将该目标文件描述符上的IO事件准备就绪时,会使用SIGIO信号通知应用进程进行I/O操作,然后SIGIO信号的信号处理函数会被触发。我们可以在SIGIO信号处理函数内部对目标文件描述符执行非阻塞I/O操作。
多路复用I/O
应用进程通过I/O复用函数向内核注册一组事件,能够同时等待多个文件描述符的I/O事件就绪,内核通过I/O复用函数将其中就绪的事件通知给应用进程。
Linux上常用的I/O复用函数:select、poll、epoll_wait。
注意:
- 程序阻塞于I/O复用系统调用,而对I/O本身的读写操作是非阻塞的。
- 多路复用是同步I/O中最高效的I/O模型,原因在于它们具有同时监听多个I/O事件的能力。
异步I/O
内核在数据拷贝完成后,通知应用程序(信号驱动是告诉应用程序何时开始拷贝数据)。
同步I/O与异步I/O的区别:
- 同步I/O要求用户自行执行I/O操作。内核将数据准备好后,由用户自己进行读写操作。
- 异步I/O由内核执行I/O操作。用户直接执行I/O操作,会告诉内核用户读写缓冲区的位置,以及I/O操作完成后内核通知应用程序的方式,接下来的I/O操作就交由内核接管。
Linux的aio.h头文件中定义了支持异步I/O的函数。
I/O模型 | 阻塞阶段 |
---|---|
阻塞I/O | 阻塞于读写函数 |
非阻塞I/O | 无阻塞阶段 |
信号驱动I/O | 无阻塞阶段 |
多路复用I/O | 阻塞于I/O复用系统调用,对I/O本身的读写操作是非阻塞的 |
异步I/O | 无阻塞阶段 |
版权声明:本文为qq_56870066原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。