走讀核心源碼,看看 epoll 的 LT 和 ET 模式差別。
詳細資訊可以參考文章《[epoll 源碼走讀] epoll 實作原理》,現在将部分代碼提取出來。
1. 原理
核心邏輯在
ep_send_events_proc
函數裡實作,關鍵在 就緒清單。
- epoll 監控的 fd 産生事件,fd 資訊被添加進就緒清單。
- epoll_wait 發現有就緒事件,程序持續執行,或者被喚醒工作。
- epoll 将 fd 資訊從就緒清單中删除。
- fd 對應就緒事件資訊從核心空間拷貝到使用者空間。
- 拷貝完成後,檢查事件模式是 LT 還是 ET,如果不是 ET,重新将 fd 資訊添加回就緒清單,下次重新觸發。
🔥 文章來源:《[epoll 源碼走讀] LT 與 ET 模式差別》
2. 源碼實作流程
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
int, maxevents, int, timeout) {
return do_epoll_wait(epfd, events, maxevents, timeout);
}
static int do_epoll_wait(int epfd, struct epoll_event __user *events,
int maxevents, int timeout) {
...
error = ep_poll(ep, events, maxevents, timeout);
...
}
// 檢查就緒隊列,如果就緒隊列有就緒事件,就将事件資訊從核心空間發送到使用者空間。
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, int maxevents, long timeout) {
...
// 檢查就緒隊列,如果有就緒事件就進入發送環節。
...
send_events:
// 有就緒事件就發送到使用者空間,否則繼續擷取資料直到逾時。
if (!res && eavail && !(res = ep_send_events(ep, events, maxevents)) &&
!timed_out)
goto fetch_events;
...
}
static int ep_send_events(struct eventpoll *ep,
struct epoll_event __user *events, int maxevents) {
struct ep_send_events_data esed;
esed.maxevents = maxevents;
esed.events = events;
// 周遊事件就緒清單,發送就緒事件到使用者空間。
ep_scan_ready_list(ep, ep_send_events_proc, &esed, 0, false);
return esed.res;
}
static __poll_t ep_scan_ready_list(struct eventpoll *ep,
__poll_t (*sproc)(struct eventpoll *,
struct list_head *, void *),
void *priv, int depth, bool ep_locked) {
...
// 将就緒隊列分片連結到 txlist 連結清單中。
list_splice_init(&ep->rdllist, &txlist);
// 執行 ep_send_events_proc
res = (*sproc)(ep, &txlist, priv);
...
}
static __poll_t ep_send_events_proc(struct eventpoll *ep, struct list_head *head, void *priv) {
...
// 周遊處理 txlist(原 ep->rdllist 資料)就緒隊列結點,擷取事件拷貝到使用者空間。
list_for_each_entry_safe (epi, tmp, head, rdllink) {
if (esed->res >= esed->maxevents)
break;
...
// 先從就緒隊列中删除 epi,如果是 LT 模式,就緒事件還沒處理完,再把它添加回去。
list_del_init(&epi->rdllink);
// 擷取 epi 對應 fd 的就緒事件。
revents = ep_item_poll(epi, &pt, 1);
if (!revents)
continue;
// 核心空間向使用者空間傳遞資料。__put_user 成功拷貝傳回 0。
if (__put_user(revents, &uevent->events) ||
__put_user(epi->event.data, &uevent->data)) {
// 如果拷貝失敗,繼續儲存在就緒清單裡。
list_add(&epi->rdllink, head);
ep_pm_stay_awake(epi);
if (!esed->res)
esed->res = -EFAULT;
return 0;
}
// 成功處理就緒事件的 fd 個數。
esed->res++;
uevent++;
if (epi->event.events & EPOLLONESHOT)
// #define EP_PRIVATE_BITS (EPOLLWAKEUP | EPOLLONESHOT | EPOLLET | EPOLLEXCLUSIVE)
epi->event.events &= EP_PRIVATE_BITS;
else if (!(epi->event.events & EPOLLET)) {
/* lt 模式下,目前事件被處理完後,不會從就緒清單中删除,留待下一次 epoll_wait
* 調用,再檢視是否還有事件沒處理,如果沒有事件了就從就緒清單中删除。
* 在周遊事件的過程中,不能寫 ep->rdllist,因為已經上鎖,隻能把新的就緒資訊
* 添加到 ep->ovflist */
list_add_tail(&epi->rdllink, &ep->rdllist);
ep_pm_stay_awake(epi);
}
}
return 0;
}
3. 參考
- [epoll 源碼走讀] epoll 實作原理
你的點贊👍,是對作者最好的支援,謝謝!😸