導航:首頁 > 數據處理 > epoll底層用了什麼數據結構

epoll底層用了什麼數據結構

發布時間:2023-02-01 18:29:04

⑴ 面試必問的epoll技術,從內核源碼出發徹底搞懂epoll

epoll是linux中IO多路復用的一種機制,I/O多路復用就是通過一種機制,一個進程可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。當然linux中IO多路復用不僅僅是epoll,其他多路復用機制還有select、poll,但是接下來介紹epoll的內核實現。

events可以是以下幾個宏的集合:

epoll相比select/poll的優勢

epoll相關的內核代碼在fs/eventpoll.c文件中,下面分別分析epoll_create、epoll_ctl和epoll_wait三個函數在內核中的實現,分析所用linux內核源碼為4.1.2版本。

epoll_create用於創建一個epoll的句柄,其在內核的系統實現如下:

sys_epoll_create:

可見,我們在調用epoll_create時,傳入的size參數,僅僅是用來判斷是否小於等於0,之後再也沒有其他用處。
整個函數就3行代碼,真正的工作還是放在sys_epoll_create1函數中。

sys_epoll_create -> sys_epoll_create1:

sys_epoll_create1 函數流程如下:

sys_epoll_create -> sys_epoll_create1 -> ep_alloc:


sys_epoll_create -> sys_epoll_create1 -> ep_alloc -> get_unused_fd_flags:

linux內核中,current是個宏,返回的是一個task_struct結構(我們稱之為進程描述符)的變數,表示的是當前進程,進程打開的文件資源保存在進程描述符的files成員裡面,所以current->files返回的當前進程打開的文件資源。rlimit(RLIMIT_NOFILE) 函數獲取的是當前進程可以打開的最大文件描述符數,這個值可以設置,默認是1024。

相關視頻推薦:

支撐億級io的底層基石 epoll實戰揭秘

網路原理tcp/udp,網路編程epoll/reactor,面試中正經「八股文」

學習地址:C/C++Linux伺服器開發/後台架構師【零聲教育】-學習視頻教程-騰訊課堂

