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
,則程序的運行將不會出現死循環的情況。
參考資料
- http://blog.lucode.net/linux/epoll-tutorial.html
- http://blog.chinaunix.net/uid-28541347-id-4285054.html
- http://blog.chinaunix.net/uid-28541347-id-4288802.html
- https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/