導航:首頁 > 操作系統 > linuxio復用

linuxio復用

發布時間:2022-11-24 22:42:02

『壹』 linux中非同步IO模型有哪些

1)阻塞I/O(blocking I/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O復用(select 和poll) (I/O multiplexing)
4)信號驅動I/O (signal driven I/O (SIGIO))
5)非同步I/O (asynchronous I/O (the POSIX aio_functions))
其中前4種都是同步,最後一種才是非同步。

『貳』 面試必問的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時除外),如果有監聽的事件產生,該進程就被喚醒,同時將事件從內核裡面拷貝到用戶空間返回給該進程。

『叄』 什麼是i/o復用

當你編寫的程序需要同時處理多個描數字(socket或file或device),你又不知道什麼時候應該(比方說有數據可以讀了)去操作(讀/寫)哪個描數字。這時候I/O復用就需要登場了。

I/O復用是一種讓進程預先「警告」內核能力,使得內核一旦發現進程預先告知時指定的一個或多個I/O條件(就是描述符)就緒(可以讀/寫了),內核就通知進程。linux有4個調用可實現I/O復用:select、poll繼承自Unix系統。pselect是select到Posix版。epoll是linux2.6內核特有的。

『肆』 Linux系統I/O模型及select、poll、epoll原理和應用

理解Linux的IO模型之前,首先要了解一些基本概念,才能理解這些IO模型設計的依據

操作系統使用虛擬內存來映射物理內存,對於32位的操作系統來說,虛擬地址空間為4G(2^32)。操作系統的核心是內核,為了保護用戶進程不能直接操作內核,保證內核安全,操作系統將虛擬地址空間劃分為內核空間和用戶空間。內核可以訪問全部的地址空間,擁有訪問底層硬體設備的許可權,普通的應用程序需要訪問硬體設備必須通過 系統調用 來實現。

對於Linux系統來說,將虛擬內存的最高1G位元組的空間作為內核空間僅供內核使用,低3G位元組的空間供用戶進程使用,稱為用戶空間。

又被稱為標准I/O,大多數文件系統的默認I/O都是緩存I/O。在Linux系統的緩存I/O機制中,操作系統會將I/O的數據緩存在頁緩存(內存)中,也就是數據先被拷貝到內核的緩沖區(內核地址空間),然後才會從內核緩沖區拷貝到應用程序的緩沖區(用戶地址空間)。

這種方式很明顯的缺點就是數據傳輸過程中需要再應用程序地址空間和內核空間進行多次數據拷貝操作,這些操作帶來的CPU以及內存的開銷是非常大的。

由於Linux系統採用的緩存I/O模式,對於一次I/O訪問,以讀操作舉例,數據先會被拷貝到內核緩沖區,然後才會從內核緩沖區拷貝到應用程序的緩存區,當一個read系統調用發生的時候,會經歷兩個階段:

正是因為這兩個狀態,Linux系統才產生了多種不同的網路I/O模式的方案

Linux系統默認情況下所有socke都是blocking的,一個讀操作流程如下:

以UDP socket為例,當用戶進程調用了recvfrom系統調用,如果數據還沒准備好,應用進程被阻塞,內核直到數據到來且將數據從內核緩沖區拷貝到了應用進程緩沖區,然後向用戶進程返回結果,用戶進程才解除block狀態,重新運行起來。

阻塞模行下只是阻塞了當前的應用進程,其他進程還可以執行,不消耗CPU時間,CPU的利用率較高。

Linux可以設置socket為非阻塞的,非阻塞模式下執行一個讀操作流程如下:

當用戶進程發出recvfrom系統調用時,如果kernel中的數據還沒准備好,recvfrom會立即返回一個error結果,不會阻塞用戶進程,用戶進程收到error時知道數據還沒准備好,過一會再調用recvfrom,直到kernel中的數據准備好了,內核就立即將數據拷貝到用戶內存然後返回ok,這個過程需要用戶進程去輪詢內核數據是否准備好。

非阻塞模型下由於要處理更多的系統調用,因此CPU利用率比較低。

應用進程使用sigaction系統調用,內核立即返回,等到kernel數據准備好時會給用戶進程發送一個信號,告訴用戶進程可以進行IO操作了,然後用戶進程再調用IO系統調用如recvfrom,將數據從內核緩沖區拷貝到應用進程。流程如下:

相比於輪詢的方式,不需要多次系統調用輪詢,信號驅動IO的CPU利用率更高。

