linux開發各種I/O操作簡析,以及select、poll、epoll機制的對比

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:良知猶存","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉載授權以及圍觀:歡迎添加微信公衆號:羽林君","attrs":{}}]},{"type":"horizontalrule","attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"IO 概念區分","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"四個相關概念:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同步(Synchronous)","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"異步( Asynchronous)","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阻塞( Blocking )","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"非阻塞( Nonblocking)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阻塞I/O","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 阻塞,就是調用我(函數),我(函數)沒有接收完數據或者沒有得到結果之前,我不會返回。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在linux中,默認情況下所有的socket都是阻塞的,一個典型的讀操作流程大概是這樣:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cb/cb67e93c90c117c567f8f8d1c6ca343d.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當用戶進程調用了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":" read()/recvfrom()","attrs":{}},{"type":"text","text":" 等系統調用函數,它會進入內核空間中,當這個網絡I/O沒有數據的時候,內核就要等待數據的到來,而在用戶進程這邊,整個進程會被阻塞,直到內核空間返回數據。當內核空間的數據準備好了,它就會將數據從內核空間中拷貝到用戶空間,此時用戶進程才解除阻塞的的狀態,重新運行起來。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,阻塞I/O的特點就是在IO執行的兩個階段(用戶空間與內核空間)都被阻塞了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"非阻塞I/O","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    非阻塞,就是調用我(函數),我(函數)立即返回。阻塞調用是指調用結果返回之前,當前線程會被掛起(線程進入非可執行狀態,在這個狀態下,cpu不會給線程分配時間片,即線程暫停運行)。函數只有在得到結果之後纔會返回。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"有人也許會把阻塞調用和同步調用等同起來","attrs":{}},{"type":"text","text":",實際上他是不同的。對於同步調用來說,很多時候當前線程還是激活的,只是從邏輯上當前函數沒有返回,它還會搶佔CPU去執行其他邏輯,也會主動檢測I/O是否準備好。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"執行的模型如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8c/8ccf2befb6fc10a86d1240bd67efb126.jpeg","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"能看到,非阻塞I/O的特點是用戶進程需要不斷的 主動詢問 內核空間的數據準備好了沒有。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同步I/O","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    在操作系統中,程序運行的空間分爲內核空間和用戶空間,用戶空間所有對io操作的代碼(如文件的讀寫、socket的收發等)都會通過系統調用進入內核空間完成實際的操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    而且我們都知道CPU的速度遠遠快於硬盤、網絡等I/O。在一個線程中,CPU執行代碼的速度極快,然而,一旦遇到I/O操作,如讀寫文件、發送網絡數據時,就需要等待 I/O 操作完成,才能繼續進行下一步操作,這種情況稱爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"同步 I/O","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"其實所謂同步,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不返回。也就是必須一件一件事做,等前一件做完了才能做下一件事。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    實際工作中我們卻很少使用同步I/O,因爲當你讀寫某個文件,進行I/O操作時候,如果數據沒有及時迴應到,那麼系統就會將當前執行讀寫的線程掛起來等待數據的讀取完成,而其他需要CPU執行的代碼就無法被當前線程執行,這就是同步I/O的弊端。僅僅因爲一個I/O操作就會阻塞當前線程,導致其他代碼無法執行,當然我們遇到這樣時候會選擇用多線程或者多進程來併發執行代碼。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"       但是多線程和多進程也無法根除這種阻塞問題,因爲系統內存大小的限制,所以系統不能無限的增加線程和進程。此外過多的線程和進程,就會導致系統切換線程和進程的開銷變大,真正運行代碼時間就會變少,這樣子系統性能也會嚴重下降。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"異步I/O","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    簡單來說就是,用戶不需要等待內核完成實際對io的讀寫操作就直接返回了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    當一個異步過程調用發出後,調用者不能立刻得到結果。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"實際處理這個調用的部件在完成後,通過狀態、通知和回調來通知調用者","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"I/O過程主要分兩個階段:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.數據準備階段","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.內核空間複製回用戶進程緩衝區空間","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"無論","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"阻塞式IO","attrs":{}},{"type":"text","text":"還是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"非阻塞式IO","attrs":{}},{"type":"text","text":",都是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"同步IO模型","attrs":{}},{"type":"text","text":",區別就在與","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"第一步是否完成後才返回","attrs":{}},{"type":"text","text":",但第二步都需要當前進程去完成,異步IO呢,就是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"從第一步開始就返回","attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"直到第二步完成後纔會返回一個消息","attrs":{}},{"type":"text","text":",也就是說,異步能夠讓你在第一步時去做其它的事情。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/65/6552780590df4634bc5db299d5caae5a.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同步IO和異步IO的區別就在於:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"數據拷貝的時候進程是否阻塞","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阻塞IO和非阻塞IO的區別就在於:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"應用程序的調用是否立即返回","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲異步IO把IO的操作給了內核,讓內核去操作,同步IO的話,需要等待IO操作從內核態的數據緩衝區拷貝到用戶態的數據緩衝區,所以此時的同步IO是阻塞的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多路複用I/O","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    多路複用I/O就是我們說的 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"select,poll,epoll","attrs":{}},{"type":"text","text":" 等操作,複用的好處就在於 單個進程 就可以同時處理 多個 網絡連接的I/O,能實現這種功能的原理就是 select、poll、epoll 等函數會不斷的 輪詢 它們所負責的所有 socket ,當某個 socket 有數據到達了,就通知用戶進程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般在Linux下我們會有以下幾種的字符設備讀寫方式,下面是一個使用的對比:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、查詢方法:一直在查詢,不斷去查詢是否有事件發生,整個過程都是佔用CPU資源,非常消耗CPU資源。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、中斷方式:當有事件發生時,就去跳轉到相應事件去處理,CPU佔用時間少。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、poll方式: 中斷方式雖然佔用CPU資源少,但是在應用程序上需要不斷在死循環裏面執行讀取函數,應用程序不能去做其它事情。poll機制解決了這個問題,當有事件發生時,纔去執行讀read函數,按鍵事件沒有按下時,去執行其它的處理函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    這裏我們能夠看到poll使用的優勢,select,poll,epoll都是IO多路複用的機制。I/O多路複用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因爲他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    我們再說一下","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"select,poll和epoll這幾個IO複用方式","attrs":{}},{"type":"text","text":",這時你就會了解它們爲什麼是同步IO了,以epoll爲例,在epoll開發的服務器模型中,epoll_wait()這個函數會阻塞等待就緒的fd,將就緒的fd拷貝到epoll_events集合這個過程中也不能做其它事(雖然這段時間很短,所以epoll配合非阻塞IO是很高效也是很普遍的服務器開發模式--同步非阻塞IO模型)。有人把epoll這種方式叫做同步非阻塞(NIO),因爲用戶線程需要不停地輪詢,自己讀取數據,看上去好像只有一個線程在做事情,也有人把這種方式叫做異步非阻塞(AIO),因爲畢竟是內核線程負責掃描fd列表,並填充事件鏈表的,個人認爲真正理想的異步非阻塞,應該是內核線程填充事件鏈表後,主動通知用戶線程,或者調用應用程序事先註冊的回調函數來處理數據,如果還需要用戶線程不停的輪詢來獲取事件信息,就不是太完美了,所以也有不少人認爲epoll是僞AIO,還是有道理的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"select","attrs":{}},{"type":"text","text":"函數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"  該函數准許進程指示內核等待多個事件中的任何一個發送,並只在有一個或多個事件發生或經歷一段指定的時間後才喚醒。select的調用過程如下所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/47/47fccaa65cd6af479a76aa1b056c6c9d.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"select的幾大缺點:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"(1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"(3)select支持的文件描述符數量太小了,默認是1024","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"    poll","attrs":{}},{"type":"text","text":"的機制與","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"select","attrs":{}},{"type":"text","text":"類似,與select在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是poll沒有最大文件描述符數量的限制。poll和select同樣存在一個缺點就是,包含大量文件描述符的數組被整體複製於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增加而線性增大。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"    epoll","attrs":{}},{"type":"text","text":"是在2.6內核中提出的,是之前的select和poll的增強版本。相對於select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關係的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"epoll","attrs":{}},{"type":"text","text":"既然是對","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"select","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"poll","attrs":{}},{"type":"text","text":"的改進,就應該能避免上述的三個缺點。那epoll都是怎麼解決的呢?在此之前,我們先看一下epoll和select和poll的調用接口上的不同, select和poll都只提供了一個函數select或者poll函數。而epoll提供了三個函數, epoll create,epoll cti和epoll wait , epoll create是創建一個epol句柄 ; epoll ctl是註冊要監聽的事件類型; epoll wait則是等待事件的產生。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    對於第一-個缺點, epoll的解決方案在epoll ctl函數中。每次註冊新的事件到epoll句柄中時(在epoll ctI中指定EPOLL CTL ADD) ,會把所有的fd拷貝進內核,而不是在epoll wait的時候重複拷貝。epoll保證 了每個fd在整個過程中只會拷貝一次。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    對於第二個缺點, epoll的解決方案不像select或poll- -樣每次都把current輪流加入fd對應的設備等待隊列中,而只在epoll ctl時把current掛一遍(這一遍必不可少)併爲每個fd指定一-個回調函數 ,當設備就緒,喚醒等待隊列上的等待者時,就會調用這個回調函數,而這個回調函數會把就緒的fd加入-一個就緒鏈表)。epoll wait的工作實際上就是在這個就緒鏈表中查看有沒有就緒的fd (利用schedule_ timeout0實現睡一會,判斷一會的效果 ,和select實現中的第7步是類似的)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"    對於第三個缺點, epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數目, 這個數字-般遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max查看,一般來說這個數目和系統內存關係很大。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"總結:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1 、 select ,poll實現需要自 己不斷輪詢所有fd集合,直到設備就緒 ,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用epoll wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,並喚醒在epoll wait中進入睡眠的進程。雖然都要睡眠和交替,但是select和poll在“醒着 ”的時候要遍歷整個fd集合,而epoll在“醒着”的時候只要判斷一下就緒 鏈表是否爲空就行了,這節省 了的CPU時間。這就是回調機制帶來的性能提升。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2 、 select , poll每次調用都要把fd集合從用戶態往內核態拷貝一-次,並且要把current往設備等待隊列中掛一次,而epoll只要一次拷貝,而且把current往等待隊列上掛也只掛一次(在epoll wait的開始,注意這裏的等待隊列並不是設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省不少的開銷。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在選擇select,poll,epoll時要根據具體的使用場合以及這三種方式的自身特點。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1、表面上看epoll的性能最好,但是在連接數少並且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數回調。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2、select低效是因爲每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就是我分享的select,poll,epoll,其中參考了很多人的文章,如果大家有什麼更好的思路,也歡迎分享交流哈。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"—","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"END","attrs":{}},{"type":"text","text":"—","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"推薦閱讀","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【1】","attrs":{}},{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzI0MTI2MDY3NQ%3D%3D&chksm=e90cea79de7b636f928c2f601d0bf1d9cdf674fc60a1d8ec2e21574ea1bf1e0d5add48e1f071&idx=1&mid=2247494549&scene=21&sn=eb59e1b663e602c6f1e9551456b19788#wechat_redirect","title":null},"content":[{"type":"text","text":"C++的智能指針你瞭解嗎?","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【2】","attrs":{}},{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzI0MTI2MDY3NQ%3D%3D&chksm=e90f0077de788961258f46eed6149498f6f7c907b8da4a2090ada295158bec3b46c2a9b628a6&idx=1&mid=2247483803&scene=21&sn=1cbc72c1985965c052c374eb627d4446#wechat_redirect","title":null},"content":[{"type":"text","text":"嵌入式底層開發的軟件框架簡述","attrs":{}}]},{"type":"text","text":" ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【3】","attrs":{}},{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzI0MTI2MDY3NQ%3D%3D&chksm=e90f1973de789065101b678830244790e06a19318162765327e6d0103a01b7d2a11827643956&idx=1&mid=2247489695&scene=21&sn=d7dd58878070d34a6ee3a21be035e6e1#wechat_redirect","title":null},"content":[{"type":"text","text":"CPU中的程序是怎麼運行起來的","attrs":{}}]},{"type":"text","text":" ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"必讀","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【4】","attrs":{}},{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzI0MTI2MDY3NQ%3D%3D&chksm=e90f1a65de78937331e9beb61f463d332c705b086b763d04f027bcda2c68b655c34d1c66f9e6&idx=1&mid=2247490441&scene=21&sn=3be088b25663c72ba43d3f42f916acf5#wechat_redirect","title":null},"content":[{"type":"text","text":"C++的匿名函數(lambda表達式)","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【5】","attrs":{}},{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzI0MTI2MDY3NQ%3D%3D&chksm=e90f1cdbde7895cdcdf789e7056a6295cbdffc05cd1272f78450d5cbbdf62bc3edee995d3e92&idx=1&mid=2247490871&scene=21&sn=c0fb6e8303302247b3a08c4b16acc4bd#wechat_redirect","title":null},"content":[{"type":"text","text":"階段性文章總結分析","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本公衆號全部原創乾貨已整理成一個目錄,回覆[ 資源 ]即可獲得","attrs":{}},{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzUxMjEyNDgyNw%3D%3D&chksm=f9687d2cce1ff43a639db6e4b6c99df002f99d51f43dfeb6e4ffa966e399780b35194db67191&idx=2&mid=2247487448&scene=21&sn=7c84dad5421841d4bb4e0fa74ae067b6#wechat_redirect","title":null},"content":[{"type":"text","text":"。","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/27/27cc21ae87cd8cb2b284f34579495cc3.jpeg","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"更多分享,掃碼關注我","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"參考鏈接:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://blog.csdn.net/Crazy_Tengt/article/details/79225913","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://tutorial.linux.doc.embedfire.com/zh_CN/latest/system_programing/socket_io.html","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://www.zhihu.com/question/19732473","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章