I/O Multiplexing -- Linux I/O 多路複用

文章還會涉及到同步 I/O,異步 I/O,阻塞 I/O 和非阻塞 I/O
首先咱們須要理解如下概念:編程

Linux用戶態和內核態

在如今操做系統中,CPU一般會在兩種不一樣的模式下工做:網絡

內核態

此模式下,程序代碼可以徹底,無限制地訪問底層硬件,可以執行任意的 CPU 指令和訪問任意的內存地址。內核模式一般留給最底層的,受信任的系統函數來使用。程序在內核模式下崩潰是災難性的,這甚至可使整臺 PC 宕機。數據結構

用戶態

在用戶模式下,程序代碼不可以直接訪問硬件和內存。執行在用戶態的代碼必須委託系統函數去訪問硬件和內存。由於有這種隔離機制的保護,程序在用戶態下崩潰一般是可恢復的。PC 中大多數程序也是在用戶態下執行。多線程

進程切換

指操做系統進程調度切換,從某個進程到另外的進程。切換過程須要保存當前進程的全部狀態,包括寄存器狀態,關聯的內核狀態,虛擬內存的配置等,具體會經歷如下幾個步驟:異步

  • 保存處理器上下文,包括程序計數器和其餘寄存器
  • 更新進程控制塊 (PCB)
  • 移動進程的 PCB 到合適的隊列,例如就緒隊列,事件阻塞隊列
  • 選擇其餘進程,並更新他的 PCB
  • 更新內存數據結構
  • 恢復 PCB 上下文

進程阻塞

一個阻塞的進程一般是在等待某個事件,例如信號的釋放或者消息的到達。在多任務的系統中,阻塞的進程會經過系統調用去通知調度器本身處於 wait 的狀態,以便可以被移除出時序隊列。進程若是在 wait 狀態下還霸佔 CPU 繼續執行,這被稱爲 busy-waiting (空等?)。顯然這是不合理的,由於他浪費了 CPU 時鐘週期,這本來能夠被其餘進程使用。因此當一個進程進入了阻塞狀態,不該繼續佔用 CPU 資源。socket

緩衝式 I/O

當咱們寫數據(到文件系統),I/O 系統會累積數據到一箇中間緩衝區,當緩衝區積累到足夠數據時(或者調用flush())纔會把數據發送到文件系統,這樣減小了文件系統的訪問次數。由於對文件系統(磁盤)的訪問一般來講開銷很大(對比內存間的拷貝),緩衝式 I/O 可以有效提升性能,尤爲是那種屢次的小數據量寫操做。如果大數據量的寫操做,非緩衝式 I/O 會更好,由於緩衝式 I/O 並不會顯著減小(對文件系統)系統調用,卻引入的額外的內存拷貝工做,這些數據拷貝操做帶來了更高的 CPU 和內存開銷。函數

文件描述符 (FD)

在 Unix 及其衍生的操做系統中,文件描述符 (FD) 是一個抽象的指示符 (原文:indicator, handle,多數文章翻譯成句柄),用來訪問文件或其餘 I/O 資源,例如管道,socket等。FD 是 POSIX 編程接口的一部分,是個非負索引值,許多底層的程序都會使用到 FD性能

I/O 模型

當一個讀操做發生,會經歷如下兩個階段:大數據

  • 數據準備階段 —— 例如等待網絡數據到達,當數據包到達時,他們會拷貝到內核緩衝區中
  • 數據轉移階段 —— 把數據從內核拷貝到用戶進程

由於這兩個階段的存在,Linux 提供瞭如下5種 I/O 模型spa

Blocking I/O Model —— 阻塞式 I/O

阻塞式 I/O 是最多見的 I/O 模式,默認地,全部的 socket 都是阻塞式的
figure_6.1.png
這裏咱們用 UDP 協議和 recvfrom 系統調用來舉例。上圖中,進程調用了 recvfrom,系統函數在有數據報到達並已經拷貝到應用程序緩衝區時,或者有錯誤發生時纔會返回(最多見的錯誤是被信號中斷)。咱們認爲進程在 recvfrom 從調用到返回的整個階段都被阻塞了。當 recvfrom 成功返回,應用程序纔會去處理數據報。

Nonblocking I/O Model —— 非阻塞式 I/O