非同步IO模型與其他模型最大的區別是,非同步IO在系統調用返回的時候所有操作都已經完成,應用進程既不需要等待數據准備,也不需要在數據到來後等待數據從內核緩沖區拷貝到用戶緩沖區,流程如下:

在數據拷貝完成後,kernel會給用戶進程發送一個信號告訴其read操作完成了。

是用select、poll等待數據,可以等待多個socket中的任一個變為可讀,這一過程會被阻塞,當某個套接字數據到來時返回,之後再用recvfrom系統調用把數據從內核緩存區復制到用戶進程,流程如下:

流程類似阻塞IO,甚至比阻塞IO更差,多使用了一個系統調用,但是IO多路復用最大的特點是讓單個進程能同時處理多個IO事件的能力,又被稱為事件驅動IO,相比於多線程模型,IO復用模型不需要線程的創建、切換、銷毀,系統開銷更小,適合高並發的場景。

select是IO多路復用模型的一種實現,當select函數返回後可以通過輪詢fdset來找到就緒的socket。

優點是幾乎所有平台都支持,缺點在於能夠監聽的fd數量有限,Linux系統上一般為1024,是寫死在宏定義中的,要修改需要重新編譯內核。而且每次都要把所有的fd在用戶空間和內核空間拷貝,這個操作是比較耗時的。

poll和select基本相同,不同的是poll沒有最大fd數量限制(實際也會受到物理資源的限制,因為系統的fd數量是有限的),而且提供了更多的時間類型。

總結:select和poll都需要在返回後通過輪詢的方式檢查就緒的socket,事實上同時連的大量socket在一個時刻只有很少的處於就緒狀態,因此隨著監視的描述符數量的變多,其性能也會逐漸下降。

epoll是select和poll的改進版本,更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的只需一次。

epoll_create()用來創建一個epoll句柄。
epoll_ctl() 用於向內核注冊新的描述符或者是改變某個文件描述符的狀態。已注冊的描述符在內核中會被維護在一棵紅黑樹上,通過回調函數內核會將 I/O 准備好的描述符加入到一個就緒鏈表中管理。
epoll_wait() 可以從就緒鏈表中得到事件完成的描述符,因此進程不需要通過輪詢來獲得事件完成的描述符。

當epoll_wait檢測到描述符IO事件發生並且通知給應用程序時,應用程序可以不立即處理該事件,下次調用epoll_wait還會再次通知該事件,支持block和nonblocking socket。

當epoll_wait檢測到描述符IO事件發生並且通知給應用程序時,應用程序需要立即處理該事件,如果不立即處理,下次調用epoll_wait不會再次通知該事件。

ET模式在很大程度上減少了epoll事件被重復觸發的次數,因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用nonblocking socket,以避免由於一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。

【segmentfault】 Linux IO模式及 select、poll、epoll詳解
【GitHub】 CyC2018/CS-Notes

『伍』 IO多路復用的三種機制Select,Poll,Epoll

select、poll 和 epoll 都是 Linux API 提供的 IO 復用方式。

相信大家都了解了Unix五種IO模型,不了解的可以 => 查看這里

[1] blocking IO - 阻塞IO
[2] nonblocking IO - 非阻塞IO
[3] IO multiplexing - IO多路復用
[4] signal driven IO - 信號驅動IO
[5] asynchronous IO - 非同步IO

其中前面4種IO都可以歸類為synchronous IO - 同步IO,而select、poll、epoll本質上也都是同步I/O,因為他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的。

與多進程和多線程技術相比,I/O多路復用技術的最大優勢是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。

在介紹select、poll、epoll之前,首先介紹一下Linux操作系統中 基礎的概念

我們先分析一下select函數

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

【參數說明】
int maxfdp1 指定待測試的文件描述字個數,它的值是待測試的最大描述字加1。
fd_set *readset , fd_set *writeset , fd_set *exceptset
fd_set 可以理解為一個集合,這個集合中存放的是文件描述符(file descriptor),即文件句柄。中間的三個參數指定我們要讓內核測試讀、寫和異常條件的文件描述符集合。如果對某一個的條件不感興趣,就可以把它設為空指針。
const struct timeval *timeout timeout 告知內核等待所指定文件描述符集合中的任何一個就緒可花多少時間。其timeval結構用於指定這段時間的秒數和微秒數。

【返回值】
int 若有就緒描述符返回其數目,若超時則為0,若出錯則為-1

select()的機制中提供一種 fd_set 的數據結構,實際上是一個long類型的數組,每一個數組元素都能與一打開的文件句柄(不管是Socket句柄,還是其他文件或命名管道或設備句柄)建立聯系,建立聯系的工作由程序員完成,當調用select()時,由內核根據IO狀態修改fd_set的內容,由此來通知執行了select()的進程哪一Socket或文件可讀。