需要更多C/C++ Linux伺服器架構師學習資料加群 812855908 獲取(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享

__alloc_fd的工作是為進程在[start,end)之間(備註:這里start為0, end為進程可以打開的最大文件描述符數)分配一個可用的文件描述符,這里就不繼續深入下去了,代碼如下:

sys_epoll_create -> sys_epoll_create1 -> ep_alloc -> get_unused_fd_flags -> __alloc_fd:

然後,epoll_create1會調用anon_inode_getfile,創建一個file結構,如下:

sys_epoll_create -> sys_epoll_create1 -> anon_inode_getfile:

anon_inode_getfile函數中首先會alloc一個file結構和一個dentry結構,然後將該file結構與一個匿名inode節點anon_inode_inode掛鉤在一起,這里要注意的是,在調用anon_inode_getfile函數申請file結構時,傳入了前面申請的eventpoll結構的ep變數,申請的file->private_data會指向這個ep變數,同時,在anon_inode_getfile函數返回來後,ep->file會指向該函數申請的file結構變數。

簡要說一下file/dentry/inode,當進程打開一個文件時,內核就會為該進程分配一個file結構,表示打開的文件在進程的上下文,然後應用程序會通過一個int類型的文件描述符來訪問這個結構,實際上內核的進程裡面維護一個file結構的數組,而文件描述符就是相應的file結構在數組中的下標。

dentry結構(稱之為「目錄項」)記錄著文件的各種屬性,比如文件名、訪問許可權等,每個文件都只有一個dentry結構,然後一個進程可以多次打開一個文件,多個進程也可以打開同一個文件,這些情況,內核都會申請多個file結構,建立多個文件上下文。但是,對同一個文件來說,無論打開多少次,內核只會為該文件分配一個dentry。所以,file結構與dentry結構的關系是多對一的。

同時,每個文件除了有一個dentry目錄項結構外,還有一個索引節點inode結構,裡面記錄文件在存儲介質上的位置和分布等信息,每個文件在內核中只分配一個inode。 dentry與inode描述的目標是不同的,一個文件可能會有好幾個文件名(比如鏈接文件),通過不同文件名訪問同一個文件的許可權也可能不同。dentry文件所代表的是邏輯意義上的文件,記錄的是其邏輯上的屬性,而inode結構所代表的是其物理意義上的文件,記錄的是其物理上的屬性。dentry與inode結構的關系是多對一的關系。

sys_epoll_create -> sys_epoll_create1 -> fd_install:

總結epoll_create函數所做的事:調用epoll_create後,在內核中分配一個eventpoll結構和代表epoll文件的file結構,並且將這兩個結構關聯在一塊,同時,返回一個也與file結構相關聯的epoll文件描述符fd。當應用程序操作epoll時,需要傳入一個epoll文件描述符fd,內核根據這個fd,找到epoll的file結構,然後通過file,獲取之前epoll_create申請eventpoll結構變數,epoll相關的重要信息都存儲在這個結構裡面。接下來,所有epoll介面函數的操作,都是在eventpoll結構變數上進行的。

所以,epoll_create的作用就是為進程在內核中建立一個從epoll文件描述符到eventpoll結構變數的通道。

epoll_ctl介面的作用是添加/修改/刪除文件的監聽事件,內核代碼如下:

sys_epoll_ctl:

根據前面對epoll_ctl介面的介紹,op是對epoll操作的動作(添加/修改/刪除事件),ep_op_has_event(op)判斷是否不是刪除操作,如果op != EPOLL_CTL_DEL為true,則需要調用_from_user函數將用戶空間傳過來的event事件拷貝到內核的epds變數中。因為,只有刪除操作,內核不需要使用進程傳入的event事件。

接著連續調用兩次fdget分別獲取epoll文件和被監聽文件(以下稱為目標文件)的file結構變數(備註:該函數返回fd結構變數,fd結構包含file結構)。

接下來就是對參數的一些檢查,出現如下情況,就可以認為傳入的參數有問題,直接返回出錯:

當然下面還有一些關於操作動作如果是添加操作的判斷,這里不做解釋,比較簡單,自行閱讀。

在ep裡面,維護著一個紅黑樹,每次添加註冊事件時,都會申請一個epitem結構的變數表示事件的監聽項,然後插入ep的紅黑樹裡面。在epoll_ctl裡面,會調用ep_find函數從ep的紅黑樹裡面查找目標文件表示的監聽項,返回的監聽項可能為空。

接下來switch這塊區域的代碼就是整個epoll_ctl函數的核心,對op進行switch出來的有添加(EPOLL_CTL_ADD)、刪除(EPOLL_CTL_DEL)和修改(EPOLL_CTL_MOD)三種情況,這里我以添加為例講解,其他兩種情況類似,知道了如何添加監聽事件,其他刪除和修改監聽事件都可以舉一反三。

為目標文件添加監控事件時,首先要保證當前ep裡面還沒有對該目標文件進行監聽,如果存在(epi不為空),就返回-EEXIST錯誤。否則說明參數正常,然後先默認設置對目標文件的POLLERR和POLLHUP監聽事件,然後調用ep_insert函數,將對目標文件的監聽事件插入到ep維護的紅黑樹裡面:

sys_epoll_ctl -> ep_insert:

前面說過,對目標文件的監聽是由一個epitem結構的監聽項變數維護的,所以在ep_insert函數裡面,首先調用kmem_cache_alloc函數,從slab分配器裡面分配一個epitem結構監聽項,然後對該結構進行初始化,這里也沒有什麼好說的。我們接下來看ep_item_poll這個函數調用:

sys_epoll_ctl -> ep_insert -> ep_item_poll:

ep_item_poll函數裡面,調用目標文件的poll函數,這個函數針對不同的目標文件而指向不同的函數,如果目標文件為套接字的話,這個poll就指向sock_poll,而如果目標文件為tcp套接字來說,這個poll就是tcp_poll函數。雖然poll指向的函數可能會不同,但是其作用都是一樣的,就是獲取目標文件當前產生的事件位,並且將監聽項綁定到目標文件的poll鉤子裡面(最重要的是注冊ep_ptable_queue_proc這個poll callback回調函數),這步操作完成後,以後目標文件產生事件就會調用ep_ptable_queue_proc回調函數。

接下來,調用list_add_tail_rcu將當前監聽項添加到目標文件的f_ep_links鏈表裡面,該鏈表是目標文件的epoll鉤子鏈表,所有對該目標文件進行監聽的監聽項都會加入到該鏈表裡面。

然後就是調用ep_rbtree_insert,將epi監聽項添加到ep維護的紅黑樹裡面,這里不做解釋,代碼如下:

sys_epoll_ctl -> ep_insert -> ep_rbtree_insert:

前面提到,ep_insert有調用ep_item_poll去獲取目標文件產生的事件位,在調用epoll_ctl前這段時間,可能會產生相關進程需要監聽的事件,如果有監聽的事件產生,(revents & event->events 為 true),並且目標文件相關的監聽項沒有鏈接到ep的准備鏈表rdlist裡面的話,就將該監聽項添加到ep的rdlist准備鏈表裡面,rdlist鏈接的是該epoll描述符監聽的所有已經就緒的目標文件的監聽項。並且,如果有任務在等待產生事件時,就調用wake_up_locked函數喚醒所有正在等待的任務,處理相應的事件。當進程調用epoll_wait時,該進程就出現在ep的wq等待隊列裡面。接下來講解epoll_wait函數。

總結epoll_ctl函數:該函數根據監聽的事件,為目標文件申請一個監聽項,並將該監聽項掛人到eventpoll結構的紅黑樹裡面。

epoll_wait等待事件的產生,內核代碼如下:

sys_epoll_wait:

首先是對進程傳進來的一些參數的檢查:

參數全部檢查合格後,接下來就調用ep_poll函數進行真正的處理:

sys_epoll_wait -> ep_poll:

ep_poll中首先是對等待時間的處理,timeout超時時間以ms為單位,timeout大於0,說明等待timeout時間後超時,如果timeout等於0,函數不阻塞,直接返回,小於0的情況,是永久阻塞,直到有事件產生才返回。

當沒有事件產生時((!ep_events_available(ep))為true),調用__add_wait_queue_exclusive函數將當前進程加入到ep->wq等待隊列裡面,然後在一個無限for循環裡面,首先調用set_current_state(TASK_INTERRUPTIBLE),將當前進程設置為可中斷的睡眠狀態,然後當前進程就讓出cpu,進入睡眠,直到有其他進程調用wake_up或者有中斷信號進來喚醒本進程,它才會去執行接下來的代碼。

如果進程被喚醒後,首先檢查是否有事件產生,或者是否出現超時還是被其他信號喚醒的。如果出現這些情況,就跳出循環,將當前進程從ep->wp的等待隊列裡面移除,並且將當前進程設置為TASK_RUNNING就緒狀態。

如果真的有事件產生,就調用ep_send_events函數,將events事件轉移到用戶空間裡面。

sys_epoll_wait -> ep_poll -> ep_send_events:

ep_send_events沒有什麼工作,真正的工作是在ep_scan_ready_list函數裡面:

sys_epoll_wait -> ep_poll -> ep_send_events -> ep_scan_ready_list:

ep_scan_ready_list首先將ep就緒鏈表裡面的數據鏈接到一個全局的txlist裡面,然後清空ep的就緒鏈表,同時還將ep的ovflist鏈表設置為NULL,ovflist是用單鏈表,是一個接受就緒事件的備份鏈表,當內核進程將事件從內核拷貝到用戶空間時,這段時間目標文件可能會產生新的事件,這個時候,就需要將新的時間鏈入到ovlist裡面。

僅接著,調用sproc回調函數(這里將調用ep_send_events_proc函數)將事件數據從內核拷貝到用戶空間。

sys_epoll_wait -> ep_poll -> ep_send_events -> ep_scan_ready_list -> ep_send_events_proc:

ep_send_events_proc回調函數循環獲取監聽項的事件數據,對每個監聽項,調用ep_item_poll獲取監聽到的目標文件的事件,如果獲取到事件,就調用__put_user函數將數據拷貝到用戶空間。

回到ep_scan_ready_list函數,上面說到,在sproc回調函數執行期間,目標文件可能會產生新的事件鏈入ovlist鏈表裡面,所以,在回調結束後,需要重新將ovlist鏈表裡面的事件添加到rdllist就緒事件鏈表裡面。

同時在最後,如果rdlist不為空(表示是否有就緒事件),並且由進程等待該事件,就調用wake_up_locked再一次喚醒內核進程處理事件的到達(流程跟前面一樣,也就是將事件拷貝到用戶空間)。

到這,epoll_wait的流程是結束了,但是有一個問題,就是前面提到的進程調用epoll_wait後會睡眠,但是這個進程什麼時候被喚醒呢?在調用epoll_ctl為目標文件注冊監聽項時,對目標文件的監聽項注冊一個ep_ptable_queue_proc回調函數,ep_ptable_queue_proc回調函數將進程添加到目標文件的wakeup鏈表裡面,並且注冊ep_poll_callbak回調,當目標文件產生事件時,ep_poll_callbak回調就去喚醒等待隊列裡面的進程。

總結一下epoll該函數: epoll_wait函數會使調用它的進程進入睡眠(timeout為0時除外),如果有監聽的事件產生,該進程就被喚醒,同時將事件從內核裡面拷貝到用戶空間返回給該進程。

⑵ linux手冊翻譯——epoll(7)


epoll — I/O 事件通知機制


epoll API與poll具有相同功能:監視多個文件描述符,以查看這些文件描述符中任何一個上可以進行特定的I/O操作,如是否可讀/可寫。epoll API可以使用edge-triggered和level-triggered兩種介面,並且可以高性能的同時監視大量的fd,這是對epoll相對魚poll的核心優勢。

epoll的核心概念是epoll instance,這是一種內核數據結構,從用戶空間角度看,可以視為一個包含兩種列表的容器:

提供以下3個系統調用來創建和管理epoll instance:

兩種觸發模式:level_triggered (LT)和 edge_triggered(ET)
假設發生如下場景:

如果使用ET觸發,那麼步驟5就會阻塞掛起,這是因為對於ET模式而言,只有當緩沖區數據發生變化時才會觸發事件(對於讀,「變化」指新數據到達)。而對於LT而言,只要緩沖區中存在數據,就會一直觸發。

使用ET時應使用非阻塞的fd (即無法讀寫時返回EAGIN,而非阻塞),以避免task阻塞導教其他fd無法監控。
合理使用ET模式步驟:
1)修改fd為非阻塞(non-blocking)
2)在read或write操作返回EAGIN後再執行wait等待事件。
為何ET需要非阻塞呢?因為ET模式下要循環多次read,並通過阻塞(即是否返回EAGIN)來確定數據是否全部讀完。第一次執行read是不可能阻塞的。

