天天看點

深入淺出異步I/O模型

從上篇文章的介紹我們知道Linux核心根據TCP/IP網絡模型,給我們隐藏了傳輸層以下的網絡傳輸細節,我們的網絡應用程式隻需要針對socket程式設計即可。這篇我們立足網絡資料包的I/O。談談linux的一些I/O知識,以及Java的NIO.

 1.  基礎知識

      我們知道Linux的核心将所有外部裝置都可以看做一個檔案來操作。那麼我們對與外部裝置的操作都可以看做對檔案進行操作。我們對一個檔案的讀寫,都通過調用核心提供的系統調用;核心給我們傳回一個file descriptor(簡稱:fd,檔案描述符);我們通過 ls -l  /proc/ pid/fd/可以看到程序 {pid}占用的所有描述符,或者lsof -p ${pid}; 而對一個socket的讀寫也會有相應的描述符,稱為socketfd(socket描述符);描述符就是一個數字,指向核心中一個結構體(檔案路徑,資料區,等一些屬性) ; 那麼我們的應用程式對檔案的讀寫就通過對描述符的讀寫完成。

     系統調用是如何完成一個I/O操作的呢? linux将記憶體分為核心區,使用者區; linux核心給我們管理所有的硬體資源,應用程式通過調用系統調用和核心互動,達到使用硬體資源的目的; 應用程式通過系統調用read發起一個讀操作;這時候核心建立一個檔案描述符,并通過驅動程式向硬體發送讀指令,并将讀的的資料放在這個描述符對應結構體的緩存區。但這個結構體是在核心記憶體區的。需要将這個資料讀到使用者區。這樣完成了一次讀操作;

     但是大家都知道I/O裝置相比cpu的速度是極慢的。linux提供的read系統調用,也是一個阻塞函數。這樣我們的應用程序在發起read系統調用時,就必須阻塞,就程序被挂起而等待檔案描述符的讀就緒;

      這裡,我們先了解一下,什麼是檔案描述符讀就緒,什麼是寫就緒?

       讀就緒:就是這個檔案描述符的接收緩沖區中的資料位元組數大于等于套接字接收緩沖區低水位标記的目前大小;

       寫就緒:該描述符發送緩沖區的可用空間位元組數大于等于描述符發送緩沖區低水位标記的目前大小。(如果是socket fd,說明上一個資料已經發送完成)。

       接收低水位标記和發送低水位标記:由應用程式指定,比如應用程式指定接收低水位為64個位元組。那麼接收緩沖區有64個位元組,才算fd讀就緒;

2.各種I/O模型比較

    有沒有辦法能讓我們在I/O時,不讓我們的應用程式阻塞;從上邊的分析我們知道向核心發起一個I/O操作,要經過等待fd就緒+核心資料到使用者資料區複制,完成一次I/O;

    Linux POSIX是這樣定義同步I/O 和 異步I/O的:

  •    同步I/O操作(synchronous I/O operation):導緻請求程序阻塞,直到I/O操作完成。
  •    異步I/O操作(asynchronous I/O operation): 不導緻請求程序阻塞。

     根據上述定義,我們的前四種模型——阻塞式I/O模型,非阻塞式I/O模型、I/O多路複用模型和信号驅動式I/O模型,因為其中真正的I/O操作将阻塞程序。隻有異步I/O模型與POSIX定義的異步I/O相比對;

深入淺出異步I/O模型

                                  圖: Linux 提供的所有I/O模型

    阻塞式:最普通的I/O模型;原生的read/write系統調用,預設是阻塞模式;導緻程序阻塞;

    非阻塞:這種方式通過指定系統調用read/write的參數為非阻塞,告知核心fd沒就緒時,不阻塞程序,而是傳回一個錯誤碼,應用程序死循環輪詢,直到fd就緒;

    異步非阻塞(I/O複用):linux提供select/poll,程序通過将一個或多個fd傳遞給select或poll系統調用,阻塞在select;這樣select/poll可以幫我們偵測許多fd是否就緒;但是select/poll是順序掃描fd是否就緒,而且支援的fd數量有限。linux還提供了一個epoll系統調用,epoll是基于事件驅動方式,而不是順序掃描,當有fd就緒時,立即回調函數rollback;

    異步非阻塞(信号驅動式I/O):核心在描述符就緒時發送SIGIO信号通知程序,程序通過信号處理函數接收資料;

    異步I/O(AIO):  告知核心某個操作,并讓核心在整個操作(包括将資料複制到我們的程序緩沖區)完成後通知我們。這種模型和信号驅動式I/O模型差別在于:信号驅動式I/O由核心通知我們何時可以啟動一個I/O操作,而異步I/O模型是核心通知我們I/O操作何時完成。(此模型linux 2.6 核心推出)

3.java的NIO(new i/o)

    我們知道jdk 1.4版本裡推出了Java nio .此後java的很多網絡應用都重寫了底層I/O子產品,大大提高并發性能;包括tomcat, jetty等;

     java nio api 裡通過将許多fd扔給一個Selector去檢測fd是否就緒;

     那麼java 的nio使用的是那種I/O模型呢?

     通過檢視jvm代碼:(下載下傳位址:http://download.java.net/jdk6/source/)

    可見jvm的NIO使用的是linux 系統調用epoll模型;

4. java何時支援真正的AIO模型呢?

    JSR 203(http://jcp.org/en/jsr/detail?id=203) 在Java SE 7.0中會完成JSR203.估計不久以後就可以普及了。asynchronous I/O對于java 絕對影響巨大,java寫的網絡伺服器能夠支援更大并發請求了。到時肯定大多網絡伺服器的I/O底層代碼都會修改。就像當時Linux 2.6 支援AIO模型,很多資料庫Oracle,DB2都釋出新版本。