Linux I O多路復用和epoll

2022-02-17 14:42:55 字數 2761 閱讀 6209

首先我們來定義流的概念,乙個流可以是檔案,socket,pipe等等可以進行i/o操作的核心物件。

不管是檔案,還是套接字,還是管道,我們都可以把他們看作流。

之後我們來討論i/o的操作,通過read,我們可以從流中讀入資料;通過write,我們可以往流寫入資料。現在假定乙個情形,我們需要從流中讀資料, 但是流中還沒有資料,(典型的例子為,客戶端要從socket讀如資料,但是伺服器還沒有把資料傳回來),這時候該怎麼辦?

阻塞:阻塞是個什麼概念呢?比如某個時候你在等快遞,但是你不知道快遞什麼時候過來,而且你沒有別的事可以幹(或者說接下來的事要等快遞來了才能做);那麼你可以去睡覺了,因為你知道快遞把貨送來時一定會給你打個**(假定一定能叫醒你)。

很明顯一般人不會用第二種做法,不僅顯很無腦,浪費話費不說,還占用了快遞員大量的時間。

大部分程式也不會用第二種做法,因為第一種方法經濟而簡單,經濟是指消耗很少的cpu時間,如果執行緒睡眠了,就掉出了系統的排程佇列,暫時不會去瓜分cpu寶貴的時間片了。

為了了解阻塞是如何進行的,我們來討論緩衝區,以及核心緩衝區,最終把i/o事件解釋清楚。緩衝區的引入是為了減少頻繁i/o操作而引起頻繁的系統呼叫 (你知道它很慢的),當你操作乙個流時,更多的是以緩衝區為單位進行操作,這是相對於使用者空間而言。對於核心來說,也需要緩衝區。

假設有乙個管道,程序a為管道的寫入方,b為管道的讀出方。

假設一開始核心緩衝區是空的,b作為讀出方,被阻塞著。然後首先a往管道寫入,這時候核心緩衝區由空的狀態變到非空狀態,核心就會產生乙個事件告訴b該醒來了,這個事件姑且稱之為「緩衝區非空」。

但是「緩衝區非空」事件通知b後,b卻還沒有讀出資料;且核心許諾了不能把寫入管道中的資料丟掉這個時候,a寫入的資料會滯留在核心緩衝區中,如果核心也 緩衝區滿了,b仍未開始讀資料,最終核心緩衝區會被填滿,這個時候會產生乙個i/o事件,告訴程序a,你該等等(阻塞)了,我們把這個事件定義為「緩衝區 滿」。

假設後來b終於開始讀資料了,於是核心的緩衝區空了出來,這時候核心會告訴a,核心緩衝區有空位了,你可以從長眠中醒來了,繼續寫資料了,我們把這個事件叫做「緩衝區非滿」

也許事件y1已經通知了a,但是a也沒有資料寫入了,而b繼續讀出資料,知道核心緩衝區空了。這個時候核心就告訴b,你需要阻塞了!,我們把這個時間定為「緩衝區空」。

這四個情形涵蓋了四個i/o事件,緩衝區滿,緩衝區空,緩衝區非空,緩衝區非滿(注都是說的核心緩衝區,且這四個術語都是我生造的,僅為解釋其原理 而造)。這四個i/o事件是進行阻塞同步的根本。(如果不能理解「同步」是什麼概念,請學習作業系統的鎖,訊號量,條件變數等任務同步方面的相關知識)。

然後我們來說說阻塞i/o的缺點。但是阻塞i/o模式下,乙個執行緒只能處理乙個流的i/o事件。如果想要同時處理多個流,要麼多程序(fork),要麼多執行緒(pthread_create),很不幸這兩種方法效率都不高。

於是再來考慮非阻塞忙輪詢的i/o方式,我們發現我們可以同時處理多個流了(把乙個流從阻塞模式切換到非阻塞模式再此不予討論):

while true 

}

我們只要不停的把所有流從頭到尾問一遍,又從頭開始。這樣就可以處理多個流了,但這樣的做法顯然不好,因為如果所有的流都沒有資料,那麼只會白白浪 費cpu。這裡要補充一點,阻塞模式下,核心對於i/o事件的處理是阻塞或者喚醒,而非阻塞模式下則把i/o事件交給其他物件(後文介紹的select以 及epoll)處理甚至直接忽略。

為了避免cpu空轉,可以引進了乙個**(一開始有一位叫做select的**,後來又有一位叫做poll的**,不過兩者的本質是一樣的)。這個**比 較厲害,可以同時觀察許多流的i/o事件,在空閒的時候,會把當前執行緒阻塞掉,當有乙個或多個流有i/o事件時,就從阻塞態中醒來,於是我們的程式就會輪 詢一遍所有的流(於是我們可以把「忙」字去掉了)。**長這樣:

while true 

}

於是,如果沒有i/o事件產生,我們的程式就會阻塞在select處。但是依然有個問題,我們從select那裡僅僅知道了,有i/o事件發生了, 但卻並不知道是那幾個流(可能有乙個,多個,甚至全部),我們只能無差別輪詢所有流,找出能讀出資料,或者寫入資料的流,對他們進行操作。

但是使用select,我們有o(n)的無差別輪詢複雜度,同時處理的流越多,沒一次無差別輪詢時間就越長。再次

說了這麼多,終於能好好解釋epoll了

epoll可以理解為event poll,不同於忙輪詢和無差別輪詢,epoll之會把哪個流發生了怎樣的i/o事件通知我們。此時我們對這些流的操作都是有意義的。(複雜度降低到了o(1))

epoll_create 建立乙個epoll物件,一般epollfd = epoll_create()

epoll_ctl (epoll_add/epoll_del的合體),往epoll物件中增加/刪除某乙個流的某乙個事件

比如epoll_ctl(epollfd, epoll_ctl_add, socket, epollin);//註冊緩衝區非空事件,即有資料流入

epoll_ctl(epollfd, epoll_ctl_del, socket, epollout);//註冊緩衝區非滿事件,即流可以被寫入

epoll_wait(epollfd,...)等待直到註冊的事件發生

(注:當對乙個非阻塞流的讀寫發生緩衝區滿或緩衝區空,write/read會返回-1,並設定errno=eagain。而epoll只關心緩衝區非滿和緩衝區非空事件)。

乙個epoll模式的**大概的樣子是:

while true 

}

限於篇幅,我只說這麼多,以揭示原理性的東西,至於epoll的使用細節,請參考man和google,實現細節,請參閱linux kernel source。

Linux IO多路復用

一.select 函式 include include include int select int n,fd set readfds,fd set writefds,fd set exceptfds,struct timeval timeout fd clr int fd,fd set set f...

IO多路復用 select poll和epoll

select include int select int maxfdp1,fd set restrict readfds,fd set restrict writefds,fd set restrict exceptfds,struct timeval restrict tvptr 引數描述 se...

Linux IO多路復用 多路轉接

io多路復用 多路轉接 在沒有io多路復用技術前,程序在同一時間只能監控乙個io狀態 乙個檔案描述符的狀態 例如a和b兩個檔案在操作,定的順序是a前b後,那麼在a阻塞過程中 b在後面也是不能被操作的,這樣會造成資源浪費 響應緩慢。io多路復用 同時監控多個io檔案描述符狀態,如果沒有準備好的描述符 ...