若是一個(數據準備階段) I/O 調用沒有完成,內核會當即返回一個錯誤標記,而不是阻塞這個進程
figure_6.2.png
第一次調用 recvfrom 時並無數據到達,因而內核當即返回了錯誤標記 EWOULDBLOCK
第四次調用 recvfrom 時數據報已經到達,拷貝到應用程序緩衝區後,recvfrom 成功返回,以後程序會處理這些數據。
像這樣,程序在一個非阻塞的 FD 上循環調用 recvfrom 被稱爲輪詢。這一般會浪費 CPU 時鐘週期,但這種模型也會偶爾使用到,例如一個系統只專一於某個功能的時候。

I/O Multiplexing Model —— I/O 多路複用

在 Linux I/O 多路複用模型,咱們會阻塞在 select, poll, epoll 這些系統函數中,而不是阻塞在真正的 I/O 調用上。
figure_6.3.png
上圖中,咱們阻塞在 select() 函數上,等待 socket 數據可讀。select() 返回則表示 socket 數據可讀,以後咱們才調用 recvfrom 拷貝數據到應用程序緩衝區

  • 缺點:這裏咱們使用了兩次系統調用 (select 和 recvfrom),而阻塞式 I/O 只使用了一次 recvfrom
  • 優點:咱們能夠監聽多個 FD 是否就緒

I/O 多路複用模型與阻塞式 I/O 模式很是類似。阻塞式 I/O 使用多線程(每一個線程負責一個 FD)且每一個線程均可以很自由地調用(阻塞式)系統函數 recvfrom,而非使用 select 負責監聽多個 FD。

Signal-Driven I/O Model —— 信號驅動 I/O

告訴內核當某個 FD 就緒時,釋放 SIGIO 信號來通知應用程序
figure_6.4.png
咱們首先讓 socket 使用信號驅動 I/O 模式,並使用 sigaction 系統函數註冊一個信號處理器 (signal handler),該系統函數當即返回,這是非阻塞的。

當數據可讀,SIGIO 信號釋放出來被進程接收到,咱們能夠進行以下操做之一

  • 在 signal handler 中調用 recvfrom 讀取數據,隨後通知主循環數據已經準備好了
  • signal handler 通知主循環去讀取數據

該模型的優點在於,等待數據到達的階段不會阻塞,主循環能夠繼續執行其餘任務並等待 signal handler 的通知(數據可讀或可處理)

Asynchronous I/O Model —— 異步 I/O

異步 I/O 模型告訴內核執行 I/O 操做,等到整個 I/O 操做(包括數據準備階段和數據轉移階段)完成後再通知咱們。該模式跟信號驅動 I/O 很是類似,主要的區別是:信號驅動 I/O 中,內核通知進程 I/O 操做能夠開始(仍需把數據從內核拷貝到進程),而異步 I/O 中內核通知咱們 I/O 操做已經完成(數據已經在進程緩衝區中)
figure_6.5.png
咱們調用了系統函數 aio_read,向內核傳遞了如下信息:

  • FD, 緩衝區指針,緩衝區大小
  • 文件偏移量
  • I/O 執行完畢的通知方式

aio_read 會當即返回,進程在等待 I/O 操做完成的整個階段都不會被阻塞。

I/O 模型的比較

figure_6.6.png

前面4種模型的主要區別在第一個階段(數據準備階段),第二階段(數據轉移階段)是同樣的:進程都阻塞在數據轉移階段(從內核拷貝到應用程序緩衝區)。異步 I/O 內核負責這兩個階段,不須要應用程序干預。

同步 I/O 和異步 I/O

POSIX 定義以下

  • 同步 I/O 會致使請求 I/O 操做的進程阻塞,直到 I/O 操做完成
  • 異步 I/O 不會致使請求 I/O 操做的進程阻塞

根據這些定義,前面4種 I/O 模型 (blocking, nonblocking, I/O multiplexing, and signal-driven I/O) 都是同步 I/O,由於實際的 I/O 操做都會阻塞進程(舉例:信號驅動 I/O 在等待數據時非阻塞,但在數據轉移時阻塞了,是同步 I/O),只有異步 I/O 模型符合定義。

select, poll, epoll

施工中

相關文章
相關標籤/搜索