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?
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

實驗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.pswp.cn/news/383655.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383655.shtml
英文地址,請注明出處:http://en.pswp.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…