????????根據前文高性能網絡設計推演中,epoll作為一個“大殺器”為網絡開發提供強大的支持。Linux系統上IO多路復用方案有select、poll、epoll。其中epoll的性能表現最優,且支持的并發量最大。本文大概介紹epoll的底層實現。
一、示例引入
了解epoll開發,最基本的暴露給應用層用戶開發使用的有三個方法:
-
epoll_create:創建一個 epoll 對象
-
epoll_ctl:向 epoll 對象中添加要管理的連接
-
epoll_wait:等待其管理的連接上的 IO 事件
一個簡單的使用流程樣例如下:
int main(){listen(lfd, ...);
?cfd1 = accept(...);cfd2 = accept(...);efd = epoll_create(...);
?epoll_ctl(efd, EPOLL_CTL_ADD, cfd1, ...);epoll_ctl(efd, EPOLL_CTL_ADD, cfd2, ...);epoll_wait(efd, ...)
}
二、關鍵數據結構eventpoll
????????對于應用層epoll三個接口方法不再贅述,主要介紹一下eventpoll。其中eventpoll標識了一個epoll,對于一個進程可以管理多個連接fd,然后由epoll實現事件監聽,并返回響應。都是圍繞eventpoll所完成。以下為eventpoll源碼:
/** This structure is stored inside the "private_data" member of the file* structure and represents the main data structure for the eventpoll* interface.*/
struct eventpoll {/** This mutex is used to ensure that files are not removed* while epoll is using them. This is held during the event* collection loop, the file cleanup path, the epoll file exit* code and the ctl operations.*/struct mutex mtx;
?/* Wait queue used by sys_epoll_wait() */wait_queue_head_t wq;
?/* Wait queue used by file->poll() */wait_queue_head_t poll_wait;
?/* List of ready file descriptors */struct list_head rdllist;
?/* Lock which protects rdllist and ovflist */rwlock_t lock;
?/* RB tree root used to store monitored fd structs */struct rb_root_cached rbr;
?/** This is a single linked list that chains all the "struct epitem" that* happened while transferring ready events to userspace w/out* holding ->lock.*/struct epitem *ovflist;
?/* wakeup_source used when ep_scan_ready_list is running */struct wakeup_source *ws;
?/* The user that created the eventpoll descriptor */struct user_struct *user;
?struct file *file;
?/* used to optimize loop detection check */int visited;struct list_head visited_list_link;
?
#ifdef CONFIG_NET_RX_BUSY_POLL/* used to track busy poll napi_id */unsigned int napi_id;
#endif
};
?
對于eventpoll需關注三個關鍵變量:
1、struct rb_root_cached rbr;
2、struct list_head rdllist;
3、wait_queue_head_t wq;
????????通過源碼中注釋可以了解其中作用:rbr為紅黑樹,rdllist為就緒隊列,wq為等待隊列。這是三種數據結構,對應到epoll中的功能而言,rbr為一顆紅黑樹,他維護了對epoll需要監聽的事件集合。rdllist維護了就緒事件集合,當有事件就緒后就會掛到rdllist上通知應用層程序處理。wq為維護的實現epoll線程的阻塞狀態。因為epoll_wait執行時,如果沒有事件就緒,此時會讓出cpu從而阻塞該線程,此時就會將該線程掛到wq上,后續就緒事件來臨時就會重新運行該線程。
????????對于rdllist和wq都選用隊列的數據結構比較好理解,因為此時的場景就是一個處理任務的場景,符合隊列“先進先出”的特性。所以選用隊列。
????????而對于紅黑樹的選擇原因是綜合了各方面的最優解。其中對于查詢效率,在整個epoll的event中有很多查詢場景,所以維護整體事件集合的數據結構需要較高的查詢效率,但是對應查詢效率更好的還有hashmap。這里就需要綜合事件集合的修改以及內存開銷這兩個問題。對于紅黑樹不需要連續的內存空間進行存儲,而hashmap需要連續的空間,并且對于應用層數量不定的事件內存的開辟大小就是一個問題。如果過小,后續添加就需要重新開辟連續空間并拷貝,如果過大就會導致內存空間的浪費。而且對于事件集合的修改而言紅黑樹也要更加方便,并且無hash沖突問題。綜上紅黑樹是最優解。
三、具體流程
????????對于epoll的實現介紹,本文從應用層角度和內核態角度分別簡單介紹,即“三+一“。應用層三個接口實現和內核層一個關鍵接口實現。(此處并非內核層只有一個接口,而是個人認為十分關鍵的一個實現,從而能夠支持整體epoll的功能實現)
????????應用層角度:三個接口:epoll_create、epoll_ctl、epoll_wait
????????1、epoll_create:該接口關鍵功能為初始化eventpoll數據結構,具體為上文提到的rbr、rdllist等。然后返回一個epoll_fd。應用層后續可以通過該句柄實現對epoll的操作。
????????2、epoll_ctl:根據操作的不同(增加、刪除、修改)對epitem節點進行操作。epitem節點標識了一個監聽的句柄的某個事件。其中EPOLL_CTL_ADD為添加,最重要的是建立socket句柄與epoll的關聯,即注冊ep_poll_callback回調函數到sock->sk_wq。并將eptime掛到socket->wq->wait_queue_head上。后續當socket句柄可讀或者可寫時,會執行該回調函數以通知eventpoll,將該epitem掛到rdllist隊列,如果此時epoll線程是阻塞的話也會將該線程從wq中喚醒。
????????3、epoll_wait:該操作會遍歷rdllist就緒隊列,如果就緒隊列有就緒事件,則正常返回由應用層進行處理。否則會阻塞當前線程并將線程掛到eventpoll->wq上,然后讓出CPU使用進入阻塞態。
????????4、當數據來臨時,數據由網卡->網卡驅動->Linux內核協議棧(ip、tcp/udp等)調用sock_def_wakup喚醒該sock的wait_queue上的eptime,然后回調ep_poll_callback。由回調函數將epitme掛到eventpoll->rdllist,如果之前epoll_wait阻塞了當前線程,則一并喚醒當前線程。
????????5、ep_send_events實現從eventpoll->rdllist中取出就緒事件,并返回應用層event數組。后續由應用層處理就緒事件。
更多內容可參考:0voice · GitHub?