epoll 或者 kqueue 的原理是什麼?為什麼 epoll 和 kqueue 可以用基於事件的方式,單執行緒的實現併發?我沒看過 linux 核心,對這方面一直有疑問……
必須從很多基礎的概念開始構建這個答案,並且可能引申到很多別的問題。
首先我們來定義流的概念,乙個流可以是檔案,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方式,我們發現我們可以同時處理多個流了(把乙個流從阻塞模式切換到非阻塞模式再此不予討論):
1
while
true
6
}
我們只要不停的把所有流從頭到尾問一遍,又從頭開始。這樣就可以處理多個流了,但這樣的做法顯然不好,因為如果所有的流都沒有資料,那麼只會白白浪費cpu。這裡要補充一點,阻塞模式下,核心對於i/o事件的處理是阻塞或者喚醒,而非阻塞模式下則把i/o事件交給其他物件(後文介紹的 select 以及 epoll)處理甚至直接忽略。
為了避免cpu空轉,可以引進了乙個**(一開始有一位叫做select的**,後來又有一位叫做poll的**,不過兩者的本質是一樣的)。這個**比較厲害,可以同時觀察許多流的i/o事件,在空閒的時候,會把當前執行緒阻塞掉,當有乙個或多個流有i/o事件時,就從阻塞態中醒來,於是我們的程式就會輪詢一遍所有的流(於是我們可以把「忙」字去掉了)。**長這樣:
1
while
true
7
}
於是,如果沒有i/o事件產生,我們的程式就會阻塞在select處。但是依然有個問題,我們從select那裡僅僅知道了,有i/o事件發生了,但卻並不知道是那幾個流(可能有乙個,多個,甚至全部),我們只能無差別輪詢所有流,找出能讀出資料,或者寫入資料的流,對他們進行操作。
但是使用select,我們有o(n)的無差別輪詢複雜度,同時處理的流越多,每一次無差別輪詢時間就越長。
說了這麼多,終於能好好解釋epoll了。
epoll 可以理解為event poll,不同於忙輪詢和無差別輪詢,epoll之會把哪個流發生了怎樣的i/o事件通知我們。此時我們對這些流的操作都是有意義的。(複雜度降低到了o(k),k為產生i/o事件的流的個數,也有認為o(1)的[原文為o(1),但實際上o(k)更為準確])
在討論epoll的實現細節之前,先把epoll的相關操作列出[原文所列第二點說法讓人產生epollin/epollout等同於「緩衝區非空」和「緩衝區非滿」的事件,但並非如此,詳細可以google關於epoll的邊緣觸發和水平觸發。]:
注:當對乙個非阻塞流的讀寫發生緩衝區滿或緩衝區空,write/read會返回-1,並設定errno=eagain。而epoll只關心緩衝區非滿和緩衝區非空事件)
乙個epoll模式的**大概的樣子是:
1
while
true
6
}
限於篇幅,我只說這麼多,以揭示原理性的東西,至於epoll的使用細節,請參考man和google,實現細節,請參閱linux kernel source。
Oracle 基本知識
乙個表空間只能屬於乙個資料庫 每個資料庫最少有乙個控制檔案 建議3個,分別放在不同的磁碟上 每個資料庫最少有乙個表空間 system表空間 建立system表空間的目的是盡量將目的相同的表存放在一起,以提高使用效率,只應存放資料字典 每個資料庫最少有兩個聯機日誌組,每組最少乙個聯機日誌檔案 乙個資料...
Oracle 基本知識
乙個表空間只能屬於乙個資料庫 每個資料庫最少有乙個控制檔案 建議3個,分別放在不同的磁碟上 每個資料庫最少有乙個表空間 system表空間 建立system表空間的目的是盡量將目的相同的表存放在一起,以提高使用效率 每個資料庫最少有兩個聯機日誌檔案 乙個資料檔案只能屬於乙個表空間 乙個資料檔案一旦被...
Oracle 基本知識
oracle 文章摘要 oracle 基本知識。正文 oracle 基本知識 乙個表空間只能屬於乙個資料庫 每個資料庫最少有乙個控制檔案 建議3個,分別放在不同的磁碟上 每個資料庫最少有乙個表空間 system表空間 建立system表空間的目的是盡量將目的相同的表存放在一起,以提高使用效率 每個資...