epoll核心實現分析
注:之前寫過兩篇關於epoll實現的文章,但是感覺懂得了實現原理並不一定會使用,所以又決定寫這一系列文章,希望能夠對epoll有比較清楚的認識。是請大家轉載務必註明出處,算是對我勞動成果的一點點尊重吧。另外,文中如果有不全面或者不正確的地方還請大家指出。也可以私信或者發郵件:lvyilong316@163.com
1. ET模式實現分析
1.1 ET和LT的實現區別
首先給出下面一張圖,這張圖是從我之前的一篇博文——epoll實現分析中摘取並細化的。這張圖對理解ET模式已經epoll的工作過程只管重要,當然我自己總結出來後也感覺有的小成就,在這裡與大家分享。
注:上圖的poll不要理解成和select相似那個poll,這是通過epoll_ctl呼叫的。
下面簡要分析一下epoll的工作過程:
(1) epoll_wait呼叫ep_poll,當rdlist為空(無就緒fd)時掛起當前程式,知道rdlist不空時程式才被喚醒。
(2) 檔案fd狀態改變(buffer由不可讀變為可讀或由不可寫變為可寫),導致相應fd上的回撥函式ep_poll_callback()被呼叫。
(3) ep_poll_callback將相應fd對應epitem加入rdlist,導致rdlist不空,程式被喚醒,epoll_wait得以繼續執行。
(4) ep_events_transfer函式將rdlist中的epitem拷貝到txlist中,並將rdlist清空。
(5) ep_send_events函式(很關鍵),它掃描txlist中的每個epitem,呼叫其關聯fd對用的poll方法(圖中藍線)。此時對poll的呼叫僅僅是取得fd上較新的events(防止之前events被更新),之後將取得的events和相應的fd傳送到使用者空間(封裝在struct epoll_event,從epoll_wait返回)。之後如果這個epitem對應的fd是LT模式監聽且取得的events是使用者所關心的,則將其重新加入回rdlist(圖中藍線),否則(ET模式)不在加入rdlist。
具體程式碼:
/* 掃描整個txlist連結串列... */
for (eventcnt = 0, uevent = esed->events;
!list_empty(head) && eventcnt < esed->maxevents;) {
/* 取出第一個成員 */
epi = list_first_entry(head, struct epitem, rdllink);
/* 然後從連結串列裡面移除 */
list_del_init(&epi->rdllink);
/* 讀取events,
* 注意events我們ep_poll_callback()裡面已經取過一次了, 為啥還要再取?
* 1. 我們當然希望能拿到此刻的最新資料, events是會變的~
* 2. 不是所有的poll實現, 都通過等待佇列傳遞了events, 有可能某些驅動壓根沒傳
* 必須主動去讀取. */
revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) &
epi->event.events;
if (revents) {
/* 將當前的事件和使用者傳入的資料都copy給使用者空間,
* 就是epoll_wait()後應用程式能讀到的那一堆資料. */
if (__put_user(revents, &uevent->events) ||
__put_user(epi->event.data, &uevent->data)) {
/* 如果copy過程中發生錯誤, 會中斷連結串列的掃描,
* 並把當前發生錯誤的epitem重新插入到ready list.
* 剩下的沒處理的epitem也不會丟棄, 在ep_scan_ready_list()
* 中它們也會被重新插入到ready list */
list_add(&epi->rdllink, head);
return eventcnt ? eventcnt : -EFAULT;
}
eventcnt++;
uevent++;
if (epi->event.events & EPOLLONESHOT)
epi->event.events &= EP_PRIVATE_BITS;
else if (!(epi->event.events & EPOLLET)) {
/*
* If this file has been added with Level
* Trigger mode, we need to insert back inside
* the ready list, so that the next call to
* epoll_wait() will check again the events
* availability. At this point, noone can insert
* into ep->rdllist besides us. The epoll_ctl()
* callers are locked out by
* ep_scan_ready_list() holding "mtx" and the
* poll callback will queue them in ep->ovflist.
*/
/* 嘿嘿, EPOLLET和非ET的區別就在這一步之差呀~
* 如果是ET, epitem是不會再進入到readly list,
* 除非fd再次發生了狀態改變, ep_poll_callback被呼叫.
* 如果是非ET, 不管你還有沒有有效的事件或者資料,
* 都會被重新插入到ready list, 再下一次epoll_wait
* 時, 會立即返回, 並通知給使用者空間. 當然如果這個
* 被監聽的fds確實沒事件也沒資料了, epoll_wait會返回一個0,
* 空轉一次.
*/
list_add_tail(&epi->rdllink, &ep->rdllist);
}
}
}
說明:
l epoll_wait返回的條件是rdlist不空,而使rdlist不空的途徑有兩個,分別對應圖中的紅線和藍線。
l ET和LT模式下的epitem都可以通過紅線方式加入rdlist從而喚醒epoll_wait,但LT模式下的epitem還可以通過藍線方式重新加入rdlist喚醒epoll_wait。所以ET模式下,fd就緒(通過紅線加入rdlist)只會被通知一次,而LT模式下只要滿足相應讀寫條件就返回就緒(通過藍線加入rdlist)。
l ET事件發生僅通知一次的原因是隻被新增到rdlist中一次,而LT可以有多次新增的機會。
1.2 兩種加入rdlist途徑的不同
下面我們來分析一下圖中兩種將epitem加入rdlist方式(也就是紅線和藍線)的區別。
l 紅線:fd狀態改變是才會觸發。那麼什麼情況會導致fd狀態的改變呢?
對於讀取操作:
(1) 當buffer由不可讀狀態變為可讀的時候,即由空變為不空的時候。
(2) 當有新資料到達時,即buffer中的待讀內容變多的時候。
對於寫操作:
(1) 當buffer由不可寫變為可寫的時候,即由滿狀態變為不滿狀態的時候。
(2) 當有舊資料被髮送走時,即buffer中待寫的內容變少得時候。
l 藍線:fd的events中有相應的時間(位置1)即會觸發。那麼什麼情況下會改變events的相應位呢?
對於讀操作:
(1) buffer中有資料可讀的時候,即buffer不空的時候fd的events的可讀為就置1。
對於寫操作:
(1) buffer中有空間可寫的時候,即buffer不滿的時候fd的events的可寫位就置1。
說明:紅線是時間驅動被動觸發,藍線是函式查詢主動觸發
EPOLLONESHOT區別:
如果epitem被設定為EPOLLONESHOT模式,則這個epitem上的事件拷貝到使用者空間之後,會將這個epitem上關注的事件清空(只是關注事件清空,並沒有從epoll中刪除,要刪除必須對那個描述符呼叫EPOLL_DEL),也就是說epitem上事件觸發時,但是因為使用者沒有關注 所以不會新增到 readylist 之中 !!!!
ET模式也可能多次觸發,ET定義是臨界條件觸發,如果傳送特別大的資料,緩衝區裝不下,讀取一部分,那麼ET模式會再次觸發。
相關文章
- 徹底學會使用epoll(一)——ET模式實現分析模式
- epoll實現快速ping
- Linux程式排程核心實現分析Linux
- epoll程式設計,單epoll+執行緒池?執行緒池+epoll?nginx實現高併發的原理?程式設計執行緒Nginx
- Linux核心分析--系統呼叫實現程式碼分析(轉)Linux
- 常見IO模型和epoll O(1)實現原理模型
- Windows核心分析——核心除錯機制的實現(NtCreateDebugObject、DbgkpPostFakeProcessCreateMessages、DbgkpPostFakeThreadMessages分析)Windows除錯GoObjectthread
- Promise核心實現Promise
- epoll+socket實現 socket併發 linux伺服器Linux伺服器
- linux系統下poll和epoll核心原始碼剖析Linux原始碼
- linux核心檔案IO的系統呼叫實現分析(open)Linux
- IO多路複用——深入淺出理解select、poll、epoll的實現
- Docker的核心實現原理Docker
- linux下epoll如何實現高效處理百萬控制程式碼的Linux
- 鴻蒙輕核心M核原始碼分析:LibC實現之Musl LibC鴻蒙原始碼
- 如何實現核心旁路(Kernel bypass)?
- epoll詳解
- epoll_event
- LevelDB 實現分析
- linux核心--使用核心佇列實現ringbufferLinux佇列
- iOS Jailbreak Principles - Undecimus 分析(三)通過 IOTrap 實現核心任意程式碼執行iOSAI
- DIY 實現 ThinkPHP 核心框架 (一)MVCPHP框架MVC
- DIY 實現 ThinkPHP 核心框架 (三)路由PHP框架路由
- 70行實現Promise核心原始碼Promise原始碼
- linux核心設計與實現Linux
- smbms專案核心功能實現
- Docker 核心技術與實現原理Docker
- 鴻蒙輕核心原始碼分析:虛實對映鴻蒙原始碼
- 《Linux核心設計與實現》學習【5】—— 核心同步Linux
- 【Linux核心設計與實現】Linux核心簡介Linux
- epoll–原始碼剖析原始碼
- 理解select、epoll
- Epoll模型詳解模型
- Epoll 模型簡介模型
- epoll使用與原理
- redis自學(18)epollRedis
- epoll的解釋
- DIY 實現 ThinkPHP 核心框架 (十四)利用反射實現依賴注入PHP框架反射依賴注入