從流程上來看,使用select函數進行IO請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視socket,以及調用select函數的額外操作,效率更差。但是,使用select以後最大的優勢是用戶可以在一個線程內同時處理多個socket的IO請求。用戶可以注冊多個socket,然後不斷地調用select讀取被激活的socket,即可達到在同一個線程內同時處理多個IO請求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達到這個目的。

poll的機制與select類似,與select在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是poll沒有最大文件描述符數量的限制。也就是說,poll只解決了上面的問題3,並沒有解決問題1,2的性能開銷問題。

下面是pll的函數原型:

poll改變了文件描述符集合的描述方式,使用了 pollfd 結構而不是select的 fd_set 結構,使得poll支持的文件描述符集合限制遠大於select的1024

【參數說明】

struct pollfd *fds fds 是一個 struct pollfd 類型的數組,用於存放需要檢測其狀態的socket描述符,並且調用poll函數之後 fds 數組不會被清空;一個 pollfd 結構體表示一個被監視的文件描述符,通過傳遞 fds 指示 poll() 監視多個文件描述符。其中,結構體的 events 域是監視該文件描述符的事件掩碼,由用戶來設置這個域,結構體的 revents 域是文件描述符的操作結果事件掩碼,內核在調用返回時設置這個域

nfds_t nfds 記錄數組 fds 中描述符的總數量

【返回值】
int 函數返回fds集合中就緒的讀、寫,或出錯的描述符數量,返回0表示超時,返回-1表示出錯;

epoll在Linux2.6內核正式提出,是基於事件驅動的I/O方式,相對於select來說,epoll沒有描述符個數限制,使用一個文件描述符管理多個描述符,將用戶關心的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的只需一次。

Linux中提供的epoll相關函數如下:

1. epoll_create 函數創建一個epoll句柄,參數 size 表明內核要監聽的描述符數量。調用成功時返回一個epoll句柄描述符,失敗時返回-1。

2. epoll_ctl 函數注冊要監聽的事件類型。四個參數解釋如下:

epoll_event 結構體定義如下:

3. epoll_wait 函數等待事件的就緒,成功時返回就緒的事件數目,調用失敗時返回 -1,等待超時返回 0。

epoll是Linux內核為處理大批量文件描述符而作了改進的poll,是Linux下多路復用IO介面select/poll的增強版本,它能顯著提高程序在大量並發連接中只有少量活躍的情況下的系統CPU利用率。原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件非同步喚醒而加入Ready隊列的描述符集合就行了。

epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。

LT和ET原本應該是用於脈沖信號的,可能用它來解釋更加形象。Level和Edge指的就是觸發點,Level為只要處於水平,那麼就一直觸發,而Edge則為上升沿和下降沿的時候觸發。比如:0->1 就是Edge,1->1 就是Level。

ET模式很大程度上減少了epoll事件的觸發次數,因此效率比LT模式下高。

一張圖總結一下select,poll,epoll的區別:

epoll是Linux目前大規模網路並發程序開發的首選模型。在絕大多數情況下性能遠超select和poll。目前流行的高性能web伺服器Nginx正式依賴於epoll提供的高效網路套接字輪詢服務。但是,在並發連接不高的情況下,多線程+阻塞I/O方式可能性能更好。

既然select,poll,epoll都是I/O多路復用的具體的實現,之所以現在同時存在,其實他們也是不同歷史時期的產物

『陸』 干貨 五種IO模型的特點以及比較

為了保證操作系統的安全,將內存劃分為內核空間和用戶空間。內核空間的進程,可以訪問硬體執行IO等操作,用戶空間的進程只能通過系統調用來訪問IO等系統資源。

對於一次IO訪問(以read舉例),數據會先被拷貝到操作系統內核的緩沖區中,然後再拷貝到應用程序的地址空間。
所以,當一個read操作發生時,它會經歷兩個階段

正是因為這兩個階段,linux系統產生了下面五種網路模型:

在linux下,默認情況下所有的socket都是blocking的,流程如下圖所示:

進程調用recvfrom系統調用來讀取數據,這是如果還沒有到達,進程就進入阻塞狀態。等數據到達後完成到內核去的拷貝,再從內核拷貝到用戶空間,用戶進程才解除阻塞狀態。

特點:在IO執行的兩個階段進程都會都阻塞

