面试的时候也许你被问到过IO模型,可能你知道有五种,可是却不一定能准确指出他们之间的区别,下面我们介绍下这五种IO模型
五种IO模型
-
阻塞IO
-
非阻塞IO
-
IO复用(select和poll)
-
信号驱动
-
异步IO(Posix.1 的 aio...系列函数)
备注
一个输入操作一般分为两个阶段:
等待数据准备好
把数据从内核拷贝到进程
一个套接字的输入操作,第一步是等待数据到达网络,当分组到达时,它被拷贝到内核中的某个缓冲区,第二步是将数据从内核缓冲区拷贝到应用缓冲区
那我们来分别看下这五种IO模型
阻塞IO模型
阻塞IO是最通用的IO模型,使用该模型接收数据时,在数据没有到之前程序会一直等待。例如函数recvfrom,内核会一直阻塞该请求直到有数据到才返回,如下图所示:
如上图所示,进程在调用recvfrom时,该系统调用直到数据到达且拷贝到应用缓冲区或出错才返回。那阻塞的时间就是从调用recvfrom开始到返回的这段时间,当进程返回成功指示时,应用进程开始处理数据报。
非阻塞IO模型
当把套接字设置成非阻塞方式时,即通知内核:当请求的IO操作非得让进程睡眠不能完成时,不要让进程睡眠,而应返回一个错误,直到数据准备好,并将数据拷贝到应用缓冲区返回成功指示,进程调用结束。如下图所示:
从图中我们可以看出,进程会反复调用recvfrom,前三次调用一直没有数据返回,因此立即返回一个EWOULDBLOCK错误,在第四次调用的时候,数据才准备好,并拷贝到了应用缓冲区,recvfrom返回成功指示,然后就时进程处理数据
当一个应用进程像这样对一个非阻塞描述字循环调用recvfrom时,我们称之为轮训。应用进程连续不断的查询内核,验证某操作是否准备好,这样会极大的浪费CPU的时间
IO复用模型
IO复用模型支持调用select或poll,会阻塞在select或poll上,而不是阻塞于真正的IO系统调用。阻塞在select调用,等待数据报套接口可读。当select返回套接口可读条件时,再调用recvfrom将数据拷贝到应用缓冲区。如下入所示:
通过阻塞IO模型和IO复用模型的对比,我们发现由之前的一次系统调用变成了两次系统调用,好像变差了,其实并没有,select可以等待多个描述字准备好,同时select可以设置超时时间。
信号驱动IO模型
信号驱动IO是让内核在描述字准备好时用信号SIGIO通知我们。首先允许套接口进行信号驱动IO,然后通过系统调用sigacation安装信号处理程序。此系统调用立即返回,进程继续工作,是非阻塞的。当数据报准备好被读时,为该进程生成一个SIGIO信号,随后在信号处理程序中调用recvfrom读取数据报,并通知主循环数据已经准备好被处理或者通知主循环让它来读取数据报,如下图所示:
异步IO模型
异步IO让内核启动操作,并在整个操作完成后(包括将数据从内核拷贝到用户空间)通知我们。如下图所示:
异步IO模型和信号驱动IO模型的主要区别是:信号驱动IO是由内核通知我们何时启动IO操作,而异步IO是由内核通知我们IO操作何时完成
五种IO模型的对比
通过上面对五种IO模型的介绍,我们发现前四种IO模型的主要区别点在于第一阶段,第二阶段基本相同:在数据从内核拷贝到调用者的缓冲区时,进程阻塞在recvfrom调用。异步IO模型的两个阶段是和前四种不同的,对比如下图所示:
我们发现五种IO模型没有提到同步IO模型,却提到了异步IO模型,这是为什么?
首先我们看下Posix对同步IO和异步IO的定义:
同步IO操作引起请求进程阻塞,直到IO操作完成
异步IO操作不引起请求进程阻塞
我们可以发现,根据上面的定义,前四种模式:阻塞IO模型、非阻塞IO模型、IO多路复用模型和信号驱动模型,其实都属于同步IO模型,因为他们四个都会经历真正的IO操作(recvfrom)且阻塞了进程,只有异步IO模型与异步IO的定义匹配
参考资料
《UNIX网络编程:卷1》