天天看點

徹底學會使用epoll(一)——ET模式實作分析 1. ET模式實作分析

注:之前寫過兩篇關于epoll實作的文章,但是感覺懂得了實作原理并不一定會使用,是以又決定寫這一系列文章,希望能夠對epoll有比較清楚的認識。是請大家轉載務必注明出處,算是對我勞動成果的一點點尊重吧。另外,文中如果有不全面或者不正确的地方還請大家指出。也可以私信或者發郵件:[email protected]

    首先給出下面一張圖,這張圖是從我之前的一篇博文——epoll實作分析中摘取并細化的。這張圖對了解ET模式已經epoll的工作過程隻管重要,當然我自己總結出來後也感覺有的小成就,在這裡與大家分享。

徹底學會使用epoll(一)——ET模式實作分析 1. ET模式實作分析

注:上圖的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 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可以有多次添加的機會。

下面我們來分析一下圖中兩種将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。

說明:紅線是時間驅動被動觸發,藍線是函數查詢主動觸發。

繼續閱讀