執行非阻塞io系統調用時,如果內核中的數據還沒有準備好,會直接返回,不會阻塞。通過進程不斷查詢,直到數據在內核中就緒,便開始拷貝到用戶空間。拷貝的過程中,進程還是被阻塞了,所有非阻塞IO也是同步IO。

特點:需要進程不斷地主動詢問kernel數據是否准備好了

單個進程處理多個網路連接IO,使用select\poll\epoll三種系統調用,不斷輪詢所有的連接,如果有數據到達內核則通知進程,進行數據拷貝到用戶內存。

當調用select時,進程會進入阻塞狀態,直到有數據到達。

這個圖看起來和阻塞IO區別不大,甚至還多使用了一個系統調用
但它的優勢在於可以同時監控多個IO連接。

所以,如果連接數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web servet性能更好。多路IO復用的優勢並不是對於單個連接能處理的更快,而在於能處理更多的連接。

linux下的非同步io使用的很少,其流程如下:

發起read操作後進程立馬返回,整個Io過程不會產生任何block。kernel會等等數據准備完成,然後將數據拷貝到用戶內存。當這一切都完成後,kernel會給用戶進程發送一個signal,告訴它read操作完成了。

調用blocking io會一直block進程直到操作完成
no-blocking io在kernel准備數據的階段是會立刻返回的

只有非同步IO是非同步IO,
其他3種:阻塞IO、非阻塞IO、多路復用IO都是同步的。
這是因為其他三種IO在執行真實IO操作的過程中都有進程阻塞的階段,而非同步IO在整個過程中進程都沒有被阻塞。非阻塞IO在內核數據就緒,拷貝到用戶空間的階段也是阻塞的,因此也是同步IO。

Linux IO模式及select、poll、epoll詳解

『柒』 linux中block IO,no-block IO,非同步IO,IO多路復用筆記

        現在操作系統都是採用虛擬存儲器,那麼對32位操作系統而言,它的定址空間(虛擬存儲空間)為4G(2的32次方)。操作系統的核心是內核,獨立於普通的應用程序,可以訪問受保護的內存空間,也有訪問底層硬體設備的所有許可權。 為了保證用戶進程不能直接操作內核(kernel),保證內核的安全,操心系統將虛擬空間劃分為兩部分,一部分為內核空間,一部分為用戶空間 。針對linux操作系統而言, 將最高的1G位元組(從虛擬地址0xC0000000到0xFFFFFFFF) ,供內核使用,稱為內核空間, 而將較低的3G位元組(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱為用戶空間。

        文件描述符(File descriptor)是計算機科學中的一個術語,是一個用於表述 指向文件的引用的抽象化概念 。文件描述符在形式上是一個非負整數。 實際上,它是一個索引值,指向內核為每一個進程所維護的該進程打開文件的記錄表 。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫往往會圍繞著文件描述符展開。但是文件描述符這一概念往往只適用於UNIX、Linux這樣的操作系統。

       剛才說了,對於一次IO訪問(以read舉例),數據會先被拷貝到操作系統內核的緩沖區中,然後才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。所以說,當一個read操作發生時,它會經歷兩個階段:

1、等待數據准備 (Waiting for the data to be ready)

2、將數據從內核拷貝到進程中 (Copying the data from the kernel to the process)

正式因為這兩個階段,linux系統產生了下面 五種網路模式 的方案。

阻塞 I/O(blocking IO)

非阻塞 I/O(nonblocking IO)

I/O 多路復用( IO multiplexing)

非同步 I/O(asynchronous IO)

信號驅動 I/O( signal driven IO)

註:由於signal driven IO在實際中並不常用,所以我這只提及剩下的四種IO Model。

阻塞 I/O(blocking IO)

在linux中,默認情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣:

        當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:准備數據(對於網路IO來說,很多時候數據在一開始還沒有到達。比如,還沒有收到一個完整的UDP包。這個時候kernel就要等待足夠的數據到來)。這個過程需要等待,也就是說數據被拷貝到操作系統內核的緩沖區中是需要一個過程的。而在用戶進程這邊,整個進程會被阻塞(當然,是進程自己選擇的阻塞)。當kernel一直等到數據准備好了,它就會將數據從kernel中拷貝到用戶內存,然後kernel返回結果,用戶進程才解除block的狀態,重新運行起來。

所以,blocking IO的特點就是在IO執行的兩個階段都被block了(內核阻塞讀取數據,內核將數據復制到應用戶態)。

非阻塞 I/O(nonblocking IO)

linux下,可以通過設置socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:

       當用戶進程發出read操作時,如果kernel中的數據還沒有準備好,那麼它並不會block用戶進程,而是立刻返回一個error。從用戶進程角度講 ,它發起一個read操作後,並不需要等待,而是馬上就得到了一個結果。用戶進程判斷結果是一個error時,它就知道數據還沒有準備好,於是它可以再次發送read操作。一旦kernel中的數據准備好了,並且又再次收到了用戶進程的system call,那麼它馬上就將數據拷貝到了用戶內存,然後返回。

所以,nonblocking IO的特點是用戶進程需要 不斷的主動詢問 kernel數據好了沒有( 內核讀取數據時,用戶態不需要阻塞,內核將數據復制到用戶態時,需要阻塞 )。

I/O 多路復用( IO multiplexing)

         IO multiplexing就是我們說的select,poll,epoll,有些地方也稱這種IO方式為event driven IO。select/epoll的好處就在於單個process就可以同時處理多個網路連接的IO。它的基本原理就是 select,poll,epoll這個function會不斷的輪詢所負責的所有socket ,當某個socket有數據到達了,就通知用戶進程。

        當用戶 進程調用了select , 那麼整個進程會被block ,而同時,kernel會「監視」所有 select負責的socket(一個管理多個socket連接),當任何一個socket中的數據准備好了,select就會返回 。這個時候用戶進程再調用read操作, 將數據從kernel拷貝到用戶進程 。

所以,I/O 多路復用的特點是通過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就可以返回。

這個圖和blocking IO的圖其實並沒有太大的不同,事實上,還更差一些。 因為這里需要使用兩個system call (select 和 recvfrom),而blocking IO只調用了一個system call (recvfrom) 。但是,用select的優勢在於它可以同時處理多個connection。

所以,如果處理的 連接數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大 。select/epoll的優勢並不是對於單個連接能處理得更快,而是在於能處理更多的連接。)

