Unix 五种 I/O 模型
I/O模型是网络编程中必须要掌握的,也是计算机系统的重要内容之一,涉及通信的系统软件都会围绕文件描述符、I/O模型等知识展开。理解I/O模型是我们程序员的基本功,然而很多人其实并不清楚。
经典的《Unix网络编程》中介绍的五种I/O模型:
- 阻塞式I/O
- 非阻塞式I/O
- I/O复用 (select、poll、epoll)
- 信号驱动式I/O (SIGIO)
- 异步I/O (aio_系列函数)
介绍这些I/O模型之前,有一个重要知识点必须要明确:一个输入操作通常包括两个阶段:
- 内核态等待数据准备好(内核缓冲区接收数据)
- 从内核态往用户态拷贝数据(内核缓冲区数据到进程缓冲区)
内核态就是操作系统的Kernel,用户态就是用户进程。比如32位计算机,4G内存会分配1G作为内核空间,其它3G作为用户空间。用户进程通过系统调用切换到内核态,内核态才有权限操作系统资源,比如磁盘、键盘这种外设。
- 内核缓冲区,可以减少和磁盘外设交互,提高I/O效率;
- 用户缓冲区,可以减少系统调用的次数,换句话说就是减少用户态与内核态切换的成本。
阻塞式I/O
大部分我们用到的I/O系统调用默认都是阻塞的,就是如下图这样。注意这两个步骤:1. 等待内核缓冲区数据准备好 2. 数据从内核空间拷贝到用户空间。 一个recv操作等待两个步骤完成,一直处于阻塞。
非阻塞式I/O
非阻塞I/O,是体现在系统调用是立即返回的,而不是一直阻塞在等待内核准备好数据。这需要用户进程一直轮询这个调用,直到等到内核准备好数据。此时,仍然要阻塞在这个步骤,从内核拷贝数据到用户空间。
I/O多路复用
IO多路复用使用select、poll、epoll等系统调用(以select为例),阻塞的是select系统调用,也可以看出来select得到可读状态时也只是内核态准备好了,内核态拷贝到用户态时recv仍然是阻塞的。如果单从时序来看,IO复用似乎和阻塞IO没啥区别,确实是的。但是当有多个客户端连接,或者说多个文件描述符时,IO复用就能提现出其高效了。
信号驱动式I/O
从示意图中能看出,通过SIGIO系统调用是非阻塞的,而且不需要应用进程去轮询IO结果(进程可以去干别的),当内核缓冲区准备好时会有信号通知应用进程,应用进程仍需要阻塞接收数据。
异步I/O
异步I/O模型中,这些系统调用会告知内核在整个操作(包括步骤1和2)完成后,通知应用进程。这和信号驱动式I/O不同之处在于,信号驱动是内核缓冲区准备好了告知应用进程你可以I/O操作了;异步I/O是整个完成了,内核通知了应用进程。
几种I/O模型的对比
示意图清晰描述了几种I/O模型
参考
- 《Unix网络编程》