Unix下5种基本的I/O模型:
1.阻塞I/O
2.非阻塞I/O
3.I/O复用(select和poll)
4.信号驱动I/O(SIGIO)
5.异步I/O(POSIX.1的aio_系列函数)
Unix中一个输入操作一般有两个不同的阶段:
1.等待数据准备好。
2.从内核到进程拷贝数据。
对于一个sockt上的输入操作,第一步一般是等待数据到达网络,当分组到达时,它被拷贝到内核中的某个缓冲区,第二步是将数据从内核缓冲区拷贝到应用程序缓冲区。
下面分别介绍上面提到的5类I/O模型
本文中我们用UDP来进行举例,并且我们将函数recvfrom视为系统调用,这样让我们的注意力都集中在I/O模型上。
阻塞I/O模型
最流行的I/O模型是阻塞I/O模型,缺省时,所有sockt都是阻塞的,这意味着当一个sockt调用不能立即完成时,进程进入睡眠状态,等待操作完成。如图:
图1 阻塞I/O模型
在图1中,进程调用recvfrom,此调用直到数据报到达且拷贝到应用缓冲区或是出错才返回。最常见的错误是系统调用被信号中断,我们所说进程阻塞的整段时间是指从调用recvfrom开始到它返回的这段时间,当进程返回成功指示时,应用进程开始处理数据报。
下面是一个采用阻塞I/O模型编写的简单服务器端代码(本代码来至于Unix网络编程),这段代码的功能是把客户端发来的数据再回射到客户端,本例子中进程阻塞于recvfrom.
#include "unp.h"
void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen);
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr, cliaddr;
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(sockfd, (SA *) &servaddr, sizeof(servaddr));
dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
}
void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
{
int n;
socklen_t len;
char mesg[MAXLINE];
for ( ; ; ) {
len = clilen;
n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
Sendto(sockfd, mesg, n, 0, pcliaddr, len);
}
}
非阻塞I/O模型
当我们把一个sockt设置成非阻塞放式时,即通知内核:当请求的I/O操作非得让进程睡眠才能完成时,不要让进程睡眠,而应该返回一个错误。如图:
图2 非阻塞I/O模型
如图2所示,前3次调用recvfrom时仍无数据返回,因此内核立即返回一个EWOULDBLOCK错误。第4次调用recvfrom时,数据报已经准备好了,被拷贝到应用缓冲区,recvfrom返回成功指示,接着就是我们处理数据报。
当一个应用进程像这样对一个非阻塞sockt循环调用recvfrom时,我们称此过程为轮询(polling).应用进程连续不断的查询内核,看看某操作是否准备好,这对CPU是极大的浪费,但这种模型只是偶尔才遇到。
I/O复用模型
I/O复用能让一个或多个I/O条件满足(例如,输入已经准备好被读,或者描述字可以承接更多的输出)时,我们就被通知到。I/O复用由select和poll支持,较新的Posix.1g也支持(pselect)。
I/O复用典型地用在下列网络应用场合:
1.当客户处理多个描述字时,必须使用。
2.一个客户同时处理多个sockt.
3.如果一个服务器既要处理监听sockt,又要处理连接sockt,一般也用到。
4.如果一个服务器既要处理TCP,又要处理UDP,一般也用到。
5.如果一个服务器要处理多个服务或多个协议(例如inetd守护进程),一般也用到。
I/O复用并非限于网络编程,许多正是应用程序也需要使用这项技术。
有了I/O复用,我们就可以调用select或poll,在这两个系统调用中的某一个上阻塞,而不阻塞于真正的I/O系统调用。图3是I/O复用模型的一个小结。
我们阻塞于select调用,等待数据报socket可读,当select返回socket可读条件时,我们调用recvfrom将数据报拷贝到应用缓存区中。
将图3与图1比较,似乎没有显示什么优越性,实际上因使用了select,要求2此系统调用而不是一次,好像变的还有点差,但是select的好处在于我们可以等待多个描述字准备好。
信号驱动 I/O模型
信号驱动 I/O模型能在描述字准备好时用信号SIGIO通知我们,下图给出例子:
图4 信号驱动 I/O模型
首先我们允许sockt进行信号驱动 I/O,并通过系统调用sigaction安装一个信号处理程序。此系统调用立即返回,进程继续工作,它是非阻塞的。当数据报准备好被读时,就为该进程生成个SIGIO信号。我们随即可以在信号处理程序中调用recvfrom来读取数据报,并通知主循环数据已准备好被处理,也可以通知主循环,让它来处理数据报。
无论我们如何处理SIGIO信号,这种模型的好处是当等待数据报到达时,可以不阻塞。主循环可以继续执行,只是等待信号处理程序的通知:或者数据报已准备好被处理,或者数据报已准备好被读取。
异步I/O模型
异步I/O模型是Posix.1的1993版本中的新内容。我们让内核启动操作,并在整个操作完成后(包括将数据报从内核拷贝到我们自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别在于:信号驱动I/O是有内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。图5给出了一个例子。
图5 异步I/O模型
我们调用aio_red(Posix异步I/O函数以aio_或lio_开头),给内核传递描述字、缓冲区指针、缓冲区大小(与red相同的3个参数)、文件偏移(与lseek类似),并高书内核当整个操作完成时如何通知我们。此系统调用立即返回,我们的进程不阻塞于等待I/O操作的完成。在此例子中,我们假设要求内核在操作完成时产生一个信号,此信号直到数据已拷贝到应用程序缓冲区才产生,这一点是于信号驱动I/O模型不同的。
各种I/O模型的比较
图6给出了上述5中I/O模型的比较。它表明:前4种模型的区别都在第1阶段,因为前4种模型的第2阶段基本相同:在数据从内核拷贝到调用者的缓冲区时,进程阻塞于recvfrom调用。然而异步I/O处理的两个阶段都不同于前4个模型。
同步I/O与异步I/O
Posix.1定义这两个术语如下:
1.同步I/O操作引起请求进程阻塞,直到I/O操作完成。
2.异步 I/O操作不引起请求进程阻塞。
根据上述定义,我们的前四个I/O模型都是同步I/O模型,因为真正的I/O操作(recvfrom)阻塞进程,只有异步I/O模型与异步I/O的定义相符合。
转载自:http://blog.sina.com.cn/s/blog_5f4344bf0100cklt.html