深入淺出IO

我是死肥宅發表於2019-05-14

什麼是IO?

在類unix系統中,所有的IO裝置(網路,磁碟,終端...)都被模型化為檔案,IO是主存(main memory)和外部裝置(磁碟,終端,網路...)之間拷貝資料的過程。

I/O的過程

  • 開啟檔案

應用程式請求核心開啟檔案,核心返回一個檔案的描述符(非負整數),核心記錄開啟的檔案的所有資訊,應用程式只需記錄檔案描述符。

  • 改變當前檔案的位置

核心中記錄一個檔案位置k,每個開啟的檔案初始為0,應用程式可以通過seek操作,顯式的改變檔案的位置。

  • 讀寫檔案

讀:從檔案位置k開始讀取n個位元組到儲存器,然後將k增加到k+n。

寫:儲存器拷貝從k開始,n結束個位元組到一個檔案,然後更新k。

  • 關閉檔案

應用程式請求核心關閉檔案,核心釋放記錄的檔案資訊,將檔案描述符釋放回描述符池。

IO模型

我們想要了解作業系統的IO模型需要先了解幾個基本的概念。

  • 阻塞:請求不能立即得到應答,需要等待。

  • 非阻塞:請求立即得到應答,不需要等待。

  • 同步I/O:同步I/O操作引起請求程式阻塞,直到I/O操作完成。

  • 非同步I/O:非同步I/O操作不引起請求程式阻塞。

舉個?

在?中我們的請求程式化身肥宅,作業系統核心化身外賣老闆。

場景是肥宅週末早上在家餓了,想要吃外賣。

下面是肥宅叫外賣不同的方式。

阻塞:肥宅打了外賣的電話,外賣的老闆正在忙,所以把肥宅掛著,肥宅只能等老闆忙完才能訂外賣。

非阻塞:肥宅打了外賣的電話,不管有沒有東西賣給肥宅,外賣老闆都立即回覆了肥宅。

同步I/O:肥宅打了外賣的電話,外賣老闆告訴肥宅外賣什麼時候送到,肥宅需要自己定時打電話問一下外賣到了沒。

非同步I/O:肥宅打了外賣的電話下了訂單,肥宅就去打遊戲去了。外賣老闆會在指定的時間把外賣送到肥宅家裡,並打電話讓肥宅來取。

五種I/O模型

瞭解了阻塞,非阻塞,非同步與同步,下面我們的五種I/O模型都會用到這些概念。

阻塞I/O模型

阻塞I/O模型

請求程式發起系統呼叫後,一直等待核心資料拷貝完成,整個過程請求程式是阻塞的。

非阻塞I/O模型

非阻塞I/O模型

請求程式發起系統呼叫後,如果核心沒有準備好會返回一個EWOULDBLOCK。然後請求程式會一直輪詢核心,直到核心準備好,開始拷貝資料。

I/O複用模型

I/O複用模型

  • select: 多個的程式的IO可以註冊到一個複用器(select)上,然後用一個程式呼叫該select, select會監聽所有註冊進來的IO。單個程式所能開啟的檔案描述符最大隻能是1024。使用該技術會有C10K併發問題
  • poll: 對select的一種優化,基於連結串列實現,沒有了1024的限制,但是對連結串列的遍歷使效能低下。
  • epoll: 不需要線性遍歷,通過callback進行IO完成後的通知。(由於AIO不成熟,在linux大多使用epoll)
  • kqueue: 與epoll類似,存在FreeBSD中。

訊號驅動I/O模型

訊號驅動I/O模型

當程式發起一個IO操作,會向核心註冊一個訊號處理函式,然後程式返回不阻塞;當核心資料就緒時會傳送一個訊號給程式,程式便在訊號處理函式中呼叫IO讀取資料。

非同步I/O模型

非同步I/O

當程式發起一個IO操作,程式返回(不阻塞),但也不能返回結果;核心把整個IO處理完後,會通知程式結果。如果IO操作成功則程式直接獲取到資料。

I/O模型的比較

I/O模型的比較

同步阻塞IO、同步非阻塞IO、IO多路複用、訊號驅動IO都屬於同步IO。

只有非同步IO屬於非同步IO。

linux非同步I/O有基於執行緒池的,但AIO有缺陷(無法利用系統快取等),使用比較多的是epoll。(AIO是posix標準)

win非同步I/O使用比較多的是IOCP。

非同步I/O執行緒池主流的實現是IO執行緒負責I/O(大部分時間阻塞在I/O上),執行緒間通過訊號量進行通訊。

非同步I/O庫

  • glibc aio: 缺陷是自帶單獨的執行執行緒去執行回撥函式,這對使用者來說很難控制。
  • ACE:ACE 過於複雜,甚至比它試圖封裝的物件更復雜。
  • libevent : 名氣最大,應用最廣泛,歷史悠久的跨平臺事件庫。(缺點:使用全域性變數,定時器無法處理時間跳變等設計缺陷。)
  • libev : 較libevent而言,設計更簡練,效能更好。(缺點:對Windows支援不夠好)
  • libuv : 開發node的過程中需要一個跨平臺的事件庫,他們首選了libev,但又要支援Windows,故重新封裝了一套,*nix下早期用libev實現(現已自己實現),Windows下用IOCP實現;(缺點:沒有正確的處理 TCP 關閉,允許空回撥,使用者不知道出錯在哪裡)。
  • ASIO:在epoll的基礎上用iocp的思想墊了一層,然後封裝為統一介面(缺點:在linux上會損失一部分效率)。

Libevent、libev、libuv三個網路庫,都是c語言實現的非同步事件庫Asynchronousevent library)。

ASIO在2017年進入了c++標準。

node的非同步I/O底層庫使用的是libuv。

參考連結

unix網路程式設計

那些年我們追過的網路庫