若使用LT模式(默認情況下,使用ET模式),則可以將epoll看作是一個快速的poll,可以在任何地方使用epoll(LT)替換poll,因為他們的語義完全相同。
即使採用ET模式,在多線程的情況依然會導致產生多個事件(對於同一被監控的fd),這將導致多個線程操作同一fd,可以使用EPOLLNESHOT標志避免,即在一次wait返回後禁止fd再產生事件,並在處理完成後使用epoll_ctl的MOD操作重新開啟。
在多進程或多線程中,epoll_fd是共享的,這將導致所有線程都會知道事情的發生,但是epoll僅會喚醒一個線程,以規避「群驚」現象。

If the system is in autosleep mode via /sys/power/autosleep and an event happens which wakes the device from sleep, the device driver will keep the device awake only until that event is queued. To keep the device awake until the event has been processed, it is necessary to use the epoll_ctl(2) EPOLLWAKEUP flag.

When the EPOLLWAKEUP flag is set in the events field for a struct epoll_event, the system will be kept awake from the moment the event is queued, through the epoll_wait(2) call which returns the event until the subsequent epoll_wait(2) call. If the event should keep the system awake beyond that time, then a separate wake_lock should be taken before the second epoll_wait(2) call.

以下介面可用於限制 epoll 消耗的內核內存用量:

雖然 epoll 在用作級別觸發介面時具有與 poll(2) 相同的語義,但邊緣觸發的用法需要更多說明以避免應用程序事件循環中的阻塞。
在下面例子中,listener 是一個非阻塞套接字,在它上面調用了 listen(2)。 函數 do_use_fd() 使用新的就緒文件描述符,直到 read(2) 或 write(2) 返回 EAGAIN。 事件驅動的狀態機應用程序應該在收到 EAGAIN 後記錄其當前狀態,以便在下一次調用 do_use_fd() 時,它將繼續從之前停止的位置read (2) 或write (2)。

當使用ET模式時,出於性能原因,可以通過EPOLL_CTL_ADD調用 epoll_ctl(2)指定 (EPOLLIN|EPOLLOUT)添加一次文件描述符。 避免使用 EPOLL_CTL_MOD 調用 epoll_ctl(2)在 EPOLLIN 和 EPOLLOUT 之間連續切換。


The epoll API is Linux-specific. Some other systems provide similar mechanisms, for example, FreeBSD has kqueue, and Solaris has /dev/poll.


通過 epoll 文件描述符監視的文件描述符集可以通過進程的 /proc/[pid]/fdinfo 目錄中的 epoll 文件描述符條目查看。 有關更多詳細信息,請參閱 proc(5)。

kcmp(2) KCMP_EPOLL_TFD 操作可用於測試文件描述符是否存在於 epoll 實例中。

⑶ 關於Linux下的select/epoll

select這個系統調用的原型如下

第一個參數nfds用來告訴內核 要掃描的socket fd的數量+1 ,select系統調用最大接收的數量是1024,但是如果每次都去掃描1024,實際上的數量並不多,則效率太低,這里可以指定需要掃描的數量。 最大數量為1024,如果需要修改這個數量,則需要重新編譯Linux內核源碼。
第2、3、4個參數分別是readfds、writefds、exceptfds,傳遞的參數應該是fd_set 類型的引用,內核會檢測每個socket的fd, 如果沒有讀事件,就將對應的fd從第二個參數傳入的fd_set中移除,如果沒有寫事件,就將對應的fd從第二個參數的fd_set中移除,如果沒有異常事件,就將對應的fd從第三個參數的fd_set中移除 。這里我們應該 要將實際的readfds、writefds、exceptfds拷貝一份副本傳進去,而不是傳入原引用,因為如果傳遞的是原引用,某些socket可能就已經丟失
最後一個參數是等待時間, 傳入0表示非阻塞,傳入>0表示等待一定時間,傳入NULL表示阻塞,直到等到某個socket就緒

FD_ZERO()這個函數將fd_set中的所有bit清0,一般用來進行初始化等。
FD_CLR()這個函數用來將bitmap(fd_set )中的某個bit清0,在客戶端異常退出時就會用到這個函數,將fd從fd_set中刪除。
FD_ISSET()用來判斷某個bit是否被置1了,也就是判斷某個fd是否在fd_set中。
FD_SET()這個函數用來將某個fd加入fd_set中,當客戶端新加入連接時就會使用到這個函數。

