什麼是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模型
請求程式發起系統呼叫後,如果核心沒有準備好會返回一個EWOULDBLOCK。然後請求程式會一直輪詢核心,直到核心準備好,開始拷貝資料。
I/O複用模型
- select: 多個的程式的IO可以註冊到一個複用器(select)上,然後用一個程式呼叫該select, select會監聽所有註冊進來的IO。單個程式所能開啟的檔案描述符最大隻能是1024。使用該技術會有C10K併發問題
- poll: 對select的一種優化,基於連結串列實現,沒有了1024的限制,但是對連結串列的遍歷使效能低下。
- epoll: 不需要線性遍歷,通過callback進行IO完成後的通知。(由於AIO不成熟,在linux大多使用epoll)
- kqueue: 與epoll類似,存在FreeBSD中。
訊號驅動I/O模型
當程式發起一個IO操作,會向核心註冊一個訊號處理函式,然後程式返回不阻塞;當核心資料就緒時會傳送一個訊號給程式,程式便在訊號處理函式中呼叫IO讀取資料。
非同步I/O模型
當程式發起一個IO操作,程式返回(不阻塞),但也不能返回結果;核心把整個IO處理完後,會通知程式結果。如果IO操作成功則程式直接獲取到資料。
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。