epoll 水平触发与边缘触发

https://blog.csdn.net/lihao21/article/details/67631516?ref=myread

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

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

水平触发 
儿子:妈妈,我收到了500元的压岁钱。 
妈妈:嗯,省着点花。 
儿子:妈妈,我今天花了200元买了个变形金刚。 
妈妈:以后不要乱花钱。 
儿子:妈妈,我今天买了好多好吃的,还剩下100元。 
妈妈:用完了这些钱,我可不会再给你钱了。 
儿子:妈妈,那100元我没花,我攒起来了 
妈妈:这才是明智的做法! 
儿子:妈妈,那100元我还没花,我还有钱的。 
妈妈:嗯,继续保持。 
儿子:妈妈,我还有100元钱。 
妈妈:…

接下来的情形就是没完没了了:只要儿子一直有钱,他就一直会向他的妈妈汇报。LT模式下,只要内核缓冲区中还有未读数据,就会一直返回描述符的就绪状态,即不断地唤醒应用进程。在上面的例子中,儿子是缓冲区,钱是数据,妈妈则是应用进程了解儿子的压岁钱状况(读操作)。

边缘触发 
儿子:妈妈,我收到了500元的压岁钱。 
妈妈:嗯,省着点花。 
(儿子使用压岁钱购买了变形金刚和零食。) 
儿子: 
妈妈:儿子你倒是说话啊?压岁钱呢?

这个就是ET模式,儿子只在第一次收到压岁钱时通知妈妈,接下来儿子怎么把压岁钱花掉并没有通知妈妈。即儿子从没钱变成有钱,需要通知妈妈,接下来钱变少了,则不会再通知妈妈了。在ET模式下, 缓冲区从不可读变成可读,会唤醒应用进程,缓冲区数据变少的情况,则不会再唤醒应用进程。

我们再详细说明LT和ET两种模式下对读写操作是否就绪的判断。

水平触发

1. 对于读操作

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

2. 对于写操作

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

边缘触发

1. 对于读操作

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

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

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

2. 对于写操作

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

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

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

实验

实验1

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

#include <unistd.h>
#include <stdio.h>
#include <sys/epoll.h>int main()
{int epfd, nfds;struct epoll_event event, events[5];epfd = epoll_create(1);event.data.fd = STDIN_FILENO;event.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);while (1) {nfds = epoll_wait(epfd, events, 5, -1);int i;for (i = 0; i < nfds; ++i) {if (events[i].data.fd == STDIN_FILENO) {printf("hello world\n");}}}
}

输出:

$ ./epoll1 

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

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

#include <unistd.h>
#include <stdio.h>
#include <sys/epoll.h>int main()
{int epfd, nfds;char buf[256];struct epoll_event event, events[5];epfd = epoll_create(1);event.data.fd = STDIN_FILENO;event.events = EPOLLIN;  // LT是默认模式epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);while (1) {nfds = epoll_wait(epfd, events, 5, -1);int i;for (i = 0; i < nfds; ++i) {if (events[i].data.fd == STDIN_FILENO) {read(STDIN_FILENO, buf, sizeof(buf));printf("hello world\n");}}}
}

输出:

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

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

实验3

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

#include <unistd.h>
#include <stdio.h>
#include <sys/epoll.h>int main()
{int epfd, nfds;struct epoll_event event, events[5];epfd = epoll_create(1);event.data.fd = STDIN_FILENO;event.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);while (1) {nfds = epoll_wait(epfd, events, 5, -1);int i;for (i = 0; i < nfds; ++i) {if (events[i].data.fd == STDIN_FILENO) {printf("hello world\n");event.data.fd = STDIN_FILENO;event.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_MOD, STDIN_FILENO, &event);}}}
}

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

参考资料

  1. http://blog.lucode.net/linux/epoll-tutorial.html
  2. http://blog.chinaunix.net/uid-28541347-id-4285054.html
  3. http://blog.chinaunix.net/uid-28541347-id-4288802.html
  4. https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383655.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络【3】网络层

主要任务时把分组从源端发送到目的端&#xff0c;为分组交换网上的不同主机提供服务。网络层传输单位是数据报 功能&#xff1a; 路由选择与分组转发&#xff08;最佳路径 &#xff09;异构网络互联拥塞控制 数据交换方式 电路交换&#xff1a;通信时延小、有序传输、没有冲…

C++空类的大小

https://blog.csdn.net/lihao21/article/details/47973609 本文中所说是C的空类是指这个类不带任何数据&#xff0c;即类中没有非静态(non-static)数据成员变量&#xff0c;没有虚函数(virtual function)&#xff0c;也没有虚基类(virtual base class)。 直观地看&#xff0c…

Linux探秘之用户态与内核态

https://www.cnblogs.com/bakari/p/5520860.html 一、 Unix/Linux的体系架构 如上图所示&#xff0c;从宏观上来看&#xff0c;Linux操作系统的体系架构分为用户态和内核态&#xff08;或者用户空间和内核&#xff09;。内核从本质上看是一种软件——控制计算机的硬件资源&…

哈夫曼算法证明+哈夫曼编码译码程序实现

哈夫曼算法证明 哈夫曼算法是一种贪心算法&#xff0c;我们考虑证明其最优子结构和贪心选择性质&#xff1a; 最优子结构&#xff1a;假设一个树是哈夫曼树&#xff0c;则以其任意节点为根节点的最大子树也是哈夫曼树。 证明&#xff1a;子树的根节点的值是其所有叶子节点出现…

Python3小知识

