linux的IO模型

鄭爾多斯發表於2018-12-11

微信公眾號:鄭爾多斯
關注可瞭解更多的Nginx知識。任何問題或建議,請公眾號留言;
關注公眾號,有趣有內涵的文章第一時間送達!

同步/非同步與阻塞/非阻塞的理解

執行緒是程式執行中一個單一的順序控制流程,是程式執行流的最小單元,是處理器排程和分派的基本單位。用執行緒執行程式流的過程去理解同步非同步,阻塞非阻塞。同步非同步關注的是流執行過程需不需要等待外部呼叫的結果,而阻塞非阻塞關注的是外部呼叫對流本身產生的影響。

同步與非同步

執行緒的執行過程中,產生一個外部呼叫,如果需要等待該呼叫返回才能繼續執行緒流則叫做同步,不需要等待結果返回執行緒流可以繼續往下執行的情況叫做非同步。
區分:執行緒流向下執行需不需要等待系統呼叫的結果。

阻塞與非阻塞

執行緒執行過程中,產生一個外部呼叫後,會不會把該執行緒流“堵”住,會“堵”對應的是阻塞,反之為非阻塞。

IO模型

我們先看一下linux中存在的幾種IO模型,如下圖所示:

Linux的IO模型
Linux的IO模型

每個 I/O 模型都有自己的使用模式,它們對於特定的應用程式都有自己的優點。本節將簡要對其一一進行介紹。

同步阻塞IO

最常用的一個模型是同步阻塞 I/O 模型。在這個模型中,使用者空間的應用程式執行一個系統呼叫,這會導致應用程式阻塞。這意味著應用程式會一直阻塞,直到系統呼叫完成為止(資料傳輸完成或發生錯誤)。呼叫應用程式處於一種不再消費 CPU 而只是簡單等待響應的狀態,因此從處理的角度來看,這是非常有效的。
下圖給出了傳統的阻塞 I/O 模型,這也是目前應用程式中最為常用的一種模型。其行為非常容易理解,其用法對於典型的應用程式來說都非常有效。在呼叫 read 系統呼叫時,應用程式會阻塞並對核心進行上下文切換。然後會觸發讀操作,當響應返回時(從我們正在從中讀取的裝置中返回),資料就被移動到使用者空間的緩衝區中。然後應用程式就會解除阻塞(read 呼叫返回)。

同步阻塞IO模型
同步阻塞IO模型

從應用程式的角度來說,read 呼叫會延續很長時間。實際上,在核心執行讀操作和其他工作時,應用程式的確會被阻塞。即在呼叫read()的時候,應用程式處於阻塞狀態,什麼也不能幹。

同步非阻塞 I/O

同步阻塞 I/O 的一種效率稍低的變種是同步非阻塞 I/O。在這種模型中,裝置是以非阻塞的形式開啟的。這意味著 I/O 操作不會立即完成,read操作可能會返回一個錯誤程式碼,說明這個命令不能立即滿足(EAGAIN 或 EWOULDBLOCK),如下圖所示。

同步非阻塞IO模型
同步非阻塞IO模型

非阻塞的實現是 I/O 命令可能並不會立即滿足,需要應用程式呼叫許多次來等待操作完成(簡單地說就是輪詢)。這可能效率不高,因為在很多情況下,當核心執行這個命令時,應用程式必須要進行忙碌等待,直到資料可用為止,或者試圖執行其他工作。正如圖 3 所示的一樣,這個方法可以引入 I/O 操作的延時,因為資料在核心中變為可用到使用者呼叫 read 返回資料之間存在一定的間隔,這會導致整體資料吞吐量的降低。
這種情況雖然呼叫read()的時候會立刻返回資料是否可用的反饋,這樣的話應用程式就不會被阻塞,在多次輪詢的時間間隔中,應用程式可以做一些其他的事情。但是如果資料不可用,我們就需要多次呼叫read();

1while(read(fd) == EAGAIN){
2     //do something ,然後再次呼叫read()進行查詢狀態
3}
複製程式碼

非同步阻塞 I/O

