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,则程序的运行将不会出现死循环的情况。

浙公网安备 33010602011771号