linux read() 函數

來源:互聯網
上載者:User

read()函數
read 函數從開啟的裝置或檔案中讀取資料。  
#include <unistd.h>    
ssize_t read(int fd, void *buf, size_t count);  
傳回值:成功返回讀取的位元組數,出錯返回-1並設定errno,如果在調read之前已到達檔案末尾,則這次read返回0
參數count是請求讀取的位元組數,讀上來的資料儲存在緩衝區buf中,同時檔案的當前讀寫位置向後移。注意這個讀寫位置和使用C標準I/O庫時的讀寫位置有可能不同,這個讀寫位置是記在核心中的,而使用C標準I/O庫時的讀寫位置是使用者空間I/O緩衝區中的位置。比如用fgetc讀一個位元組,fgetc有可能從核心中預讀1024個位元組到I/O緩衝區中,再返回第一個位元組,這時該檔案在核心中記錄的讀寫位置是1024,而在FILE結構體中記錄的讀寫位置是1。注意傳回值類型是ssize_t,表示有符號的size_t,這樣既可以返回正的位元組數、0(表示到達檔案末尾)也可以返回負值-1(表示出錯)。read函數返回時,傳回值說明了buf中前多少個位元組是剛讀上來的。有些情況下,實際讀到的位元組數(傳回值)會小於請求讀的位元組數count,例如:讀常規檔案時,在讀到count個位元組之前已到達檔案末尾。例如,距檔案末尾還有30個位元組而請求讀100個位元組,則read返回30,下次read將返回0。

從終端裝置讀,通常以行為單位,讀到分行符號就返回了。

從網路讀,根據不同的傳輸層協議和核心緩衝機制,傳回值可能小於請求的位元組數,後面socket編程部分會詳細講解。

write
函數向開啟的裝置或檔案中寫資料。

#include <unistd.h>   
ssize_t write(int fd, const void *buf, size_t count);  
傳回值:成功返回寫入的位元組數,出錯返回-1並設定errno寫常規檔案時,write的傳回值通常等於請求寫的位元組數count,而向終端裝置或網路寫則不一定。

讀常規檔案是不會阻塞的,不管讀多少位元組,read一定會在有限的時間內返回。從終端裝置或網路讀則不一定,如果從終端輸入的資料沒有分行符號,調用read讀終端裝置就會阻塞,如果網路上沒有接收到資料包,調用read從網路讀就會阻塞,至於會阻塞多長時間也是不確定的,如果一直沒有資料到達就一直阻塞在那裡。同樣,寫常規檔案是不會阻塞的,而向終端裝置或網路寫則不一定。

現在明確一下阻塞(Block)這個概念。當進程調用一個阻塞的系統函數時,該進程被置於睡眠(Sleep)狀態,這時核心調度其它進程運行,直到該進程等待的事件發生了(比如網路上接收到資料包,或者調用sleep指定的睡眠時間到了)它才有可能繼續運行。與睡眠狀態相對的是運行(Running)狀態,在Linux核心中,處於運行狀態的進程分為兩種情況:

正在被調度執行。CPU處於該進程的上下文環境中,程式計數器(eip)裡儲存著該進程的指令地址,通用寄存器裡儲存著該進程運算過程的中間結果,正在執行該進程的指令,正在讀寫該進程的地址空間。

就緒狀態。該進程不需要等待什麼事件發生,隨時都可以執行,但CPU暫時還在執行另一個進程,所以該進程在一個就緒隊列中等待被核心調度。系統中可能同時有多個就緒的進程,那麼該調度誰執行呢?核心的調度演算法是基於優先順序和時間片的,而且會根據每個進程的運行情況動態調整它的優先順序和時間片,讓每個進程都能比較公平地得到機會執行,同時要兼顧使用者體驗,不能讓和使用者互動的進程響應太慢。

下面這個小程式從終端讀資料再寫回終端。

例 28.2. 阻塞讀終端

#include <unistd.h>  
#include <stdlib.h>    
int main(void)  {
   char buf[10];
   int n;
   n = read(STDIN_FILENO, buf, 10);
   if (n < 0) {
    perror("read STDIN_FILENO");
    exit(1);
   }
   write(STDOUT_FILENO, buf, n);
   return 0;  }

執行結果如下:

$ ./a.out   hello(斷行符號)  hello  
$ ./a.out   hello world(斷行符號)  hello 
worl$ d  bash: d: command not found

第一次執行a.out的結果很正常,而第二次執行的過程有點特殊,現在分析一下:

Shell進程建立a.out進程,a.out進程開始執行,而Shell進程睡眠等待a.out進程退出。

