Linux 下的I/o

Linux的I/O機制經歷了一下幾個階段的演進:
(1)同步阻塞I/O: 用戶進程進行I/O操作,一直阻塞到I/O操作完成爲止。
(2)同步非阻塞I/O: 用戶程序可以通過設置文件描述符的屬性O_NONBLOCK,I/O操作可以立即返回,但是並不保證I/O操作成功。
(3)異步阻塞I/O: 用戶進程可以對I/O事件進行阻塞,但是I/O操作並不阻塞。通過select/poll/epoll等函數調用來達到此目的。
(4)異步非阻塞I/O: 也叫做異步I/O(AIO),用戶程序可以通過向內核發出I/O請求命令,不用等帶I/O事件真正發生,可以繼續做另外的事情,等I/O操作完成,內核會通過函數回調或者信號機制通知用戶進程。這樣很大程度提高了系統吞吐量。

1、    一般典型的I/O(同步阻塞I/O)

它的典型流程如下:


示例代碼:

while ( (n=read(STDIN_FILENO, buf, BUFSIZ) ) > 0)
  
if (write (STDOUT_FILENO, buf, n) != n)
    err_sys (write error ”) ;
從應用程序的角度來說,read 調用可能會延續很長時間。實際上,在內核執行讀操作和其他工作時,應用程序的確會被阻塞,也就是說應用程序不能做其它事情了。


2、   同步 非阻塞I/O
它的典型流程如下:


對於一個給定的描述符有兩種方法對其指定非阻塞I / O:
(1) 如果是調用o p e n以獲得該描述符,則可指定O _ N O N B L O C K標誌。
(2) 對於已經打開的一個描述符,則可調用f c n t l打開O _ N O N B L O C K文件狀態標誌。
對於非阻塞I/O,read發現沒有數據可讀,則簡單的返回-EAGAIN("try it agin"),而不是阻塞當前進程。來看一個非阻塞I/O的例子:

//nbtest.c
#include <stdio.h>
#include 
<unistd.h>
#include 
<fcntl.h>
#include 
<stdlib.h>
#include 
<errno.h>

char buffer[4096];