epoll_create系統調用用來創建epfd,會在開辟一塊內存空間(epoll的結構空間)。size為epoll上能關注的最大描述符數,不夠會進行擴展,size只要>0就行,早期的設計size是固定大小,但是現在size參數沒什麼用,會自動擴展。
返回值是epfd,如果為-1則說明創建epoll對象失敗

第一個參數epfd傳入的就是epoll_create返回的epfd。
第二個參數傳入對應操作的宏,包括 增刪改(EPOLL_CTL_ADD、EPOLL_CTL_DEL、EPOLL_CTL_MOD)
第三個參數傳入的是 需要增刪改的socket的fd
第四個參數傳入的是 需要操作的fd的哪些事件 ,具體的事件可以看後續。
返回值是一個int類型,如果為-1則說明操作失敗

第一個參數是epfd,也就是epoll_create的返回值。
第二個參數是一個epoll_event類型的指針,也就是傳入的是一個數組指針。 內核會將就緒的socket的事件拷貝到這個數組中,用戶可以根據這個數組拿到事件和消息等
第三個參數是maxevents,傳入的是 第二個參數的數組的容量
第四個參數是timeout, 如果設為-1一直阻塞直到有就緒數據為止,如果設為0立即返回,如果>0那麼阻塞一段時間
返回值是一個int類型,也就是就緒的socket的事件的數量(內核拷貝給用戶的events的元素的數量),通過這個數量可以進行遍歷處理每個事件

一般需要傳入 ev.data.fd 和 ev.events ,也就是fd和需要監控的fd的事件。事件如果需要傳入多個,可以通過按位與來連接,比如需要監控讀寫事件,只需要像如下這樣操作即可: ev.events=EPOLLIN | EPOLLOUT 。

LT(水平觸發), 默認 的工作模式, 事件就緒後用戶可以選擇處理和不處理,如果用戶不處理,內核會對這部分數據進行維護,那麼下次調用epoll_wait()時仍舊會打包出來
ET(邊緣觸發),事件就緒之後, 用戶必須進行處理 ,因為內核把事件打包出來之後就把對應的就緒事件給清掉了, 如果不處理那麼就緒事件就沒了 。ET可以減少epoll事件被重復觸發的次數,效率比LT高。
如果需要設置為邊緣觸發只需要設置事件為類似 ev.events=EPOLLIN | EPOLLET 即可

select/poll/epoll是nio多路復用技術, 傳統的bio無法實現C10K/C100K ,也就是無法滿足1w/10w的並發量,在這么高的並發量下,在進行上下文切換就很容易將伺服器的負載拉飛。

1.將fd_set從用戶態拷貝到內核態
2.根據fd_set掃描內存中的socket的fd的狀態,時間復雜度為O(n)
3.檢查fd_set,如果有已經就緒的socket,就給對應的socket的fd打標記,那麼就return 就緒socket的數量並喚醒當前線程,如果沒有就緒的socket就繼續阻塞當前線程直到有socket就緒才將當前線程喚醒。
4.如果想要獲取當前已經就緒的socket列表,則還需要進行一次系統調用,使用O(n)的時間去掃描socket的fd列表,將已經打上標記的socket的fd返回。

CPU在同一個時刻只能執行一個程序,通過RR時間片輪轉去切換執行各個程序。沒有被掛起的進程(線程)則在工作隊列中排隊等待CPU的執行,將進程(線程)從工作隊列中移除就是掛起,反映到Java層面的就是線程的阻塞。

什麼是中斷?當我們使用鍵盤、滑鼠等IO設備的時候,會給主板一個電流信號,這個電流信號就給CPU一個中斷信號,CPU執行完當前的指令便會保存現場,然後執行鍵盤/滑鼠等設備的中斷程序,讓中斷程序獲取CPU的使用權,在中斷程序後又將現場恢復,繼續執行之前的進程。

如果第一次沒檢測到就緒的socket,就要將其進程(線程)從工作隊列中移除,並加入到socket的等待隊列中。

socket包含讀緩沖區+寫緩沖區+等待隊列(放線程或eventpoll對象)

當從客戶端往伺服器端發送數據時,使用TCP/IP協議將通過物理鏈路、網線發給伺服器的網卡設備,網卡的DMA設備將接收到的的數據寫入到內存中的一塊區域(網卡緩沖區),然後會給CPU發出一個中斷信號,CPU執行完當前指令則會保存現場,然後網卡的中斷程序就獲得了CPU的使用權,然後CPU便開始執行網卡的中斷程序,將內存中的緩存區中的數據包拿出,判斷埠號便可以判斷它是哪個socket的數據,將數據包寫入對應的socket的讀(輸入)緩沖區,去檢查對應的socket的等待隊列有沒有等待著的進程(線程),如果有就將該線程(進程)從socket的等待隊列中移除,將其加入工作隊列,這時候該進程(線程)就再次擁有了CPU的使用許可權,到這里中斷程序就結束了。

之後這個進程(線程)就執行select函數再次去檢查fd_set就能發現有socket緩沖區中有數據了,就將該socket的fd打標記,這個時候select函數就執行完了,這時候就會給上層返回一個int類型的數值,表示已經就緒的socket的數量或者是發生了錯誤。這個時候就再進行內核態到用戶態的切換,對已經打標記的socket的fd進行處理。

將原本1024bit長度的bitmap(fd_set)換成了數組的方式傳入 ,可以 解決原本1024個不夠用的情況 ,因為傳入的是數組,長度可以不止是1024了,因此socket數量可以更多,在Kernel底層會將數組轉換成鏈表。

在十多年前,linux2.6之前,不支持epoll,當時可能會選擇用Windows/Unix用作伺服器,而不會去選擇Linux,因為select/poll會隨著並發量的上升,性能變得越來越低,每次都得檢查所有的Socket列表。

1.select/poll每次調用都必須根據提供所有的socket集合,然後就 會涉及到將這個集合從用戶空間拷貝到內核空間,在這個過程中很耗費性能 。但是 其實每次的socket集合的變化也許並不大,也許就1-2個socket ,但是它會全部進行拷貝,全部進行遍歷一一判斷是否就緒。

2.select/poll的返回類型是int,只能代表當前的就緒的socket的數量/發生了錯誤, 如果還需要知道是哪些socket就緒了,則還需要再次使用系統調用去檢查哪些socket是就緒的,又是一次O(n)的操作,很耗費性能

