Unix下可用的5種I/O模型分别是:
- 阻塞IO
- 非阻塞IO
- IO複用(select和poll)
- 信号驅動式IO(SIGIO)
- 異步IO(POSIX的aio系列函數)
阻塞式I/O模型:
本例子中,我們吧recvfrom函數視為系統調用,為的是區分應用程序和核心,不論它是如何實作的。在上圖中,程序調用recvfrom,其系統調用直到資料報到達且被複制到應用程序的緩存或者發生錯誤才會傳回。進行在recvfrom開始到它傳回的整段時間内是被阻塞的,recvfrom成功傳回後,應用程序開始處理資料報。
在調用recvfrom到傳回,可以分為兩個過程:
- 核心等待資料
- 把資料從核心複制到使用者空間。
可以看到使用阻塞式IO在進行在上面兩個過程中都處于等待(阻塞)當中。後面講到的其他模型和阻塞IO的差別就在于不同的方式處理上面兩個程序。
非阻塞IO和阻塞IO的最大差別在于等待資料這個過程,在阻塞IO中核心處于等待資料的過程中,程序是處于阻塞狀态的。而在非阻塞IO中不會阻塞,當資料報沒有準備好的時候,會直接傳回一個EWOULDBLOCK錯誤。應用程式像這樣對一個非阻塞描述符循環調用recvfrom時,我們稱之為輪詢,但是持續輪詢核心以檢視某個操作是否就緒,往往浪費大量的cpu時間。
相比阻塞IO,IO複用把等待資料和複制資料到使用者緩存區兩個操作分開做了,需要兩次系統調用。這樣看來IO複用不具有什麼優勢,事實上select的優勢在于我們可以等待多個描述符就緒。(socket資料或者是檔案資料)
JAVA中我們用到的select所對應的IO模型就是IO複用模型。
我們首先開啟套接字的信号驅動式IO功能,并且通過sigaction系統調用安裝一個信号處理函數,該系統調用立刻傳回,我們的進行繼續工作。
當資料準備好之後,核心就為該程序産生一個SIGIO信号,這個時候程序就知道資料準備好了,可以讓核心進行複制資料到使用者空間操作了。
這個模式比起阻塞IO來說,等待資料這步不需要阻塞。
應用程式先進行一次系統排程,告訴核心需要監聽的描述符,緩存區指針,緩存區大小,檔案偏移,該系統排程立馬傳回。待核心把資料準備好,并且複制到使用者空間了,再通知程序,在異步IO中,程序完全不需要阻塞。
上面看到的四種模式,在複制資料到使用者空間這個過程中,程序都需要阻塞的,唯獨異步IO在這個過程中,不需要阻塞。
模型對比
POSIX把兩個屬于定義如下:
同步IO操作:導緻請求程序阻塞,直到IO操作完成。
異步IO操作:不導緻請求阻塞。
可以得出結論,阻塞IO,非阻塞IO,複用IO,信号驅動式IO都為同步IO操作,異步IO模型與POSIX的異步IO操作比對。
select函數
簡單介紹下入參,中間的三個參數readset,writeset,exceptset指定我們要讓核心測試讀,寫和異常條件的描述符。 timeout指定的是逾時時間。maxfdpl是最大描述符數。
那麼什麼情況下核心會認為描述符可讀或者可寫或者異常呢。如下圖:
另外,POSIX定義的pselect函數把時間精度從微妙增加到納秒級,并采用一個指向信号集的指針作為它的一個參數。當有信号需要捕獲時,該參數能夠讓我們避免競争條件。
poll函數提供類似于select的功能,不過能夠為流裝置提供額外的資訊。
以上内容主要是《UNIUX網絡程式設計》關于IO模型章節的一個筆記。