在IO multiplexing Model中,實際中,對於每一個socket,一般都設置成為non-blocking,但是,如上圖所示,整個用戶的process其實是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。

總結:IO多路復用其實也是阻塞的,阻塞的地方在用當有socket連接有數據以後, 會阻塞知道數據從內核復制到用戶態(第二步阻塞)。

非同步 I/O(asynchronous IO)

inux下的asynchronous IO其實用得很少。先看一下它的流程:

        用戶進程發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到一個asynchronous read之後,首先它會立刻返回,所以不會對用戶進程產生任何block。然後,kernel會等待數據准備完成,然後將數據拷貝到用戶內存,當這一切都完成之後,kernel會給用戶進程發送一個signal,告訴它read操作完成了。

總結:兩個階段都不需要用戶進程干涉,內核將數據准備好以後通知用戶態去讀取

總結

blocking和non-blocking的區別

調用blocking IO會一直block住對應的進程直到操作完成,而non-blocking IO在kernel還准備數據的情況下會立刻返回。

synchronous IO和asynchronous IO的區別

在說明synchronous IO和asynchronous IO的區別之前,需要先給出兩者的定義。POSIX的定義是這樣子的:

- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;

- An asynchronous I/O operation does not cause the requesting process to be blocked;

兩者的區別就在於synchronous IO做」IO operation」的時候會將process阻塞。按照這個定義,之前所述的 blocking IO,non-blocking IO,IO multiplexing都屬於synchronous IO 。

       有人會說,non-blocking IO並沒有被block啊。這里有個非常「狡猾」的地方,定義中所指的」IO operation」是指真實的IO操作,就是例子中的recvfrom這個system call。non-blocking IO在執行recvfrom這個system call的時候,如果kernel的數據沒有準備好,這時候不會block進程。但是, 當kernel中數據准備好的時候,recvfrom會將數據從kernel拷貝到用戶內存中,這個時候進程是被block了,在這段時間內,進程是被block的。

而asynchronous IO則不一樣,當進程發起IO 操作之後,就直接返回再也不理睬了,直到kernel發送一個信號,告訴進程說IO完成。在這整個過程中,進程完全沒有被block。

『捌』 同步與非同步,阻塞與非阻塞的區別,以及select,poll和epoll

非同步的概念和同步相對。
(1)當一個同步調用發出後,調用者要一直等待返回消息(結果)通知後,才能進行後續的執行;

(2)當一個非同步過程調用發出後,調用者不能立刻得到返回消息(結果)。實際處理這個調用的部件在完成後,通過 狀態、通知和回調 來通知調用者。

這里提到執行部件和調用者通過三種途徑返回結果:狀態、通知和回調。使用哪一種通知機制,依賴於執行部件的實現,除非執行部件提供多種選擇,否則不受調用者控制。

