Unix系统下一共有5种I/O模型:
- 阻塞式I/O;
- 非阻塞式I/O;
- I/O复用(select 和 poll);
- 信号驱动的I/O(SIGIO);
- 非同步式I/O(POSIX aio_函数);
对于一个输入操作来说,通常就处于一下两个阶段:
- 等待数据准备好,等待网络上的数据到达;
- 从内核中拷贝数据到进程中,数据包接收到以后,首先是拷贝到内核空间的缓存中,然后再从内核中拷贝到应用程序的缓存中;
1. 阻塞式I/O模型
所有的sockets默认都是阻塞式的。
以UDP为例,UDP协议下,数据是不是已经ready了判断条件很简单:整个数据块收到了;或者没收到。TCP协议的判断就相对要复杂一点,需要判断socket的低水位标志什么的。
当使用系统调用recvfrom()
函数去从一个配置成阻塞的socket来接收数据时,会一直阻塞等待直到数据可用。
初始状态 ------阻塞------> 收到数据(结束)
这种模型下,如果没有数据收到,会一直阻塞在实际的用于I/O的系统调用recvfrom()。
2. 非阻塞式I/O模型
如果sockets是非阻塞式的,recvfrom()
函数会不停的进行系统调用,检查kernel中是否有数据ready
- 如果没有数据ready,立即返回;
- 如果内核中有数据ready,执行数据拷贝到用户空间,然后返回成功,
检查 -> 检查 -> 检查 -> 检查 --阻塞进行数据拷贝> 完成接收
3. I/O复用模型
这种模型下,不同于之前的第一种模型,系统会阻塞在select
或者poll
这两个系统调用之一。
- 第一步,调用
select()
阻塞等待数据ready;一旦数据准备好了,返回成功; - 第二步,调用
recvfrom()
函数来实现从内核空间buffer到进程空间buffer的拷贝,接着返回OK,应用程序可以对接收到的数据进行处理。
这种模型和第一种相比的好处是,可以同时处理多个socket;一旦有哪个socket有数据准备好了,就可以拷贝进用户空间继续处理。
而阻塞式模型整个进程只能阻塞在某一个socket的数据I/O处理;如果有多个socket要进行数据I/O,则必须引入多线程。
检查(poll/select) --阻塞等待--> 数据ready --阻塞进行数据拷贝(recvfrom)--> 完成接收
4. 信号驱动的I/O模型
当内核中有数据ready时,内核会通过SIGNIO信号量来通知应用程序数据可用;接下来就可以通过recvfrom系统调用来完成数据拷贝。
这是一种非阻塞式的I/O方式,用户程序可以继续运行。
内核通知 --阻塞进行数据拷贝(recvfrom)--> 完成接收
5. 非同步的I/O模型
[REF]
Unix Networking Programming
网友评论