1.epoll在Kernel內核中存儲了對應的數據結構(eventpoll)。我們可以 使用epoll_create()這個系統調用去創建一個eventpoll對象 ,並返回eventpoll的對象id(epfd),eventpoll對象主要包括三個部分:需要處理的正在監聽的socket_fd列表(紅黑樹結構)、socket就緒列表以及等待隊列(線程)。

2.我們可以使用epoll_ctl()這個系統調用對socket_fd列表進行CRUD操作,因為可能頻繁地進行CRUD,因此 socket_fd使用的是紅黑樹的結構 ,讓其效率能更高。epoll_ctl()傳遞的參數主要是epfd(eventpoll對象id)。

3.epoll_wait()這個系統調用默認會 將當前進程(線程)阻塞,加入到eventpoll對象的等待隊列中,直到socket就緒列表中有socket,才會將該進程(線程)重新加入工作隊列 ,並返回就緒隊列中的socket的數量。

socket包含讀緩沖區、寫緩沖區和等待隊列。當使用epoll_ctl()系統調用將socket新加入socket_fd列表時,就會將eventpoll對象引用加到socket的等待隊列中, 當網卡的中斷程序發現socket的等待隊列中不是一個進程(線程),而是一個eventpoll對象的引用,就將socket引用追加到eventpoll對象的就緒列表的尾部 。而eventpoll對象中的等待隊列存放的就是調用了epoll_wait()的進程(線程),網卡的中斷程序執行會將等待隊列中的進程(線程)重新加入工作隊列,讓其擁有佔用CPU執行的資格。epoll_wait()的返回值是int類型,返回的是就緒的socket的數量/發生錯誤,-1表示發生錯誤。

epoll的參數有傳入一個epoll_event的數組指針(作為輸出參數),在調用epoll_wait()返回的同時,Kernel內核還會將就緒的socket列表添加到epoll_event類型的數組當中。

⑷ epoll知識點總結

epoll是linux IO多路復用的管理機制,現在是linux平台高性能網路io必要的組件。

理解內核epoll的運行原理,需要從四方面來理解:

1.epoll的數據結構。2.epoll的線程安全。

3.epoll的內核回調。4.epoll的LT與ET。

主要兩個結構體 eventpoll 與 epitem。

eventpoll是每一個epoll所對應的,epitem是每一個IO所對應的事件。

數據結構圖下圖所示

list用來存儲准備就緒的IO,內核IO准備就緒的時候,會執行epoll_event_callback的回調函數,將epitem添加到list中;當epoll_wait激活重新運行的時候,將list的epitem逐一到events參數中。

rbtree用來存儲所有的io數據,方便快速通過io_fd查找;epoll_ctl執行EPOLL_CTL_ADD操作時,將epitem添加到rbtree中;epoll_ctl執行EPOLL_CTL_DEL操作時,將epitem從retree中刪除。

     以下幾個包括list操作,rbtree操作,epoll_wait的等待需要加鎖。

    list使用最小粒度的spinlock鎖,避免多核競爭。

    rbtree的添加使用互斥鎖,

    epoll_wait採用pthread_cond_wait;

1.tcp三次握手,對端反饋ack,socket進入rcvd狀態,需要將監聽的socket的event置為EPOLLIN,此時標識可以進入到accept讀取socket數據。

2.established狀態時,收到數據,將socket的event置為EPOLLIN狀態。

3.established狀態時 收到fin,socket進入close_Wait,需要將socket的event設置為EPOLLIN,讀取斷開信息

4 .   檢測到socket的send狀態,cwnd >0可以發送的數據,需要將socket置為EPOLLOUT。

LT(水平觸發):socket接收緩沖區不為空 有數據可讀,讀事件一直觸發;socket發送緩沖區不滿,可以繼續寫入數據,寫事件一直觸發。

ET(邊緣觸發):socket接收緩沖區變化時觸發讀事件,空的接收緩沖區剛接收到數據時觸發讀事件;socket發送緩沖區狀態發生變化時觸發寫事件,即滿的緩沖區剛空出空間時觸發讀事件。

LT的處理過程:

    accept一個連接,添加到epoll中監聽EPOLLIN事件。

    當EPOLLIN事件到達時,read fd中的數據並處理,

    當需要寫出數據時,把數據write到fd中;如果數據較大,無法一次性寫出,那麼在epoll中監聽EPOLLOUT事件。

    當EPOLLOUT事件到達時,繼續把數據write到fd中 ;如果數據寫出完畢,那麼在epoll中關閉EPOLLOUT事件。

ET的處理過程:

    accept一個連接,添加到epoll中監聽EPOLLIN|EPOLLOUT事件

    當EPOLLIN事件到達時,read fd中數據並處理,read需要一直讀,直到返回EAGAIN為止

    當需要寫出數據時,把數據write到fd中,直到數據全部寫完或者write返回EAGAIN

    當EPOLLOUT事件到達時,繼續把數據write到fd中,直到數據全部寫完,或者write返回EAGAIN

accept要考慮兩個問題:

阻塞模式accept存在的問題:TCP連接被客戶端夭折,即伺服器調用accept之前,客戶端主動發送RST終止連接,導致剛剛建立的連接從就緒隊列中移出,如果套介面被設置成阻塞模式,伺服器就一直阻塞到accept調用上,直到其他某個客戶建立一個新的連接為止。在此期間,伺服器 單純阻塞在accept調用上,就緒隊列上其他描述符都得不到處理。解決辦法是把監聽的套介面設置成非阻塞的,客戶端在在伺服器端調用accept之前中止某個連接時,accept調用可以立即返回-1。

ET模式accept存在的問題:

    多個連接同時到達,,伺服器TCP就行連接瞬間積累多個就緒連接,由於是邊緣觸發模式,epoll只會通知一次,accept只處理一個連接,導致TCP就緒隊列中剩下的連接都得不到處理,解決辦法是,while循環 中accpet調用,處理完accept就緒隊列中所有連接後再退出循環。如何知道是否處理完所有連接,accept返回-1並且error設置為errno設置為EAGAIN便是所有連接都處理完。

LT 只要event為EPOLLIN時就能不斷調用回調函數

ET 如果從EPOLLOUT變化為EPOLLIN時候,就會觸發。    

⑸ 定時器實現 & 紅黑樹,跳錶

跳錶:是為一個有序的鏈表建立多級索引的數據結構叫做跳錶。redis中zset數據量大時底層數據結構使用跳錶。

redis中定時器使用的是無序的雙向鏈表。時間復雜度為O(N),redis作者在定時器備注了可以適當優化的措施:

1 盡可能讓數據有序,2 可以使用跳錶完成

回歸正題,跳錶:

其他地方借圖一張如下:

跳錶是基於有序鏈表所實現的,為了實現快速的查找,在做節點比較的時候跳過一些節點以達到快速查找的目的,是一種空間換時間的思想。

如上圖,此跳錶總共三層(level = 3),redis中zset在數據量比較大時,採用的即是跳錶實現 其最大層數是32,再插入節點的時候隨機生成層數,最大不超過32;

如上圖為例,頭節點不保存數據,按照上圖分析插入操作:

插入之前要先從上層到下層臨時保存每一層的當前節點,然後隨即生成新的newlevel,如果newlevel大於level,則對於(level-newlevel)層進行初始化頭結點 。

插入14前,沒有數據只有頭結點 ,當前level默認是1,用temp[0]臨時保存節點,

while循環是找到下一個節點,比如插入34時,while之後level1 層 x節點就是23;插入50時,while循環之後 level1層temp[0] = 43,level2層 temp[1]=34,level3層 temp[2]=14;

保存完臨時節點後,隨機生成新的層數,

14插入時隨機層數是3,大於之前默認的level =1;對於level2 和level3就要進行頭結點賦值,temp[1]=header;temp[2]=header;

然後進行新節點的插入,之前遍歷都是從高層向底層擴展,插入操作要從底層向高層擴展。

插入操作之後,形成了temp[0]->next = 節點14,temp[1]->next = 節點14,temp[2]->next = 節點14即header[0]->next = 節點14,header[1]->next = 節點14,header[2]->next = 節點14;

14節點插入之後,鏈表長度+1(單指最底層的鏈表);

接著插入節點23,臨時變數temp值為temp[0]= 節點14,temp[1] = 節點14,temp[2] = 節點14; 隨機新的newlevel =1,說明23 只出現在level1層,進行插入後temp[0]->next = 節點23,其他兩個節點不變。

插入節點34後,各層鏈表為

header[2]->14

header[1]->14->34

header[0]->14->23->34

以上就是插入的大致邏輯分析。

刪除操作類似,先從上層開始向後遍歷然後向下,刪除時從下層開始向上操作。

跳錶實現定時器demo源碼地址: 跳錶實現定時器demo

紅黑樹:一顆節點非紅即黑的平衡二叉樹。epoll底層使用紅黑樹。

紅黑樹插入 查詢,刪除等基本操作時間復雜度為O(lgn),跳錶搜索插入刪除操作時間復雜度接近O(lgn),最壞情況下變成O(n)。

做范圍查找的時候 ,平衡樹比跳錶操作要復雜,平衡樹上,找到指定范圍的小值之後,還需要以中序遍歷的順序繼續尋找其他不超過大值的節點,相對而言,跳錶的范圍查詢就比較簡單,只要找到小值,在第一層鏈表進行若干層遍歷就行。

從演算法實現難度講,跳錶比紅黑樹要簡單的多。

紅黑樹也是實現定時器的數據結構之一,具體實現不再詳敘。

⑹ Handler消息機制(一):Linux的epoll機制

在linux 沒有實現epoll事件驅動機制之前,我們一般選擇用select或者poll等IO多路復用的方法來實現並發服務程序。在linux新的內核中,有了一種替換它的機制,就是epoll。

相比select模型, poll使用鏈表保存文件描述符,因此沒有了監視文件數量的限制 ,但其他三個缺點依然存在。

假設我們的伺服器需要支持100萬的並發連接,則在__FD_SETSIZE 為1024的情況下,則我們至少需要開辟1k個進程才能實現100萬的並發連接。除了進程間上下文切換的時間消耗外,從內核/用戶空間大量的無腦內存拷貝、數組輪詢等,是系統難以承受的。因此,基於select模型的伺服器程序,要達到10萬級別的並發訪問,是一個很難完成的任務。

由於epoll的實現機制與select/poll機制完全不同,上面所說的 select的缺點在epoll上不復存在。

設想一下如下場景:有100萬個客戶端同時與一個伺服器進程保持著TCP連接。而每一時刻,通常只有幾百上千個TCP連接是活躍的(事實上大部分場景都是這種情況)。如何實現這樣的高並發?

在select/poll時代,伺服器進程每次都把這100萬個連接告訴操作系統(從用戶態復制句柄數據結構到內核態),讓操作系統內核去查詢這些套接字上是否有事件發生,輪詢完後,再將句柄數據復制到用戶態,讓伺服器應用程序輪詢處理已發生的網路事件,這一過程資源消耗較大,因此,select/poll一般只能處理幾千的並發連接。

epoll的設計和實現與select完全不同。epoll通過在Linux內核中申請一個簡易的文件系統(文件系統一般用什麼數據結構實現?B+樹)。把原先的select/poll調用分成了3個部分:

1)調用epoll_create()建立一個epoll對象(在epoll文件系統中為這個句柄對象分配資源)

2)調用epoll_ctl向epoll對象中添加這100萬個連接的套接字

3)調用epoll_wait收集發生的事件的連接

如此一來,要實現上面說是的場景,只需要在進程啟動時建立一個epoll對象,然後在需要的時候向這個epoll對象中添加或者刪除連接。同時,epoll_wait的效率也非常高,因為調用epoll_wait時,並沒有一股腦的向操作系統復制這100萬個連接的句柄數據,內核也不需要去遍歷全部的連接。

當某一進程調用epoll_create方法時,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關。eventpoll結構體如下所示:

每一個epoll對象都有一個獨立的eventpoll結構體,用於存放通過epoll_ctl方法向epoll對象中添加進來的事件。這些事件都會掛載在紅黑樹中,如此,重復添加的事件就可以通過紅黑樹而高效的識別出來(紅黑樹的插入時間效率是lgn,其中n為樹的高度)。

而所有 添加到epoll中的事件都會與設備(網卡)驅動程序建立回調關系,也就是說,當相應的事件發生時會調用這個回調方法 。這個回調方法在內核中叫ep_poll_callback,它會將發生的事件添加到rdlist雙鏈表中。

在epoll中,對於每一個事件,都會建立一個epitem結構體,如下所示:

當調用epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不為空,則把發生的事件復制到用戶態,同時將事件數量返回給用戶。

epoll結構示意圖

通過紅黑樹和雙鏈表數據結構,並結合回調機制,造就了epoll的高效。

events可以是以下幾個宏的集合:
EPOLLIN:觸發該事件,表示對應的文件描述符上有可讀數據。(包括對端SOCKET正常關閉);
EPOLLOUT:觸發該事件,表示對應的文件描述符上可以寫數據;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP: 表示對應的文件描述符被掛斷;
EPOLLET:將EPOLL設為邊緣觸發(EdgeTriggered)模式,這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT: 只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里。
示例:

ET(EdgeTriggered) :高速工作模式,只支持no_block(非阻塞模式)。在此模式下,當描述符從未就緒變為就緒時,內核通過epoll告知。然後它會假設用戶知道文件描述符已經就緒,並且不會再為那個文件描述符發送更多的就緒通知,直到某些操作導致那個文件描述符不再為就緒狀態了。(觸發模式只在數據就緒時通知一次,若數據沒有讀完,下一次不會通知,直到有新的就緒數據)

LT(LevelTriggered) :預設工作方式,支持blocksocket和no_blocksocket。在LT模式下內核會告知一個文件描述符是否就緒了,然後可以對這個就緒的fd進行IO操作。如果不作任何操作,內核還是會繼續通知!若數據沒有讀完,內核也會繼續通知,直至設備數據為空為止!

1.我們已經把一個用來從管道中讀取數據的文件句柄(RFD)添加到epoll描述符
2. 這個時候從管道的另一端被寫入了2KB的數據
3. 調用epoll_wait(2),並且它會返回RFD,說明它已經准備好讀取操作
4. 然後我們讀取了1KB的數據
5. 調用epoll_wait(2)……

ET工作模式:
如果我們在第1步將RFD添加到epoll描述符的時候使用了EPOLLET標志,在第2步執行了一個寫操作,第三步epoll_wait會返回同時通知的事件會銷毀。因為第4步的讀取操作沒有讀空文件輸入緩沖區內的數據,因此我們在第5步調用epoll_wait(2)完成後,是否掛起是不確定的。epoll工作在ET模式的時候,必須使用非阻塞套介面,以避免由於一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。

只有當read(2)或者write(2)返回EAGAIN時(認為讀完)才需要掛起,等待。但這並不是說每次read()時都需要循環讀,直到讀到產生一個EAGAIN才認為此次事件處理完成,當read()返回的讀到的數據長度小於請求的數據長度時(即小於sizeof(buf)),就可以確定此時緩沖中已沒有數據了,也就可以認為此事讀事件已處理完成。

LT工作模式:
LT方式調用epoll介面的時候,它就相當於一個速度比較快的poll(2),並且無論後面的數據是否被使用,因此他們具有同樣的職能。

當調用 epoll_wait檢查是否有發生事件的連接時,只是檢查 eventpoll對象中的 rdllist雙向鏈表是否有 epitem元素而已,如果 rdllist鏈表不為空,則把這里的事件復制到用戶態內存中,同時將事件數量返回給用戶。因此,epoll_wait的效率非常高。epoll_ctl在向 epoll對象中添加、修改、刪除事件時,從 rbr紅黑樹中查找事件也非常快,也就是說,epoll是非常高效的,它可以輕易地處理百萬級別的並發連接。

1.減少用戶態和內核態之間的文件句柄拷貝;

2.減少對可讀可寫文件句柄的遍歷。

https://cloud.tencent.com/developer/information/linux%20epoll%E6%9C%BA%E5%88%B6
https://blog.csdn.net/u010657219/article/details/44061629
https://jiahao..com/s?id=1609322251459722004&wfr=spider&for=pc

⑺ Node.js的心臟-epoll

    我們都知道Node.js是非同步的,那麼Node.js為什麼會是非同步的呢?這是因為Node.js使用了LIBUV做為它的跨平台抽象層。具體請看 nodejs運行機制

    select、poll、epoll是Linux平台下的IO多路復用機制,用來管理大量的文件描述符。但是select/poll相對於epoll來說效率是低下的。

      1、linux內核在select的每次返回前都要對所有的描述符 循環遍歷 ,將有事件發生的文件描述符放在一個集合里返回。在描述符不多的時候對性能影響不大,但是當描述符達到數十萬甚至更多的時候,這種處理方式造成大量的浪費和資源開銷,select的效率會急劇下降。這是因為每次select的時候,會將所有的文件描述符從用戶態拷貝的內核態,在內核態進行循環,查看是否有事件發生。2、select默認的管理的最大文件描述符是1024個,當然可以對linux內核從新編譯來改變這個限制。

    原理和select相似也是使用循環遍歷的方式管理文件描述符,不同的是管理的文件最大文件描述符的數量沒有限制(根據系統限制來定)。

下文講解epoll實現原理

    epoll改進了select的兩個缺點,從而能夠在管理大量的描述符的情況下,對系統資源的使用並沒有急劇的增加,而只是對內存的使用有所增加(畢竟存儲大量的描述符的數據結構會佔用大量內存)。epoll在實現上的三個核心點是:1、mmap,2、紅黑樹,3、rdlist(就緒描述符鏈表)接下來一一解釋這三個並且解釋為什麼會高效。

    mmap是共享內存,用戶進程和內核有一段地址(虛擬存儲器地址)映射到了同一塊物理地址上,這樣當內核要對描述符上的事件進行檢查的時候就不用來回的拷貝了。   

    紅黑樹是用來存儲這些描述符的。當內核初始化epoll的時候(當調用epoll_create的時候內核也是個epoll描述符創建了一個文件,畢竟在Linux中一切都是文件,而epoll面對的是一個特殊的文件,和普通文件不同),會開辟出一塊內核緩沖區,這塊區域用來存儲我們要監管的所有的socket描述符,當然在這裡面存儲有一個數據結構,這就是紅黑樹,由於紅黑樹的接近平衡的查找,插入,刪除能力,在這里顯著的提高了對描述符的管理。

        rdlist就緒描述符鏈表這是一個雙鏈表,epoll_wait()函數返回的也是這個就緒鏈表。當內核創建了紅黑樹之後,同時也會建立一個雙向鏈表rdlist,用於存儲准備就緒的描述符,當調用epoll_wait的時候在timeout時間內,只是簡單的去管理這個rdlist中是否有數據,如果沒有則睡眠至超時,如果有數據則立即返回並將鏈表中的數據賦值到events數組中。這樣就能夠高效的管理就緒的描述符,而不用去輪詢所有的描述符。

        當執行epoll_ctl時除了把socket描述符放入到紅黑樹中之外,還會給內核中斷處理程序注冊一個回調函數,告訴內核,當這個描述符上有事件到達(或者說中斷了)的時候就調用這個回調函數。這個回調函數的作用就是將描述符放入到rdlist中,所以當一個socket上的數據到達的時候內核就會把網卡上的數據復制到內核,然後把socket描述符插入就緒鏈表rdlist中。