(A)阻塞調用是指調用結果返回之前,當前線程會被掛起,一直處於等待消息通知,不能夠執行其他業務

(B)非阻塞調用是指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回

場景比喻:
舉個例子,比如我去銀行辦理業務,可能會有兩種方式:

在上面的場景中,如果:
a)如果選擇排隊(同步),且排隊的時候什麼都不幹(線程被掛起,什麼都幹不了),是同步阻塞模型;
b)如果選擇排隊(同步),但是排隊的同時做與辦銀行業務無關的事情,比如抽煙,(線程沒有被掛起,還可以干一些其他的事),是同步非阻塞模型;
c)如果選擇拿個小票,做在位置上等著叫號(通知),但是坐在位置上什麼都不幹(線程被掛起,什麼都幹不了),這是非同步阻塞模型;
d)如果選擇那個小票,坐在位置上等著叫號(通知),但是坐著的同時還打電話談生意(線程沒有被掛起,還可以干其他事情),這是非同步非阻塞模型。

對這四種模型做一個總結:
1:同步阻塞模型,效率最低,即你專心排隊,什麼都不幹。
2:非同步阻塞,效率也非常低,即你拿著號等著被叫(通知),但是坐那什麼都不幹
3:同步非阻塞,效率其實也不高,因為涉及到線程的來回切換。即你在排隊的同時打電話或者抽煙,但是你必須時不時得在隊伍中挪動。程序需要在排隊和打電話這兩種動作之間來回切換,系統開銷可想而知。
4:非同步非阻塞,效率很高,你拿著小票在那坐著等叫號(通知)的同時,打電話談你的生意。

linux下幾個基本概念
1:用戶控制項和內核空間。 現代操作系統都是採用虛擬存儲器,在32位操作系統下,它的定址空間(虛擬存儲空間)為4G(2的32次方)。為了保證用戶進程補鞥呢直接操作內核,保證內核的安全,操作系統將虛擬空間劃分為兩部分,一部分為內核空間,一部分為用戶空間。對linux操作系統而言,將最高的1G位元組空間分給了內核使用,稱為內核空間,將較低的3G位元組的空間劃分為用戶空間。

2:進程切換很耗資源 ,為了控制進程的執行,內核必須有能力掛起正在cpu上運行的進程,並恢復以前掛起的某個進程的執行,這種行為叫進程的切換。每次切換,要保存上一個的上下文環境等等,總之記住進程切換很耗資源。

3:文件描述符 :文件描述符在形式上是一個非負整數。實際上,他是一個索引,指向內核為每個進程所維護的該進程打開文件的記錄表。當程序打開一個文件時,內核就會向進程返回一個非負整數的文件描述符。但是文件描述符一般在unix,linux系統中才講。

緩存IO ,大多數系統的默認IO操作都是緩存IO,在linux的緩存IO機制中,操作系統會將IO的數據緩存在系統的頁緩存(page cache)中,也就是說,數據會先被拷貝到操作系統內核的緩沖區,然後才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。 緩存IO的缺點: 數據在傳輸過程中需要在應用程序和地址空間和內核進行多次數據拷貝操作,這種數據拷貝操作鎖帶來的cpu以及內存消耗是很大的。

LINUX的IO模型
網路IO的本質是socket的讀取。socket在linux系統被抽象為流,故對網路IO的操作可以理解為對流的操作。

對於一次IO訪問,比如以read操作為例, 數據會先被拷貝到操作系統內核的緩沖區,然後才會從內核緩沖區拷貝到進程的用戶層,即應用程序的地址空間 。故當一個read操作發生時,其實是經歷了兩個階段:
1:內核緩沖區的數據就位
2:數據從內核緩沖區拷貝到用戶程序地址空間

那麼具體到socket io的一次read操來說,這兩步分別是:
1:等待網路上的數據分組到達,然後復制到內核緩沖區中
2:數據從內核緩沖區拷貝到用戶程序的地址空間(緩沖區)

所以說 網路應用要處理的無非就兩個問題:網路IO和數據計算 ,一般來說網路io帶來的延遲影響比較大。

網路IO的模型大致有如下幾種:

熟悉不? 我們常說的select,poll和epoll就是屬於同步模型中多路復用IO的不同實現方法罷了。 下面分別對同步阻塞,同步不阻塞,同步io復用進行說明。

一:同步阻塞
它是最簡單也最常用的網路IO模型。linux下默認的socket都是blocking的。

