作者: Sam (甄峰) sam_code@hotmail.com
0. Linux下的IO模型:
I/O操作, I/O操作通常包括两个阶段:
A. 等待数据准备好。
B. 和内核交换数据。(读写)
对socket上的输入操作来说,
A步骤就是等待数据从网络中到达,此时,数据被存放在内核缓冲区。
B操作则是利用系统调用read, 把数据从内核缓冲区读取到应用程序层。
0.1: Linux下各I/O模型:
Linux下的I/O模型有以下几种:
阻塞式I/O.
非阻塞I/O.
I/O复用。
信号驱动I/O。
异步I/O。
0.1.1: 阻塞式I/O模型:
在低并发情况下,最常用的I/O模型,缺省条件下,所有Socket都是阻塞的。因为逻辑最清晰。
0.1.2: 非阻塞式I/O:
在打开socket时,可以指定为非阻塞式I/O, 此时,会告知Kernel,
当数据没有准备好时,不要阻塞(进程不要被放入休眠队列)。 而是立刻返回错误码。
0.1.3: I/O复用:
I/O复用的意思是: 多个设备同时监控,哪个设备准备好了,就对它进行数据处理和交换。
Linux2.4时代,有select() poll()两个系统调用。 linux2.6则引入了epoll.
select(),
poll()本身是阻塞的。它们像Kernel注册要关注那些文件描述符的哪些状况(如有数据可读写,断连,异常)。
若发生注册的状况。则返回。此时调用对应系统调用来处理。
注意:阻塞是在select()或者poll()上,而非阻塞在I/O系统调用上。
它最大的优势,在于可以等待多个描述符就绪。
0.1.4: 信号驱动I/O。
可以用信号,让内核在描述符就绪时发送SIGIO信号通知我们。称为信号驱动式I/O .我们首先开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程继续工作,也就是说它没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理。也可以立即通知循环,让它读取数据报。
无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。
0.1.5: 异步I/O:
告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与前一节介绍的信号驱动模型的主要区别在于:信号驱动I/O是由内核通知我们如何启动一个I/O操作,而异步I/O模型是由内核通知我们IO操作何时完成: