Unix網絡編程讀書筆記-Unix下5種基本I/O模型

Unix下分5種基本的I/O模型:
1.阻塞I/O
2.非阻塞I/O
3.I/O複用(select和poll)
4.信號驅動I/O(SIGIO)
5.異步I/O(POSIX.1的aio_系列函數)
Unix中一個輸入操作一般有兩個不同的階段:
1.等待數據準備好。
2.從內核到進程拷貝數據。
對於一個sockt上的輸入操作,第一步一般是等待數據到達網絡,當分組到達時,它被拷貝到內核中的某個緩衝區,第二步是將數據從內核緩衝區拷貝到應用程序緩衝區。
    下面分別介紹上面提到的5類I/O模型
本文中我們用UDP來進行舉例,並且我們將函數recvfrom視爲系統調用,這樣讓我們的注意力都集中在I/O模型上。
阻塞I/O模型
   
    最流行的I/O模型是阻塞I/O模型,缺省時,所有sockt都是阻塞的,這意味着當一個sockt調用不能立即完成時,進程進入睡眠狀態,等待操作完成。如圖:
 
圖1 阻塞I/O模型
    在圖1中,進程調用recvfrom,此調用直到數據報到達且拷貝到應用緩衝區或是出錯才返回。最常見的錯誤是系統調用被信號中斷,我們所說進程阻塞的整段時間是指從調用recvfrom開始到它返回的這段時間,當進程返回成功指示時,應用進程開始處理數據報。
    下面是一個採用阻塞I/O模型編寫的簡單服務器端代碼(本代碼來至於Unix網絡編程),這段代碼的功能是把客戶端發來的數據再回射到客戶端,本例子中進程阻塞於recvfrom.
#include     "unp.h" 
void   dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen);
int
main(int argc, char **argv)
 {
     int     sockfd;
     struct sockaddr_in servaddr, cliaddr;
     sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
     bzero(&servaddr, sizeof(servaddr));
     servaddr.sin_family = AF_INET;
     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
     servaddr.sin_port = htons(SERV_PORT);
     Bind(sockfd, (SA *) &servaddr, sizeof(servaddr));
     dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
 }
void   dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
  {
      int     n;
      socklen_t len;
      char    mesg[MAXLINE];
      for ( ; ; ) {
         len = clilen;
         n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
         Sendto(sockfd, mesg, n, 0, pcliaddr, len);
     }
 }
非阻塞I/O模型 
      
   當我們把一個sockt設置成非阻塞放式時,即通知內核:當請求的I/O操作非得讓進程睡眠才能完成時,不要讓進程睡眠,而應該返回一個錯誤。如圖:
 
圖2 非阻塞I/O模型
    如圖2所示,前3次調用recvfrom時仍無數據返回,因此內核立即返回一個EWOULDBLOCK錯誤。第4次調用recvfrom時,數據報已經準備好了,被拷貝到應用緩衝區,recvfrom返回成功指示,接着就是我們處理數據報。
    當一個應用進程像這樣對一個非阻塞sockt循環調用recvfrom時,我們稱此過程爲輪詢(polling).應用進程連續不斷的查詢內核,看看某操作是否準備好,這對CPU是極大的浪費,但這種模型只是偶爾才遇到。
I/O複用模型
    I/O複用能讓一個或多個I/O條件滿足(例如,輸入已經準備好被讀,或者描述字可以承接更多的輸出)時,我們就被通知到。I/O複用由select和poll支持,較新的Posix.1g也支持(pselect)。
I/O複用典型地用在下列網絡應用場合:
    1.當客戶處理多個描述字時,必須使用。
    2.一個客戶同時處理多個sockt.
    3.如果一個服務器既要處理監聽sockt,又要處理連接sockt,一般也用到。
   
4.如果一個服務器既要處理TCP,又要處理UDP,一般也用到。
    5.如果一個服務器要處理多個服務或多個協議(例如inetd守護進程),一般也用到。
    I/O複用並非限於網絡編程,許多正是應用程序也需要使用這項技術。
    有了I/O複用,我們就可以調用select或poll,在這兩個系統調用中的某一個上阻塞,而不阻塞於真正的I/O系統調用。圖3是I/O複用模型的一個小結。



圖3 I/O複用模型

    我們阻塞於select調用,等待數據報socket可讀,當select返回socket可讀條件時,我們調用recvfrom將數據報拷貝到應用緩存區中。
    將圖3與圖1比較,似乎沒有顯示什麼優越性,實際上因使用了select,要求2此係統調用而不是一次,好像變的還有點差,但是select的好處在於我們可以等待多個描述字準備好。

信號驅動 I/O模型
   
    信號驅動 I/O模型能在描述字準備好時用信號SIGIO通知我們,下圖給出例子:





圖4 信號驅動 I/O模型
    首先我們允許sockt進行信號驅動 I/O,並通過系統調用sigaction安裝一個信號處理程序。此係統調用立即返回,進程繼續工作,它是非阻塞的。當數據報準備好被讀時,就爲該進程生成個SIGIO信號。我們隨即可以在信號處理程序中調用recvfrom來讀取數據報,並通知主循環數據已準備好被處理,也可以通知主循環,讓它來處理數據報。
    無論我們如何處理SIGIO信號,這種模型的好處是當等待數據報到達時,可以不阻塞。主循環可以繼續執行,只是等待信號處理程序的通知:或者數據報已準備好被處理,或者數據報已準備好被讀取。

異步I/O模型
    異步I/O模型是Posix.1的1993版本中的新內容。我們讓內核啓動操作,並在整個操作完成後(包括將數據報從內核拷貝到我們自己的緩衝區)通知我們。這種模型與信號驅動模型的主要區別在於:信號驅動I/O是有內核通知我們何時可以啓動一個I/O操作,而異步I/O模型是由內核通知我們I/O操作何時完成。圖5給出了一個例子。

 

 
圖5 異步I/O模型
    我們調用aio_red(Posix異步I/O函數以aio_或lio_開頭),給內核傳遞描述字、緩衝區指針、緩衝區大小(與red相同的3個參數)、文件偏移(與lseek類似),並高書內核當整個操作完成時如何通知我們。此係統調用立即返回,我們的進程不阻塞於等待I/O操作的完成。在此例子中,我們假設要求內核在操作完成時產生一個信號,此信號直到數據已拷貝到應用程序緩衝區才產生,這一點是於信號驅動I/O模型不同的。
各種I/O模型的比較



圖6 各類I/O模型的比較

    圖6給出了上述5中I/O模型的比較。它表明:前4種模型的區別都在第1階段,因爲前4種模型的第2階段基本相同:在數據從內核拷貝到調用者的緩衝區時,進程阻塞於recvfrom調用。然而異步I/O處理的兩個階段都不同於前4個模型。
   
    同步I/O與異步I/O

    Posix.1定義這兩個術語如下:
    1.同步I/O操作引起請求進程阻塞,直到I/O操作完成。
    2.異步 I/O操作不引起請求進程阻塞。
    根據上述定義,我們的前四個I/O模型都是同步I/O模型,因爲真正的I/O操作(recvfrom)阻塞進程,只有異步I/O模型與異步I/O的定義相符合。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章