IO多路復用的本質是通過系統內核緩沖IO數據讓單個進程可以監視多個文件描述符,一旦某個進程描述符就緒(讀/寫就緒),就能夠通知程序進行相應的讀寫操作。
select poll epoll都是Linux提供的IO復用方式,它們本質上都是同步IO,因為它們都需要在讀寫事件就緒后自己負責進行讀寫,讀寫的過程是阻塞的。
IO復用技術最大優勢就是系統開銷小,系統不必創建進程或線程,也不必維護這些進程線程。
基礎知識
用戶空間與內核空間:操作系統將虛擬空間劃分為兩個部分,一部分為內核空間,一部分為用戶空間。內核可以訪問受保護的內存空間,也有訪問底層硬件設備的所有權限。
進程切換:為了控制進程執行,內核必須有能力掛起正在CPU執行的進程,然后恢復之前被掛起的進程。進程切換非常消耗資源
進程阻塞:正在執行的進程,由于等待某些事件,如請求資源、等待某些操作完成、等待讀取新數據,這些系統自動執行阻塞原語,使進程變為阻塞態。所以說進程的阻塞是進程自身的主動行為。進入阻塞態是不會占用CPU資源的。
文件描述符:形式上是一個非負整數,實際上是個索引,指向內核為每個進程所維護的該進程打開文件的記錄表。當程序打開一個文件或者創建一個新文件時,內核向進程返回一個文件描述符。
緩存IO:操作系統會將IO的數據緩存在文件系統的頁緩存中,數據會先被拷貝到操作系統內核的緩沖區中,然后才會從內核緩沖區拷貝到應用程序的地址空間。
select運行機制
提供fd_set
的數據結構,實際上為一個long類型的數組,每一個數組元素都能與一個打開的文件句柄建立聯系。當調用select時,內核根據IO狀態修改fd_set的內容,由此來通知執行了select的進程哪一個文件可讀。
從流程上看,select函數與同步阻塞模型無太大區別,甚至多了添加監視socket,以及調用select的額外操作。
但是select以后最大的優勢就是用戶可以在一個線程內同時處理多個socket的Io請求。用戶可以注冊多個socket,然后不斷調用select讀取被激活的socket。達成同一個線程內同時處理多個IO請求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達到這個目的。
select機制的問題
1、每次調用select,都需要把fd_set集合從用戶態拷貝到內核態,集合很大時開銷也大
2、每次調用select都需要在內核中遍歷fd_set,如果集合很大,開銷也大
3、為了減少數據拷貝帶來的性能損失,內核對被監控的fd_set集合大小做限制,限制為1024
Poll
poll機制本質上與select沒啥區別,管理多個描述符也是進行輪詢,根據描述符狀態進行處理,但是poll沒有最大描述符數量限制。
epoll
epoll是基于事件驅動的IO方式。
1、相對于select來說,epoll沒有描述符個數限制,使用一個文件描述符管理多個描述符,將用戶關心的文件描述符的事件存放到內核的一個事件表中,這樣用戶空間和內核空間的copy只需要一次。
2、并且,它在獲取事件的時候,無須遍歷整個被偵聽的描述符集合,只要遍歷哪些被內核IO事件異步喚醒而加入redy隊列的描述符集合就行了。
3、epoll除了提供select/poll那種IO事件的水平觸發,還提供了邊緣觸發,使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。
水平觸發:當epoll_wait檢測到某描述符事件就緒,并且通知應用程序時,應用程序可以不立即處理該事件;下次調用epoll_wait時,會再次通知此事件
邊緣觸發:當epoll_wait檢測到某描述符事件就緒并通知應用程序時,應用程序必須立即處理該事件。如果不處理,下次調用epoll_wait的時候,不會再次通知此事件。
一圖總結: