文章還會涉及到同步 I/O,異步 I/O,阻塞 I/O 和非阻塞 I/O
首先咱們須要理解如下概念:編程
在如今操做系統中,CPU一般會在兩種不一樣的模式下工做:網絡
此模式下,程序代碼可以徹底,無限制地訪問底層硬件,可以執行任意的 CPU 指令和訪問任意的內存地址。內核模式一般留給最底層的,受信任的系統函數來使用。程序在內核模式下崩潰是災難性的,這甚至可使整臺 PC 宕機。數據結構
在用戶模式下,程序代碼不可以直接訪問硬件和內存。執行在用戶態的代碼必須委託系統函數去訪問硬件和內存。由於有這種隔離機制的保護,程序在用戶態下崩潰一般是可恢復的。PC 中大多數程序也是在用戶態下執行。多線程
指操做系統進程調度切換,從某個進程到另外的進程。切換過程須要保存當前進程的全部狀態,包括寄存器狀態,關聯的內核狀態,虛擬內存的配置等,具體會經歷如下幾個步驟:異步
一個阻塞的進程一般是在等待某個事件,例如信號的釋放或者消息的到達。在多任務的系統中,阻塞的進程會經過系統調用去通知調度器本身處於 wait 的狀態,以便可以被移除出時序隊列。進程若是在 wait 狀態下還霸佔 CPU 繼續執行,這被稱爲 busy-waiting (空等?)。顯然這是不合理的,由於他浪費了 CPU 時鐘週期,這本來能夠被其餘進程使用。因此當一個進程進入了阻塞狀態,不該繼續佔用 CPU 資源。socket
當咱們寫數據(到文件系統),I/O 系統會累積數據到一箇中間緩衝區,當緩衝區積累到足夠數據時(或者調用flush())纔會把數據發送到文件系統,這樣減小了文件系統的訪問次數。由於對文件系統(磁盤)的訪問一般來講開銷很大(對比內存間的拷貝),緩衝式 I/O 可以有效提升性能,尤爲是那種屢次的小數據量寫操做。如果大數據量的寫操做,非緩衝式 I/O 會更好,由於緩衝式 I/O 並不會顯著減小(對文件系統)系統調用,卻引入的額外的內存拷貝工做,這些數據拷貝操做帶來了更高的 CPU 和內存開銷。函數
在 Unix 及其衍生的操做系統中,文件描述符 (FD) 是一個抽象的指示符 (原文:indicator, handle,多數文章翻譯成句柄),用來訪問文件或其餘 I/O 資源,例如管道,socket等。FD 是 POSIX 編程接口的一部分,是個非負索引值,許多底層的程序都會使用到 FD性能
當一個讀操做發生,會經歷如下兩個階段:大數據
由於這兩個階段的存在,Linux 提供瞭如下5種 I/O 模型spa
阻塞式 I/O 是最多見的 I/O 模式,默認地,全部的 socket 都是阻塞式的
這裏咱們用 UDP 協議和 recvfrom 系統調用來舉例。上圖中,進程調用了 recvfrom,系統函數在有數據報到達並已經拷貝到應用程序緩衝區時,或者有錯誤發生時纔會返回(最多見的錯誤是被信號中斷)。咱們認爲進程在 recvfrom 從調用到返回的整個階段都被阻塞了。當 recvfrom 成功返回,應用程序纔會去處理數據報。
若是一個(數據準備階段) I/O 調用沒有完成,內核會當即返回一個錯誤標記,而不是阻塞這個進程
第一次調用 recvfrom 時並無數據到達,因而內核當即返回了錯誤標記 EWOULDBLOCK
第四次調用 recvfrom 時數據報已經到達,拷貝到應用程序緩衝區後,recvfrom 成功返回,以後程序會處理這些數據。
像這樣,程序在一個非阻塞的 FD 上循環調用 recvfrom 被稱爲輪詢。這一般會浪費 CPU 時鐘週期,但這種模型也會偶爾使用到,例如一個系統只專一於某個功能的時候。
在 Linux I/O 多路複用模型,咱們會阻塞在 select, poll, epoll 這些系統函數中,而不是阻塞在真正的 I/O 調用上。
上圖中,咱們阻塞在 select() 函數上,等待 socket 數據可讀。select() 返回則表示 socket 數據可讀,以後咱們才調用 recvfrom 拷貝數據到應用程序緩衝區
I/O 多路複用模型與阻塞式 I/O 模式很是類似。阻塞式 I/O 使用多線程(每一個線程負責一個 FD)且每一個線程均可以很自由地調用(阻塞式)系統函數 recvfrom,而非使用 select 負責監聽多個 FD。
告訴內核當某個 FD 就緒時,釋放 SIGIO 信號來通知應用程序
咱們首先讓 socket 使用信號驅動 I/O 模式,並使用 sigaction 系統函數註冊一個信號處理器 (signal handler),該系統函數當即返回,這是非阻塞的。
當數據可讀,SIGIO 信號釋放出來被進程接收到,咱們能夠進行以下操做之一
該模型的優點在於,等待數據到達的階段不會阻塞,主循環能夠繼續執行其餘任務並等待 signal handler 的通知(數據可讀或可處理)
異步 I/O 模型告訴內核執行 I/O 操做,等到整個 I/O 操做(包括數據準備階段和數據轉移階段)完成後再通知咱們。該模式跟信號驅動 I/O 很是類似,主要的區別是:信號驅動 I/O 中,內核通知進程 I/O 操做能夠開始(仍需把數據從內核拷貝到進程),而異步 I/O 中內核通知咱們 I/O 操做已經完成(數據已經在進程緩衝區中)
咱們調用了系統函數 aio_read,向內核傳遞了如下信息:
aio_read 會當即返回,進程在等待 I/O 操做完成的整個階段都不會被阻塞。
前面4種模型的主要區別在第一個階段(數據準備階段),第二階段(數據轉移階段)是同樣的:進程都阻塞在數據轉移階段(從內核拷貝到應用程序緩衝區)。異步 I/O 內核負責這兩個階段,不須要應用程序干預。
POSIX 定義以下
根據這些定義,前面4種 I/O 模型 (blocking, nonblocking, I/O multiplexing, and signal-driven I/O) 都是同步 I/O,由於實際的 I/O 操做都會阻塞進程(舉例:信號驅動 I/O 在等待數據時非阻塞,但在數據轉移時阻塞了,是同步 I/O),只有異步 I/O 模型符合定義。
施工中