1. 程式人生 > >read函式---------詳解

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;
  }
轉自:http://blog.csdn.net/zjhkobe/article/details/6633446