水平触发和边沿触发

默认情况下epol1提供的是水平触发通知。这表示epol1 会告诉我们何时能在文件描述符上以非阻塞的方式执行I/O 操作。这同po11()和select ()所提供的通知类型相同。

epoll API还能以边缘触发方式进行通知---也就是说, 会告诉我们自从上一次调用epo11_ wait ()以来文件描述符上是否已经有I/0 活动了(或者由于描述符被打开.了,如果之前没有调用的话)。使用epo11的边缘触发通知在语义上类似于信号驱动I/O,只是如果有多个I/0事件发生的话,epoll 会将它们合并成一-次单独的通知,通过epo11_ wait()返回,而在信号驱动I/0中则可能会产生多个信号。要使用边缘触发通知,我们在调用epo11_ ct1()时在ev. events字段中指定EPOLLET标志。

水平触发和边沿触发_第1张图片

 

我们通过一个例子来说明epoll 的水平触发和边缘触发通知之间的区别。假设我们使用epoll 来监视一个套接字上的输入(EPOLLIN) ,接下来会发生如下的事件。

1.套接字上有输入到来。

2.我们调用一-次epoll wait()。无论我们采用的是水平触发还是边缘触发通知,该调用都会告诉我们套接字已经处于就绪态了。

3.再次调用epo11_ wait()。

如果我们采用的是水平触发通知,那么第二个epoll_ wait (调用将告诉我们套接字处于就绪态。而如果我们采用边缘触发通知,那么第二个epoll wait()调用将阻塞,因为自从上一次调用epoll_ wait()以来并没有新的输入到来。

边缘触发通知通常和非阻塞的文件描述符结合使用。因而,采用epoll 的边缘触发通知机制的程序基本框架如下。

1.让所有待监视的文件描述符都成为非阻塞的。

2.通过epol1_ ct1()构建epo1l 的兴趣列表。

3.通过如下的循环处理I/0事件:a. 通过epo11_ wait()取得处于就绪态的描述符列表。b.针对每-一个处于就绪态的文件描述符,不断进行I/0 处理直到相关的系统调用(例如read()、 write()、recv()、 send() 或accept())返回EAGAIN或EWOULDBLOCK错误。

水平触发通知:如果文件描述符上可悱阻塞地执行I/0系统调用,此时认为它已经就绪。

边沿触发通知:如文件描述符自上次状态检查以来有了新的I/0活动( 比如新的输入)此时需要触发通知。

下表总结了IO多路复用、信号驱动I/0以及epoll所采用的通知模型。epoll API同其他两种I/0模型的区别在于它对水平触发(默认)和边缘触发都支持。

水平触发和边沿触发_第2张图片

 

当采用水平触发通知时,我们可以在任意时刻检查文件描述符的就绪状态。这表示当我们确定了文件描述符处于就绪态时(比如存在有输入数据),就可以对其执行一些I/0操作,然后重复检查文件描述符,看看是否仍然处于就绪态(比如还有更多的输入数据),此时我们就能执行更多的I/0,以此类准。换句话说,由于水平触发模式允许我们在任意时刻重复检查I/0状态,没有必要每次当文件描述符就绪后需要尽可能多地执行I/0 (也就是尽可能多地读取字节,亦或是根本不去执行任何I/0)。与之相反的是,当我们采用边缘触发时,只有当I/0事件发生时我们才会收到通知。在另一个I/0事件到来前我们不会收到任何新的通知。另外,当文件描述符收到I/0事件通知时,通常我们并不知道要处理多少I0 (例如有多少字节可读)。因此,采用边缘触发通知的程序通常要按照如下规则来设计。在接收到一个I0事件通知后,程序在某个时刻应该在相应的文件描述符上尽可能多地执行I/0(比如尽可能多地读取字节)。如果程序没这么做,那么就可能失去执行I/0的机会。因为直到产生另一个I/0事件为止,在此之前程序都不会再接收到通知了,因此也就不知道此时应该执行I/0操作。这将导致数据丢失或者程序中出现阻塞。前面我们说“在某个时刻”,是因为有时候当我们确定了文件描述符是就绪态时,此时可能并不适合马上执行所有的I0操作。问题的原因在于如果我们仅对一个文件描述符执行大量的I/0操作,可能会让其他文件描述符处于饥饿状态。如果程序采用循环来对文件描述符执行尽可能多的I/0,而文件描述符又被置为可阻塞的,那么最终当没有更多的I/0可执行时,I/0系统调用就会阻塞。基于这个原因,每个被检查的文件描述符通常都应该置为非阻塞模式,在得到I/0事件通知后重复执行I/0操作,直到相应的系统调用(比如read(),write()以错误码EAGAIN 或EWOULDBLOCK的形式失败。

非阻塞I/O通常和提供由边缘触发通知机制的I/O模型一起使用。如果多个进程(或线程)在同一个打开的文件描述符上执行I/0操作, 那么从某个特定进程的角度来看,文件描述符的就绪状态可能会在通知就绪和执行后续I/0调用之间发生改变。结果就是一个阻塞式的I/0调用将阻塞,从而阻止进程检查其他的文件描述符。(这种情 况会发生在本章所描述的所有I/0模型上,无论它们采用的是水平触发还是边缘触发。)尽管水平触发模式的API比如select )或po11 ()通知我们流式套接字的文件描述符已经写就绪了,如果我们在单个write 0或send()调用中写入足够大块的数据,那么该调用将阻塞。在非常罕见的情况下, 水平触发型的API比如select(和 po11), 会返回虚假的就绪通知-一---它们会错误地通知我们文件描述符已经就绪了。这可能是由内核bug造成的,或非普通情况下的设计方案所期望的行为。

你可能感兴趣的:(linux网络编程,网络)