epoll 水平触发与边缘触发

epoll也是实现I/O多路复用的一种方法,为了深入了解epoll的原理,我们先来看下epoll水平触发(level trigger,LT,LT为epoll的默认工作模式)与边缘触发(edge trigger,ET)两种工作模式。

使用脉冲信号来解释LT和ET可能更加贴切。Level是指信号只需要处于水平,就一直会触发;而edge则是指信号为上升沿或者下降沿时触发。说得还有点玄乎,我们以生活中的一个例子来类比LT和ET是如何确定读操作是否就绪的。

水平触发

1. 对于读操作

只要缓冲内容不为空,LT模式返回读就绪。

2. 对于写操作

只要缓冲区还不满,LT模式会返回写就绪。

边缘触发

1. 对于读操作

(1)当缓冲区由不可读变为可读的时候,即缓冲区由空变为不空的时候。

(2)当有新数据到达时,即缓冲区中的待读数据变多的时候。

(3)当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件时。

2. 对于写操作

(1)当缓冲区由不可写变为可写时。

(2)当有旧数据被发送走,即缓冲区中的内容变少的时候。

(3)当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLOUT事件时。

边缘触发实验

实验1对标准输入文件描述符使用ET模式进行监听。当我们输入一组字符并接下回车时,屏幕中会输出”hello world”。但是没有调用read函数将缓存区的数据取出来, 缓冲区的数据还是留在那。

 1 #include <unistd.h>
 2 #include <stdio.h>
 3 #include <sys/epoll.h>
 4 
 5 int main()
 6 {
 7     int epfd, nfds;
 8     struct epoll_event event, events[5];
 9     epfd = epoll_create(1);
10     event.data.fd = STDIN_FILENO;
11     event.events = EPOLLIN | EPOLLET;
12     epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
13     while (1) {
14         nfds = epoll_wait(epfd, events, 5, -1);
15         int i;
16         for (i = 0; i < nfds; ++i) {
17             if (events[i].data.fd == STDIN_FILENO) {
18                 printf("hello world\n");
19             }
20         }
21     }
22 }

输出:

$ ./epoll1
a
hello world
abc
hello world
hello
hello world
ttt
hello world

当用户输入一组字符,这组字符被送入缓冲区,因为缓冲区由空变成不空,所以ET返回读就绪,输出”hello world”。
之后再次执行epoll_wait,但ET模式下只会通知应用进程一次,故导致epoll_wait阻塞。
如果用户再次输入一组字符,导致缓冲区内容增多,ET会再返回就绪,应用进程再次输出”hello world”。
如果将上面的代码中的event.events = EPOLLIN | EPOLLET;改成event.events = EPOLLIN;,即使用LT模式,则运行程序后,会一直输出hello world。

水平触发实验

实验2对标准输入文件描述符使用LT模式进行监听。当我们输入一组字符并接下回车时,屏幕中会输出”hello world”。

 1 #include <unistd.h>
 2 #include <stdio.h>
 3 #include <sys/epoll.h>
 4 
 5 int main()
 6 {
 7     int epfd, nfds;
 8     char buf[256];
 9     struct epoll_event event, events[5];
10     epfd = epoll_create(1);
11     event.data.fd = STDIN_FILENO;
12     event.events = EPOLLIN;  // LT是默认模式
13     epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
14     while (1) {
15         nfds = epoll_wait(epfd, events, 5, -1);
16         int i;
17         for (i = 0; i < nfds; ++i) {
18             if (events[i].data.fd == STDIN_FILENO) {
19                 read(STDIN_FILENO, buf, sizeof(buf));
20                 printf("hello world\n");
21             }
22         }
23     }
24 }

输出:

$ ./epoll2
abc
hello world
eeeee
hello world
lihao
hello world

实验2中使用的是LT模式,则每次epoll_wait返回时我们都将缓冲区的数据读完,下次再调用epoll_wait时就会阻塞,直到下次再输入字符。
如果将上面的程序改为每次只读一个字符,那么每次输入多少个字符,则会在屏幕中输出多少个“hello world”。有意思吧。

 

实验3

实验3对标准输入文件描述符使用ET模式进行监听。当我们输入任何输入并接下回车时,屏幕中会死循环输出”hello world”。

 1 #include <unistd.h>
 2 #include <stdio.h>
 3 #include <sys/epoll.h>
 4 
 5 int main()
 6 {
 7     int epfd, nfds;
 8     struct epoll_event event, events[5];
 9     epfd = epoll_create(1);
10     event.data.fd = STDIN_FILENO;
11     event.events = EPOLLIN | EPOLLET;
12     epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
13     while (1) {
14         nfds = epoll_wait(epfd, events, 5, -1);
15         int i;
16         for (i = 0; i < nfds; ++i) {
17             if (events[i].data.fd == STDIN_FILENO) {
18                 printf("hello world\n");
19                 event.data.fd = STDIN_FILENO;
20                 event.events = EPOLLIN | EPOLLET;
21                 epoll_ctl(epfd, EPOLL_CTL_MOD, STDIN_FILENO, &event);
22             }
23         }
24     }
25 }

实验3使用ET模式,但是每次读就绪后都主动对描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件,由上面的描述我们可以知道,会再次触发读就绪,这样就导致程序出现死循环,不断地在屏幕中输出”hello world”。但是,如果我们将EPOLL_CTL_MOD 改为EPOLL_CTL_ADD,则程序的运行将不会出现死循环的情况。

 

posted @ 2020-04-04 12:58  LyinTomy  阅读(813)  评论(0)    收藏  举报