注:之前寫過兩篇關于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。
?
說明:紅線是時間驅動被動觸發,藍線是函數查詢主動觸發。
?
原文:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28541347&id=4273856