int main(int argc, char **argv)
{
    
int delay = 1, n, m = 0;

    
if (argc > 1)
        delay
=atoi(argv[1]);
    fcntl(
0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK); /* stdin */
    fcntl(
1, F_SETFL, fcntl(1,F_GETFL) | O_NONBLOCK); /* stdout */

    
while (1) {
        n 
= read(0, buffer, 4096);
        
if (n >= 0)
            m 
= write(1, buffer, n);
        
if ((n < 0 || m < 0&& (errno != EAGAIN))
            
break;
        sleep(delay);
    }
    perror(n 
< 0 ? "stdin" : "stdout");
    exit(
1);
}
我們用strace來跟蹤一下程序執行的結果:

out.txt的內容如下:

可以清楚的看到read讀取失敗的情況。實際上,該方式需要應用程序以一種輪詢的方式來實現數據讀取,多次無謂的系統調用會加大系統開銷,影響應整個系統的吞吐量。

3、,異步阻塞I/O
即UNIX環境下的I/O多路轉接(I/O multiplexing),典型流程如下:



Linux中,poll、epoll和select這三個函數可以用來實現 I/O多路轉接。它們的本質上是相同的:每個允許一個進程來決定它是否可讀或者寫一個或多個文件而不阻塞. 這些調用也可阻塞進程直到任何一個給定集合的文件描述符可用來讀或寫. 因此, 它們常常用在必須使用多輸入輸出流的應用程序。
3.1、poll函數

#include <stropts.h>
#include 
<poll.h>
int poll(struct pollfd  fdarray[],unsigned long  nfds,int timeout) ;
返回:準備就緒的描述符數,若超時則爲 
0,若出錯則爲- 1

struct pollfd {
int fd ; 
/* file descriptor to check, or < 0 to ignore */
short events; 
/* events of interest on fd */
short revents; 
/* events that occurred on fd */
} ;
fdarray數組中的元素數由nfds說明。
應將events成員設置爲如下所示值的一個或幾個。通過這些值告訴內核我們對該描述符關心的是什麼。返回時,內核設置revents成員,以說明對該描述符發生了什麼事件。 (注意,poll沒有更改events成員)。events和revents的取值:


頭四行測試可讀性,接着三行測試可寫性,最後三行則是異常條件。最後三行是
由內核在返回時設置的。即使在 events字段中沒有指定這三個值,如果相應條件發生,則在revents中也返回它們。當一個描述符被掛斷後(POLLUP) ,就不能再寫向該描述符。但是仍可能從該描述符讀取到數據。
poll的最後一個參數說明我們想要等待多少時間。有三種不同的情形:
•  timeout  == -1永遠等待。常數INFTIM定義在<stropts.h>,其值通常是-1。當所指定
的描述符中的一個已準備好,或捕捉到一個信號則返回。如果捕捉到一個信號,則p o l l返回-1,errno設置爲EINTR。
•  timeout == 0   不等待。測試所有描述符並立即返回。這是得到很多個描述符的狀態而不阻塞p o l l函數的輪詢方法。
• timeout > 0   等待timeout毫秒。當指定的描述符之一已準備好,或指定的時間值已超過時立即返回。如果已超時但是還沒有一個描述符準備好,則返回值是 0。 (如果系統不提供毫秒分辨率,則timeout值取整到最近的支持值)。

3.2、例子

#include <stdio.h>
#include 
<unistd.h>
#include 
<stdlib.h>
#include 
<errno.h>
#include 
<sys/poll.h>
#include 
<fcntl.h>

char buffer[4096];

int main(int argc, char **argv)
{
    
struct pollfd pfd;
    
int n;

    fcntl(
0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK); /* stdin */
    pfd.fd 
= 0;  /* stdin */
    pfd.events 
= POLLIN;

    
while (1) {
        n
=read(0, buffer, 4096);
        
if (n >= 0)
            write(
1, buffer, n);
    n 
= poll(&pfd, 1-1);
    
if (n < 0)
        
break;
    }
    perror( n
<0 ? "stdin" : "stdout");
    exit(
1);
}

我們用strace來跟蹤一下程序執行的結果:


out.txt文件:


該方式中,select(或poll)的調用仍然會阻塞進程,與一般典型的I/O不一樣的它是等待事件通知。但是它引入了超時機制,可以讓應用程序有權力避免過長時間等待;另一方面,如果應用程序需要讀寫多個文件,該方式可以一顯身手。典型的應用就是telnet命令(詳細見《UNIX環境高級編程》)。
3、    異步I/O
Linux 異步 I/O (AIO),即異步非阻塞I/O,是 Linux 內核中提供的一個相當新的增強。它是 2.6 版本內核的一個標準特性,但是我們在 2.4 版本內核的補丁中也可以找到它。AIO 背後的基本思想是允許進程發起很多 I/O 操作,而不用阻塞或等待任何操作完成。稍後或在接收到 I/O 操作完成的通知時,進程就可以檢索 I/O 操作的結果。
它的流程如下:

異步I/O 模型是一種處理與 I/O 重疊進行的模型。讀請求會立即返回,說明 read 請求已經成功發起了。在後臺完成讀操作時,應用程序然後會執行其他處理操作。當 read 的響應到達時,就會產生一個信號或執行一個基於線程的回調函數來完成這次 I/O 處理過程。

在一個進程中爲了執行多個 I/O 請求而對計算操作和 I/O 處理進行重疊處理的能力利用了處理速度與 I/O 速度之間的差異。當一個或多個 I/O 請求掛起時,CPU 可以執行其他任務;或者更爲常見的是,在發起其他 I/O 的同時對已經完成的 I/O 進行操作


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章