從圖中可以看到,用戶進程調用recvfrom這個系統調用後,就處於阻塞狀態。然後kernel就開始了IO的第一個階段:數據准備。等第一個階段准備完成之後,kernel開始第二階段,將數據從內核緩沖區拷貝到用戶程序緩沖區(需要花費一定時間)。然後kernel返回結果(確切的說是recvfrom這個系統調用函數返回結果),用戶進程才結束blocking,重新運行起來。
總結 同步阻塞模型下,用戶程序在kernel執行io的兩個階段都被blocking住了 。但是優點也是因為這個,無延遲能及時返回數據,且程序模型簡單。

二:同步非阻塞
同步非阻塞就是隔一會瞄一下的輪詢方式。同步非阻塞模式其實是可以看做一小段一小段的同步阻塞模式。

三:IO多路復用
由於同步非阻塞方式需要不斷的輪詢,光輪詢就占據了很大一部分過程,且消耗cpu資源。而這個用戶進程可能不止對這個socket的read,可能還有對其他socket的read或者write操作,那人們就想到了一次輪詢的時候,不光只查詢詢一個socket fd,而是在一次輪詢下,查詢多個任務的socket fd的完成狀態,只要有任何一個任務完成,就去處理它。而且,輪詢人不是進程的用戶態,而是有人幫忙就好了。那麼這就是所謂的 IO多路復用 。總所周知的linux下的select,poll和epoll就是這么乾的。。。

selelct調用是內核級別的,selelct輪詢相比較同步非阻塞模式下的輪詢的區別為: 前者可以等待多個socket,能實現同時對多個IO埠的監聽 ,當其中任何一個socket數據准備好了,就返回可讀。 select或poll調用之後,會阻塞進程 ,與blocking IO 阻塞不用在於,此時的select不是等到所有socket數據達到再處理,而是某個socket數據就會返回給用戶進程來處理。
其實select這種相比較同步non-blocking的效果在單個任務的情況下可能還更差一些 ,因為這里調用了select和recvfrom兩個system call,而non-blocking只調用了一個recvfrom,但是 用select的優勢在於它可以同時處理多個socket fd

在io復用模型下,對於每一個socket,一般都設置成non-blocking,但是其實 整個用戶進程是一直被block的 ,只不過用戶process不是被socket IO給block住,而是被select這個函數block住的。

與多進程多線程技術相比,IO多路復用的最大優勢是系統開銷小。

一:select
select函數監視多個socket fs,直到有描述符就緒或者超時,函數返回。當select函數返回後,可以通過遍歷fdset,來找到就緒的描述符。select的基本流程為:

二:poll
poll本質上跟select沒有區別,它將用戶傳入的數組拷貝到內核空間,然後查詢每個fd的狀態,如果某個fd的狀態為就緒,則將此fd加入到等待隊列中並繼續遍歷。如果遍歷完所有的fd後發現沒有就緒的,則掛起當前進程,直到設備就緒或者主動超時。被喚醒後它又要再次遍歷fd。
特點:
1:poll沒有最大連接數限制,因為它是用基於鏈表來存儲的,跟selelct直接監聽fd不一樣。
2:同樣的大量的fd的數組被整體復制與用戶態和內核地址空間之間。
3:poll還有一個特點是水平觸發:如果報告了fd後沒有被處理,則下次poll時還會再次報告該fd。
4:跟select一樣,在poll返回後,還是需要通過遍歷fdset來獲取已經就緒的socket。當fd很多時,效率會線性下降。

三:epoll

epoll支持水平觸發和邊緣觸發,最大的特點在於邊緣觸發,它只告訴進程哪些fd剛剛變為就緒態,並且只會通知一次。還有一個特點是,epoll使用「事件」的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內核就會採用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知。

沒有最大並發連接的限制,能打開的FD的上限遠大於1024(1G的內存上能監聽約10萬個埠)。

效率提升,不是輪詢的方式,不會隨著FD數目的增加效率下降。只有活躍可用的FD才會調用callback函數;即Epoll最大的優點就在於它只管你「活躍」的連接,而跟連接總數無關,因此在實際的網路環境中,Epoll的效率就會遠遠高於select和poll。

內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少復制開銷。

聊聊同步、非同步、阻塞與非阻塞
聊聊Linux 五種IO模型
聊聊IO多路復用之select、poll、epoll詳解

『玖』 Linux的五種IO模型

在linux中,對於一次讀取IO請求(不僅僅是磁碟,還有網路)的操作,數據並不會直接拷貝到用戶程序的用戶空間緩沖區。它首先會被拷貝到操作系統的內核空間,然後才會從操作系統內核的緩沖區拷貝到用戶空間的緩沖區。
大概是這個樣子。