a.out調用read時睡眠等待,直到終端裝置輸入了分行符號才從read返回,read唯讀走10個字元,剩下的字元仍然儲存在核心的終端裝置輸入緩衝區中。

a.out
進程列印並退出,這時Shell進程恢複運行,Shell繼續從終端讀取使用者輸入的命令,於是讀走了終端裝置輸入緩衝區中剩下的字元d和分行符號,把它當成一條命令解釋執行,結果發現執行不了,沒有d這個命令。

如果在open一個裝置時指定了O_NONBLOCK標誌,read/write就不會阻塞。以read為例,如果裝置暫時沒有資料可讀就返回-1,同時置errno為EWOULDBLOCK(或者EAGAIN,這兩個宏定義的值相同),表示本來應該阻塞在這裡(would block,虛擬語氣),事實上並沒有阻塞而是直接返回錯誤,調用者應該試著再讀一次(again)。這種行為方式稱為輪詢(Poll),調用者只是查詢一下,而不是阻塞在這裡死等,這樣可以同時監視多個裝置:

while(1) {
非阻塞read(裝置1);
if(裝置1有資料到達)
  處理資料;
非阻塞read(裝置2);
if(裝置2有資料到達)
  處理資料;
...
}

如果
read(裝置1)
是阻塞的,那麼只要裝置1沒有資料到達就會一直阻塞在裝置1的
read
調用上,即使裝置2有資料到達也不能處理,使用非阻塞I/O就可以避免裝置2得不到及時處理。

非阻塞I/O有一個缺點,如果所有裝置都一直沒有資料到達,調用者需要反覆查詢做無用功,如果阻塞在那裡,作業系統可以調度別的進程執行,就不會做無用功了。在使用非阻塞I/O時,通常不會在一個while迴圈中一直不停地查詢(這稱為Tight Loop),而是每延遲等待一會兒來查詢一下,以免做太多無用功,在延遲等待的時候可以調度其它進程執行。

while(1) {
   非阻塞read(裝置1);
   if(裝置1有資料到達)    處理資料;
   非阻塞read(裝置2);
   if(裝置2有資料到達)    處理資料;
   ...   sleep(n);
  }

這樣做的問題是,裝置1有資料到達時可能不能及時處理,最長需延遲n秒才能處理,而且反覆查詢還是做了很多無用功。以後要學習的select(2)函數可以阻塞地同時監視多個裝置,還可以設定阻塞等待的逾時時間,從而圓滿地解決了這個問題。

以下是一個非阻塞I/O的例子。目前我們學過的可能引起阻塞的裝置只有終端,所以我們用終端來做這個實驗。程式開始執行時在0、1、2檔案描述符上自動開啟的檔案就是終端,但是沒有O_NONBLOCK標誌。所以就像例
28.2 “阻塞讀終端”一樣,讀標準輸入是阻塞的。我們可以重新開啟一遍裝置檔案/dev/tty(表示當前終端),在開啟時指定
O_NONBLOCK標誌。

例 28.3. 非阻塞讀終端

#include <unistd.h>  
#include <fcntl.h>  
#include <errno.h>  
#include <string.h>  
#include <stdlib.h>    
#define MSG_TRY "try again\n"    
int main(void)  {
   char buf[10];
   int fd, n;
   fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
   if(fd<0) {
    perror("open /dev/tty");
    exit(1);
   }
  tryagain:
   n = read(fd, buf, 10);
   if (n < 0) {
    if (errno == EAGAIN) {
     sleep(1);
     write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
     goto tryagain;
    }
     perror("read /dev/tty");
    exit(1);
   }
   write(STDOUT_FILENO, buf, n);
   close(fd);
   return 0;
  }

以下是用非阻塞I/O實現等待逾時的例子。既保證了逾時退出的邏輯又保證了有資料到達時處理延遲較小。

例 28.4. 非阻塞讀終端和等待逾時

#include <unistd.h> 
#include <fcntl.h>  
#include <errno.h> 
#include <string.h> 
#include <stdlib.h>   
#define MSG_TRY "try again\n"  
#define MSG_TIMEOUT "timeout\n"    
int main(void)  {
   char buf[10];
   int fd, n, i;
   fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
   if(fd<0) {
    perror("open /dev/tty");
    exit(1);
   } 
  for(i=0; i<5; i++) {
    n = read(fd, buf, 10);
    if(n>=0)     break;
    if(errno!=EAGAIN) {
     perror("read /dev/tty");
     exit(1); 
   } 
   sleep(1);
    write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
   } 
  if(i==5)
     write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
  else 
    write(STDOUT_FILENO, buf, n);
   close(fd);
   return 0;
  }

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.