Epoll的兩種模式:

      1. 水平觸發(LT):使用此種模式,當數據可讀的時候,epoll_wait()將會一直返回就緒事件。如果你沒有處理完全部數據,並且再次在該epoll實例上調用epoll_wait()才監聽描述符的時候,它將會再次返回就緒事件,因為有數據可讀。

      2. 邊緣觸發(ET):使用此種模式,只能獲取一次就緒通知,如果沒有處理完全部數據,並且再次調用epoll_wait()的時候,它將會阻塞,因為就緒事件已經釋放出來了。

        ET的效能更高,但是對程序員的要求也更高。在ET模式下,我們必須一次干凈而徹底地處理完所有事件。

epoll的linux實現

⑻ epoll底層原理總結

1  epoll一種網路模式,採用的是 IO多路復用技術(就是可以監控多個文件描述符),相比較於select 和poll是非常快的;

首先看這三個函數:

1 int epoll_create(int size);

2 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

3 int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

函數用法不在這里講解;

首先epoll_create創建一個epoll文件描述符,底層同時創建一個 紅黑樹 ,和一個 就緒鏈表 ;紅黑樹存儲所監控的文件描述符的節點數據,就緒鏈表存儲就緒的文件描述符的節點數據;epoll_ctl將會添加新的描述符,首先判斷是紅黑樹上是否有此文件描述符節點,如果有,則立即返回。如果沒有, 則在樹幹上插入新的節點,並且告知 內核注冊回調函數 。當接收到某個文件描述符過來數據時,那麼內核將該節點插入到就緒鏈表裡面。epoll_wait將會接收到消息,並且將數據拷貝到用戶空間,清空鏈表。對於LT模式epoll_wait清空就緒鏈表之後會檢查該文件描述符是哪一種模式, 如果為LT模式,且必須該節點確實有事件未處理,那麼就會把該節點重新放入到剛剛刪除掉的且剛准備好的就緒鏈表,epoll_wait馬上返回。 ET 模式不會檢查,只會調用一次

每個epollfd在內核中有一個對應的eventpoll結構對象.其中關鍵的成員是一個readylist(eventpoll:rdllist)

和一棵紅黑樹(eventpoll:rbr).

一個fd被添加到epoll中之後(EPOLL_ADD),內核會為它生成一個對應的epitem結構對象.epitem被添加到

eventpoll的紅黑樹中.紅黑樹的作用是使用者調用EPOLL_MOD的時候可以快速找到fd對應的epitem。

調用epoll_wait的時候,將readylist中的epitem出列,將觸發的事件拷貝到用戶空間.之後判斷epitem是否需

要重新添加回readylist.

epitem重新添加到readylist必須滿足下列條件:

1) epitem上有用戶關注的事件觸發.

2) epitem被設置為水平觸發模式(如果一個epitem被設置為邊界觸發則這個epitem不會被重新添加到readylist

中,在什麼時候重新添加到readylist請繼續往下看).

注意,如果epitem被設置為EPOLLONESHOT模式,則當這個epitem上的事件拷貝到用戶空間之後,會將

這個epitem上的關注事件清空(只是關注事件被清空,並沒有從epoll中刪除,要刪除必須對那個描述符調用

EPOLL_DEL),也就是說即使這個epitem上有觸發事件,但是因為沒有用戶關注的事件所以不會被重新添加到

readylist中.

epitem被添加到readylist中的各種情況(當一個epitem被添加到readylist如果有線程阻塞在epoll_wait中,那

個線程會被喚醒):

1)對一個fd調用EPOLL_ADD,如果這個fd上有用戶關注的激活事件,則這個fd會被添加到readylist.

2)對一個fd調用EPOLL_MOD改變關注的事件,如果新增加了一個關注事件且對應的fd上有相應的事件激活,

則這個fd會被添加到readylist.

3)當一個fd上有事件觸發時(例如一個socket上有外來的數據)會調用ep_poll_callback(見eventpoll::ep_ptable_queue_proc),

如果觸發的事件是用戶關注的事件,則這個fd會被添加到readylist中.

了解了epoll的執行過程之後,可以回答一個在使用邊界觸發時常見的疑問.在一個fd被設置為邊界觸發的情況下,

調用read/write,如何正確的判斷那個fd已經沒有數據可讀/不再可寫.epoll文檔中的建議是直到觸發EAGAIN

錯誤.而實際上只要你請求位元組數小於read/write的返回值就可以確定那個fd上已經沒有數據可讀/不再可寫.

最後用一個epollfd監聽另一個epollfd也是合法的,epoll通過調用eventpoll::ep_eventpoll_poll來判斷一個

epollfd上是否有觸發的事件(只能是讀事件).

閱讀全文

與epoll底層用了什麼數據結構相關的資料

熱點內容
項目代理合同是什麼 瀏覽:834
東莞貸款代理公司怎麼這么多 瀏覽:353
硅烷產品的主要優點都有哪些 瀏覽:336
紐威機床如何調程序 瀏覽:173
小公司財務代理記賬哪裡找 瀏覽:36
現在加價購買的電子產品有哪些 瀏覽:302
什麼產品親測能變白 瀏覽:195
如何開一家白酒代理商 瀏覽:896
微信推廣怎麼代理 瀏覽:24
專業技術崗位如何轉正 瀏覽:314
農產品加工怎麼搶零食 瀏覽:921
智能小程序包怎麼修改 瀏覽:372
品牌縣級代理商是什麼級別 瀏覽:120
新車沒信息怎麼辦 瀏覽:99
體制內技術行業有哪些 瀏覽:828
qq小程序的游戲如何反饋 瀏覽:759
泡殼包裝產品如何包裝 瀏覽:384
菜市場賣菜的商戶怎麼好招商 瀏覽:36
喜歡消費的女人用什麼產品 瀏覽:529
表格數據變日期了怎麼辦 瀏覽:473