另外一個阻塞解決方案是IO多路複用(複用的select執行緒)。I/O複用模型會用到select、poll、epoll函式,這幾個函式也會使程式阻塞,(這一點要知道,我們呼叫select,poll,epoll的時候,應用程式是阻塞的,很多文章說epoll是非阻塞的,完全是胡扯。)但是和阻塞I/O所不同的的,這兩個函式可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函式進行檢測,直到有資料可讀或可寫時,才真正呼叫I/O操作函式。對於每個提示符來說,我們可以獲取這個描述符可以寫資料、有讀資料可用以及是否發生錯誤的通知。

非同步阻塞I/O模型
非同步阻塞I/O模型

epoll跟select都能提供多路I/O複用的解決方案。在現在的Linux核心裡有都能夠支援,其中epoll是Linux所特有,而select則應該是POSIX所規定,一般作業系統均有實現.
select 呼叫的主要問題是它的效率不是非常高。儘管這是非同步通知使用的一種方便模型,但是對socket進行掃描時是線性掃描,即採用輪詢的方法,效率較低,對於高效能的 I/O 操作來說不建議使用。而且需要維護一個用來存放大量fd的資料結構,這樣會使得使用者空間和核心空間在傳遞該結構時複製開銷大.
poll本質上和select沒有區別,它將使用者傳入的陣列拷貝到核心空間,然後查詢每個fd對應的裝置狀態,如果裝置就緒則在裝置等待佇列中加入一項並繼續遍歷,如果遍歷完所有fd後沒有發現就緒裝置,則掛起當前程式,直到裝置就緒或者主動超時,被喚醒後它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷.它沒有最大連線數的限制,原因是它是基於連結串列來儲存的,但是同樣有一個缺點:大量的fd的陣列被整體複製於使用者態和核心地址空間之間. poll還有一個特點是“水平觸發”,如果報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。
epoll支援水平觸發和邊緣觸發,最大的特點在於邊緣觸發,它只告訴程式哪些fd剛剛變為就需態,並且只會通知一次。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl註冊fd,一旦該fd就緒,核心就會採用類似callback的回撥機制來啟用該fd,epoll_wait便可以收到通知.1. 沒有最大併發連線的限制,能開啟的FD的上限遠大於1024(1G的記憶體上能監聽約10萬個埠)2. 效率提升,不是輪詢的方式,只管你“活躍”的連線,不會隨著FD數目的增加效率下降。只有活躍可用的FD才會呼叫callback函式 3. 記憶體拷貝,利用mmap()檔案對映記憶體加速與核心空間的訊息傳遞;即epoll使用mmap減少複製開銷.
BUT: 表面上看epoll的效能最好,但是在連線數少並且連線都十分活躍的情況下,select和poll的效能可能比epoll好,畢竟epoll的通知機制需要很多函式回撥。

非同步非阻塞 I/O(AIO)

最後,非同步非阻塞 I/O 模型是一種CPU處理與 I/O 重疊進行的模型。讀請求會立即返回,說明 read 請求已經成功發起了。在後臺完成讀操作時,應用程式然後會執行其他處理操作。當 read 的響應到達時,就會產生一個訊號或執行一個基於執行緒的回撥函式來完成這次 I/O 處理過程。
IO操作的兩個步驟,請求和執行。其中請求是查詢裝置的狀態,看一下資料是否準備好,執行指的是資料準備好之後,核心將資料從內和緩衝區複製到應用程式的緩衝區中。
上面介紹的各種情況都是阻塞的,因為當資料準備好之後,應用程式必須自己呼叫read()函式將資料從核心緩衝區複製到應用程式緩衝區,在呼叫read()的過程中,應用程式不能做其他的事情。但是AIO則是非同步非阻塞的,因為在當資料資料準備好之後,核心會自動將資料從核心緩衝區複製到應用程式緩衝區中,在這個過程中,應用程式是無感知的,應用程式可以做其他的事情,當核心已經完成了資料複製的工作,會通知應用程式進行處理。所以AIO是真正的非同步非阻塞。

非同步非阻塞AIO模型
非同步非阻塞AIO模型

在一個程式中為了執行多個 I/O 請求,利用CPU處理速度與 I/O 速度之間的差異, 而對CPU計算操作和 I/O 處理進行重疊處理。當一個或多個 I/O 請求掛起時,CPU 可以執行其他任務;或者更為常見的是,在發起其他 I/O 的同時對已經完成的 I/O 進行操作。


喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達

鄭爾多斯
鄭爾多斯

相關文章