第一篇:APUE-操做系統IO模型 操做系統IO模型

操做系統IO模型

 

操做系統IO模型

聲明:以下內容是根據APUE和mycat兩本著做中關於I/O模式的一些內容加上本身的一些理解整理而成,僅供學習使用。html

本節內容

  1. UNIX下可用的五種I/O模型
  2. 三種I/O模型
  3. Reactor和Proactor模式

UNIX下可用的五種I/O模型

  1. 阻塞式I/O
  2. 非阻塞式I/O
  3. I/O複用(select和poll)
  4. 信號驅動式I/O(SIGIO)
  5. 異步I/O(POSIX的aio_系列函數)

其中,2,3,4又能夠總結成一類叫作非阻塞同步型,他們的實現方式上有一些差異。linux

下面是5種I/O模型展現:

  1. 阻塞式I/O

默認狀況下,全部套接字都是阻塞式的,進程調用recvfrom以後到數據成功返回以前,這段時間都是阻塞的。 設計模式

2. 非阻塞式I/O緩存

進程在調用recvfrom的時候,若是數據沒有準備好,那麼返回一個EWOULDBLOCK錯誤給進程,這時進程並不進入休眠狀態,而是循環調用recvform直到數據準備完畢以後並返回數據處理成功的消息。當循環調用recvfrom這個動做,咱們稱之爲輪詢(polling),這個過程當中將會大量消耗CPU的時間。在平常使用中這種模型並不常見。多線程

 

3. I/O複用(select和poll)app

進程使用I/O複用的時候,調用select或poll,阻塞在這兩個系統調用的某一個之上,而不是阻塞在真正的I/O系統調用之上。 進程阻塞在select調用之上,直到資源已經準備好時,返回套接字可讀,以後進程才調用recvfrom去獲取數據。這種方式看起來和非阻塞式I/O模型對比並沒什麼優點,可是使用select的優點在於咱們能夠等待多個文件描述符就緒。異步

其實select內部實現機制也就是使用輪詢去檢查多個文件描述符是否準備就緒,只要有一個文件描述符準備就緒,那麼就通知進程去處理該文件。socket

與I/O複用密切相關的另外一種I/O模型是在多線程中使用阻塞式I/O.這種模型與I/O複用模型很類似,但它沒有使用select阻塞在多個文件描述符上,而是使用多個線程(每一個文件描述符一個線程),這樣每一個線程均可以自由的調用諸如recvfrom之類的阻塞式I/O系統調用了。(PS:或許這就是我以前看socketserver源碼中使用select沒理解的地方吧。。。)函數

4. 信號驅動式I/O(SIGIO)post

進程在最開始時使用sigaction發送系統調用,以後當即返回調用結果,這時進程處於非阻塞狀態,去處理其餘事情,當內核在描述符就緒時發送SIGIO信號通知進程,進程再使用recvfrom讀取數據。該模型好處在於等待數據準備好這段時間,進程不會處於阻塞狀態中。 

5. 異步I/O(POSIX的aio_系列函數)

這種模型的工做機制是進程告訴內核要作某個操做,並讓內核在完成整個操做以後通知咱們。這種方式和前面的信號驅動式I/O區別是信號驅動I/O由內核通知咱們什麼時候能夠啓動I/O操做,而異步I/O模型是由內核完成了全部操做以後告訴咱們I/O操做完成了。

在linux中實現這種機制的方式是epoll,在2.6x之後的內核中才支持這種方式。。。

 

五種I/O模型的比較

前面四種I/O模型都在處理真正的I/O時是阻塞模式,只有最後一種是真正的異步I/O模型,和POSIX定義的異步I/O匹配。

三種 IO 類型

系統 I/O 可分爲阻塞型, 非阻塞同步型及非阻塞異步型.

阻塞型 I/O 意味着控制權直接調用操做結束了纔會回到調用者手裏. 若是調用者被阻塞了, 這段時間作不了任何其它事情. 更鬱悶的是,在等待 IO 結果的時間裏,調用者所在線程此時沒法騰出手來去響應其它的請求,這真是太浪費資源了。拿 read()操做來講吧, 調用此函數的代碼會一直僵在此處直至它所讀的 socket 緩存中有數據到來.