從圖中可以看見,這是分四步進行的,而這四步裡面有些細節,就有了這5種IO模型

前四種為同步IO,後一種為非同步IO,什麼是同步非同步可以看看我之前寫的 同步與非同步,阻塞與非阻塞 。

應用進程發起系統調用後就阻塞了,直到內核buffer拷貝到用戶buffer,發出成功提示後才繼續執行。

適用場景:並發量小的要及時響應的網路應用開發,JavaBIO。
優點:易於開發,不消耗CPU資源(線程阻塞),及時響應。
缺點:不適用與並發量大的網路應用開發,一個請求一個線程,系統開銷大。

應用進程發起系統調用,內核立馬返回一個自己當前的緩沖區的狀態(錯誤或者說成功),假如
為錯誤則隔段時間再系統調用(輪詢),直到返回成功為止。另外再說一點,有人說輪詢之間可以設置一個時間,例如每幾秒執行一次,然後在這段期間程序可以干自己的事情。(這個我不清楚是不是,雖然理論上可以實現,但是我覺得第一種與第二種的區別應該強調的是是否放棄CPU,第二種有點CAS+輪詢這種輕量級鎖的感覺,第一種就是那種重量級鎖的感覺)。

適用場景:並發量小且不用技術響應的網路應用開發
優點:易於開發,可以在輪詢的間斷期間繼續執行程序。
缺點:不適用與並發量大的網路應用開發,一個請求一個線程,系統開銷大。消耗CPU資源(輪詢),不及時響應。

將多個IO注冊到一個復用器上(select,poll,epoll),然後一個進程監視所有注冊進來的IO。
進程阻塞在select上,而不是真正阻塞在IO系統調用上。當其中任意一個注冊的IO的內核緩沖區有了數據,select就會返回(告訴程序內核態緩存有數據了),然後用戶進程再發起調用,數據就從內核態buffer轉到用態buffer(這段期間也是要阻塞的)。

適用場景:並發量大且對響應要求較為高的網路應用開發,JavaNIO
優點:將阻塞從多個進程轉移到了一個select調用身上,假如並發量大的話select調用是不易被阻塞的,或者說阻塞時間短的。
缺點:不易開發,實現難度大,當並發量小的時候還不如同步阻塞模型。

應用程序向內核注冊一個信號處理程序,然後立即返回,當數據准備好了以後(數據到了內核buffer),內核個應用進程一個信號,然後應用進程通過信號處理程序發起系統調用,然後阻塞直達數據從內核buffer復制到用戶buffer。

優點:將阻塞從多個進程轉移到了一個select調用身上,假如並發量大的話select調用是不易被阻塞的,或者說阻塞時間短的。
缺點:不易開發,實現難度大。

以上四個IO模型都可以看出來,到最後用戶進程都要在數據從內核buffer復制到用戶buffer時阻塞,直到內核告訴進程准備成功。這就是同步進程,就是發出一個功能調用時,在沒有得到結果之前,該調用就不返回或繼續執行後續操作。

就是發出一個功能調用時,在沒有得到結果之前,該調用就不返回或繼續執行後續操作

這個就是直到數據完成到用戶buffer才通知。

應用場景:Java AIO,適合高性能高並發應用。
優點:不阻塞,減少了線程切換,
缺點:難以實現,要操作系統支持。

『拾』 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

閱讀全文

與linuxio復用相關的資料

熱點內容
程序員用得到數字區嗎 瀏覽:170
python求商 瀏覽:473
ipad能用c語言編譯器嗎 瀏覽:557
軟泥解壓球最新版 瀏覽:994
4萬程序員辭職創業 瀏覽:755
thinkingphp 瀏覽:593
安卓相冊移動文件夾 瀏覽:2
耳朵清潔解壓聲控99的人都睡得著 瀏覽:201
叉車出租網站源碼 瀏覽:870
共享單車的app是什麼 瀏覽:404
不帶gui的伺服器什麼意思 瀏覽:369
金剛經及PDF 瀏覽:98
php中冒號 瀏覽:354
php5432 瀏覽:348
命令在哪使用 瀏覽:168
php獲取網頁元素 瀏覽:704
為什麼需要硬體驅動編譯 瀏覽:881
pm編程怎樣看導柱孔對不對稱 瀏覽:134
農業大學選課找不到伺服器怎麼辦 瀏覽:645
路由配置網關命令 瀏覽:931