IO模型(select, poll, epoll的区别和原理)

参考《unix网络编程》

参考http://blog.csdn.net/blueboy2000/article/details/4485874

参考http://blog.csdn.net/suxinpingtao51/article/details/46314097


五种I/O模型

  1. 阻塞I/O:应用程序调用一个IO函数,导致应用程序阻塞,如果数据已经准备好,从内核拷贝到用户空间,否则一直等待下去

  2. 非阻塞I/O:

  3. I/O复用(select和poll)

  4. 信号驱动I/O(SIGIO)

  5. 异步I/O(Posix.1的aio_系列函数)

一个输入操作通常包括两个阶段:
1.等待数据准备好
2.从内核向进程复制数据
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达,当所有等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是将数据从内核缓冲区复制到应用进程的缓冲区

阻塞I/O应用程序调用一个IO函数,导致应用程序阻塞,如果数据已经准备好,从内核拷贝到用户空间,否则一直等待下去
IO模型(select, poll, epoll的区别和原理)_第1张图片

非阻塞I/O模型 
我们把一个套接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量的占用CPU的时间
当一个应用程序像这样对一个非阻塞描述符循环调用recvfrom时,我们称之为轮循(polling)
IO模型(select, poll, epoll的区别和原理)_第2张图片
总结:阻塞I/O模式下,虽然不会占用大量的CPU时间,一个线程只能处理一个流的I/O事件。如果想要同时处理多个流,要么多进程(fork),要么多线程(pthread_create),很不幸这两种方法效率都不高。于是再来考虑非阻塞忙轮询的I/O方式,我们发现我们可以同时处理多个流了(把一个流从阻塞模式切换到非阻塞模式再此不予讨论):
while true {
    for i in stream[]; {
        if i has data
        read until unavailable
    }
}
    我们只要不停的把所有流从头到尾问一遍,又从头开始。这样就可以处理多个流了,但这样的做法显然不好,因为如果所有的流都没有数据,那么只会白白浪费CPU。
为了避免CPU空转,可以引进了一个代理(一开始有一位叫做select的代理,后来又有一位叫做poll的代理,不过两者的本质是一样的)。这个代理比较厉害,可以同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流(于是我们可以把“忙”字去掉了)
while true {
    select(streams[])
    for i in streams[] {
        if i has data
        read until unavailable
    }
}
于是,如果没有I/O事件产生,我们的程序就会阻塞在select处。但是依然有个问题,我们从select那里仅仅知道了,有I/O事件发生了,但却并不知道是那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。即使用select,我们有O(n)的无差别轮询复杂度,同时处理的流越多,没一次无差别轮询时间就越长。
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll之会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

I/O复用模型 
I/O复用模型会用到select或者poll函数,这两个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数。 
IO模型(select, poll, epoll的区别和原理)_第3张图片
我们阻塞于select调用,等待数据报套接字变为可读。当select返回套接字可读这一条件时,我们调用recefrom把所有读数据报复制到应用进程缓冲区。

信号驱动I/O模型 
我们也可以用信号,让内核在描述符就绪时发送SIGIO信号通知我们。通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程继续工作,也就是说它没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已经准备好待处理。
优势:等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。
IO模型(select, poll, epoll的区别和原理)_第4张图片

异步I/O模型 
告知内核启动某个操作,并让内核在整个操作(包括将内核复制到我们自己的缓冲区)完成后通知我们。
与信号驱动模型的主要区别在于:信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步模型是由内核通知我们I/O操作何时完成。
IO模型(select, poll, epoll的区别和原理)_第5张图片

调用aio_read函数,告诉内核描述字,缓冲区指针,缓冲区大小,文件偏移以及通知的方式,然后立即返回。当内核将数据拷贝到缓冲区后,再通知应用程序。 

各种I/O模型的对比:

同步I/O和异步I/O对比:同步I/O操作导致请求阻塞(前4种模型是是同步I/O模型),直到I/O操作完成;异步I/O操作不导致请求进程阻塞。

http://www.cnblogs.com/Anker/p/3265058.html

select, poll, epoll区别以及原理

介绍epoll中函数,以及它们的使用, ET和LT是区别




你可能感兴趣的:(IO模型(select, poll, epoll的区别和原理))