相比之下,非阻塞同步是會當即返回控制權給調用者的。調用者不須要等等,它僅調用的函數獲取兩種結果:要麼這次調用成功進行了;要麼系統返回錯誤標識告訴調用者當前資源不可用,你再等等或者再試度看吧。好比 read()操做, 若是當前 socket 無數據可讀,則當即返回 EWOULBLOCK/EAGAIN,告訴調用 read()者「 數據還沒準備好,你稍後再試」 。

在非阻塞異步調用中,稍有不一樣。調用函數在當即返回時,還告訴調用者,此次請求已經開始了。系統會使用另外的資源或者線程來完成此次調用操做,並在完成的時候知會調用者(好比經過回調函數)。拿 Windows的 ReadFile()或者 POSIX 的 aio_read()來講,調用它以後,函數當即返回,操做系統在後臺同時開始讀操做。

在以上三種 IO 形式中,理論上,非阻塞異步是性能最高、伸縮性最好的。

同步和異步是相對於應用和內核的交互方式而言的,同步須要主動去詢問,而異步的時候內核在 IO事件發生的時候通知應用程序,而阻塞與非阻塞僅僅是系統在調用系統調用的時候函數的實現方式而已。

Reactor和Proactor模式

NIO 和 AIO 分別對應的兩種設計模式:Reactor 和 Proactor

通常狀況下,I/O 複用機制須要事件分享器(event demultBossiplexor). 事件分享器的做用,即將那些讀寫事件源分發給各讀寫事件的處理者,就像送快遞的在樓下喊: 誰的什麼東西送了, 快來拿吧。開發人員在開始的時候須要在分享器那裏註冊感興趣的事件,並提供相應的處理者(event handlers),或者是回調涵數; 事件分享器在適當的時候會將請求的事件分發給返讀 handler 或者回調涵數。

涉及到事件分享器的兩種模式稱爲:Reactor和Proactor.

Reactor 模式是基於同步 I/O 的,而 Proactor 模式是和異步 I/O 相關的. 在 Reactor 模式中,事件分離者等待某個事件或者應用或操做的狀態發生(好比文件描述符可讀寫,或者是 socket 可讀寫),事件分離者就把返個事件傳給事兇註冊的事件處理涵數或者回調涵數,由後者來作實際的讀寫操做。

而在 Proactor 模式中,事件處理者(或者代由事件分離者發起)直接發起一個異步讀寫操做(至關於請求),而實際的工做是由操做系統來完成的。發起時,須要提供的參數包括用於存放讀到數據的緩存區,讀的數據大小,或者用於存放外發數據的緩存區,以及這個請求完後的回調涵數等信息。事件分離者得知了這個請求,它默默等待這個請求的完成,而後轉發完成事件給相應的事件處理者或者回調。舉例來該,在 Windows 上事件處理者投遞了一個異步 IO 操做(稱有 overlapped 的技術),事件分離者等 IOCompletion 事件完成. 返種異步模式的典型實現是基於操做系統底層異步 API 的,所仌咱們可稱之爲「 系統級別」 的或者「 真正意義上」 的異步,由於具體的讀寫是由操做系統代勞的。

Reactor 不 Proactor 兩種模式的場景區別:

下面是 Reactor 的作法:

1. 等待事件響應 (Reactor job)

2. 分發 「Ready-to-Read」 事件給用戶句柄 ( Reactor job)

3. 讀數據 (user handler job)

4. 處理數據( user handler job)

下面再來看看真正意義的異步模式 Proactor 是如何作的:

1. 等待事件響應 (Proactor job)

2. 讀數據 (Proactor job)

3. 分發 「Read-Completed」 事件給用戶句柄 (Proactor job)

4. 處理數據(user handler job)

僅上面能夠看出,Reactor 和 Proactor 模式的主要區別就是真正的讀取和寫入操做是由誰來完成的,Reactor 中須要應用程序本身讀取或寫入數據,而 Proactor 模式中,應用程序不須要進行實際的讀寫過程,它只須要從緩存區讀取或者寫入便可,操做系統會讀取緩存區或者寫入緩存區到真正的 IO 設備。

最後結合下面的兩張圖更容易理解:

 

 
分類:  APUE & UNP
相關文章
相關標籤/搜索