对于迭代器对象&#xff0c;Python默认赋值是将引用赋值&#xff0c;即指向同一片内存空间。为了实现对内存空间的赋值&#xff0c;我们可以使用分片进行深复制。例如&#xff1a; 当定义元组的时候&#xff0c;我们一般使用小括号将元素包围起来&#xff0c;也可以不使用括号…

汇编:实现日历星期数查询工具

编制一个简单日历查询工具&#xff0c;输入年、月、日&#xff0c;能够判断当日的星期数&#xff0c;并进行输出&#xff0c;数据的输入和结果的输出要有必要的提示&#xff0c;且提示独占一行。 查阅资料 ​ 经过查阅资料&#xff0c;发现有两个相关的算法可以解决这个问题&…

一个通用纯C队列的实现

https://blog.csdn.net/kxcfzyk/article/details/31728179 队列并不是很复杂的数据结构&#xff0c;但是非常实用&#xff0c;这里实现一个队列是因为在我的另一篇博客非常精简的Linux线程池实现中要用到。 队列API定义如下&#xff1a; //queue.h #ifndef QUEUE_H_INCLUDED…

Dijkstra算法介绍+正确性证明+性能分析

算法介绍 源点s,数组d[u]表示s到u的最短距离&#xff0c;空集S&#xff0c;点集Q初始化&#xff1a;将源点s从点集中去掉&#xff0c;加入S&#xff0c;d[s]0&#xff0c;∀v∈Q,d[v]w[s][v]\forall v\in Q ,d[v]w[s][v]∀v∈Q,d[v]w[s][v]将Q中d[v]最小的点去掉加入S,并对u∈…

Linux C 实现一个简单的线程池

https://www.cnblogs.com/GyForever1004/p/9185240.html 线程池的定义 线程池是一种多线程处理形式&#xff0c;处理过程中将任务添加到队列&#xff0c;然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小&#xff0c;以默认的优先级…

斐波那契数列求解+尾递归

1.普通递归 这里观察f[4]的递归树代替f[10]的递归树&#xff08;后者比较大&#xff0c;画不下&#xff09;。 使用递归求解的时候复杂度为T(n)T(n−1)T(n−2)T(n)T(n-1)T(n-2)T(n)T(n−1)T(n−2)&#xff0c;观察递归树&#xff0c;发现降速最快的是最右边每次减2&#xff0c…

循环服务器,并发服务器模型以及I/O多路转接模型

https://blog.csdn.net/xinianbuxiu/article/details/53455784 一、基于TCP/IP协议的基本循环服务器 tcp_server.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #incl…

c++继承父类的子类,如何调用父类的同名函数?

https://blog.csdn.net/qq_26399665/article/details/52080215 子类调用父类的同名函数&#xff1a; 子类和父类返回值参数相同&#xff0c;函数名相同&#xff0c;有virtual关键字&#xff0c;则由对象的类型决定调用哪个函数。 子类和父类只要函数名相同&#xff0c;没有vi…

LCS最长公共子串

问题介绍 LCS问题(longest common subsequence problem)指的是求解两个字符串最长公共子序列问题。这里的子序列是可以不连续的。LCS问题广泛地出现在计算生物学中&#xff08;DNA序列、系统生成树等等&#xff09;。这里介绍如何解决LCS问题&#xff0c;以及算法的正确性证明…

将字符串中的空格用%20替换

如果不需要原地操作&#xff0c;则一遍遍历&#xff0c;将非空串复制&#xff0c;遇到空格加上%20&#xff0c;如果需要原地操作&#xff0c;首先进行遍历出空格的个数x,然后扩容2x,从后往前遍历实现。如果非空格字符串比空格字符串多的多的时候而且字符串非常长的时候使用原地…

12步轻松搞定python装饰器

http://python.jobbole.com/81683/ 呵呵&#xff01;作为一名教python的老师&#xff0c;我发现学生们基本上一开始很难搞定python的装饰器&#xff0c;也许因为装饰器确实很难懂。搞定装饰器需要你了解一些函数式编程的概念&#xff0c;当然还有理解在python中定义和调用函数…

操作系统【六】虚拟内存

传统存储管理方式的不足 一次性&#xff1a;作业必须一次性全部装入内存后才能开始运行。这会造成&#xff1a;当作也很大时不能全部装入内存&#xff1b;当大量作业要求运行时&#xff0c;由于内存无法容纳所有作业&#xff0c;因此只有少量作业能够运行&#xff0c;导致多道…

python装饰器详解

https://blog.csdn.net/xiangxianghehe/article/details/77170585 你会Python嘛&#xff1f; 我会&#xff01; 那你给我讲下Python装饰器吧&#xff01; Python装饰器啊&#xff1f;我没用过哎 以上是我一个哥们面试时候发生的真实对白。 ———————————————-分…

SQL Server【一】简介和基本概念和命令

数据结构和数据库的区别 数据库是应用软件级别研究数据的存储和操作&#xff08;主要针对磁盘上的数据&#xff09; 数据结构是在系统软件级别研究数据的存储和操作&#xff08;主要是针对内存中的数据&#xff09; 对硬盘数操作是数据库的强项&#xff0c;是数据库研究的核心…

SQL Server【二】单表查询

查询 计算列 select * from emp; -- *通配符&#xff0c;表示所有的字段 -- from emp 从emp表查询select empno, ename from emp; select ename as "员工姓名", sal*12 as "年薪" from emp;-- as可以省略&#xff0c;用于设置字段名 -- 注意用双引号将字…

SQL Server【三】连接查询

将两个表或者两个以上的表以一定的连接条件连接起来&#xff0c;从中检索出满足条件的数据。 内连接 使用inner join&#xff0c;inner可以省略 -- 查询员工的姓名和部门名称 select "E".ename as "员工姓名", "D".dname as "部门名称&q…