1. 程式人生 > >第三章 文件 I/O

第三章 文件 I/O

符號 current 改變 延遲 比較 修改 esc 第5章 命令

3.1 引言

先說明可用的文件 I/O 函數:open、read、write、close,然後說明不同緩沖區長度對read和write函數的影響。

本章所說的函數經常被稱為不帶緩沖的 I/O (unbuffered I/O),與將在第5章中說明的標準 I/O相對照。術語不帶緩沖指每個read和write都調用內核中的一個系統調用。

只要涉及在多個進程間共享資源,原子操作的概念就變得非常重要。我們將通過文件 I/O 和 open 函數的參數來討論此概念。然後本章將進一步討論在多個進程間如何共享文件,以及所涉及的內核數據結構。在討論了這些特征後,將說明 dup、fcntl、sync、fsync和 ioctl函數。

3.2 文件描述符

對於內核而言,所有 打開的文件都通過文件描述符引用。文件描述符是一個非負整數。當打開一個現有文件或創建一個新文件時,內核向進程返回一個文件描述符。當讀寫一個文件時,使用open或creat返回文件描述符標識該文件,將其作為參數傳送給 read 或 write。

按照慣例,UNIX系統shell使用文件描述符0與進程的標準輸入相關聯,文件描述符1與標準輸出關聯,文件描述符2與標準出錯關聯,這是shell以及應用程序的使用慣例,與UNIX內核無關,盡管如此,如果不遵照這種慣例,那麽很多UNIX系統應用程序就不能正常工作。

在依從POSIX的應用程序中,幻數0、1、2應當替換成符號常量 STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO。這些常量定義在 <unistd.h>。

文件描述符的變化範圍是0~OPEN_MAX。

3.3 open函數

調用open函數可以打開或創建一個文件。

#include <fcntl.h>
int open(const char *pathname, int oflag, ... /* mode_t mode */ );

oflag參數可用來說明多個選項,用下列一個或多個常量進行或運算構成oflag參數(這些常量定義在<fcntl.h>):

O_RDONLY 只讀打開

O_WRONLY 只寫打開

O_RDWR 讀寫打開

上面三個常量中必須指定一個且只能指定一個,下面常量則是可選擇的:

O_APPEND 每次寫時都追加到文件尾端。

O_CREAT 若文件不存在,則創建。使用此選項時,需要使用第三個參數mode,用其指定該新文件的訪問權限(4.5節將說明文件的訪問權限,那時就能了解如何指定mode,以 及如何使用進程的umask值修改它)。

O_EXCL 如果同時指定了 O_CREAT,而文件已經存在,則會出錯。用此可以測試一個文件是否存在,如果不存在,則創建此文件,這事測試和創建兩者成為一個原子操作。 3.11節將更詳細的說明原子操作。

O_TRUNC 如果此文件存在,而且為只寫或讀寫成功打開,則其長度截短為0。

O_NOCTTY 如果pathname指的是終端設備,則不將該設備分配作為此進程的控制終端。9.6節將說明控制終端。

O_NONBLOCK 如果pathname指的是一個FIFO、一個塊特殊文件或一個字符特殊文件、則此選項為文件的本次打開操作和後續的 I/O 操作設置非阻塞模式。14.2節將說明此工作模式。

下面三個標誌也是可選的。它們是 Single UNIX Specification(已經POSIX.1)中同步輸入和輸出選項的一部分。

O_DSYNC 使每次 write 等待物理 I/O 操作完成,但是如果寫操作並不影響讀取剛寫入的數據,則不等待文件屬性被更新。

O_RSYNC 使每一個以文件描述符作為參數的read操作等待,直至任何對文件同一部分進行未 決寫操作都完成。

O_SYNC 使每次 write 都等待物理 I/O 操作完成,包括由 write 操作引起的文件屬性更新所需的 I/O。3.14節將使用此選項。

O_DSYNC 和 O_SYNC 標誌有微妙的區別。僅當文件屬性需要更新以反映文件數據變化(例如,更新文件大小以反映文件包含了更多數據)時,O_DSYNC 標誌才影響文件屬性。而設置 O_SYNC 標誌後,數據和屬性總是同步更新,當文件用 O_DSYNC 標誌打開,在重寫其現有部分內容時,文件時間屬性不會同步更新 。與此相反,如果文件是用 O_SYNC 標誌打開,那麽對該文件的每一次 write 操作都將在 write 返回前更新文件時間,這與是否改寫現有字節或增寫文件無關。

(個人理解是 O_DSYNC 和 O_SYNC 都會確保內存中的數據寫入到磁盤,但使用 O_DSYNC 會比較文件操作前後的內容,如果內容沒有變化,那麽不會更新文件時間,而使用 O_SYNC ,只要調用了write,無論是否真正修改文件內容,文件時間屬性都會更新)

由 open 返回的文件描述符一定是最小未用文件描述符數值。這一點被某些應用程序用來標誌輸入、標誌輸出或標誌出錯重定向。在 3.12節說明 dup2 函數時,可以了解到有更好的方法來保證在一個給定的描述符上打開一個文件。

文件名和路徑名截短

如果NAME_MAX是14,而試圖創建一個文件名包含15個字符的新文件,會發生什麽呢?

Linux總是返回出錯。

3.4 creat函數

也可調用 creat 函數創建一個新文件。

#include <fcntl.h>
int creat(const char *pathname, mode_t mode);

註意,此函數等效於:

open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

在早期的UNIX系統中,open第二個參數只能是0、1、2,沒法打開一個未存在的文件,因此需要另一個系統調用 creat 創建新文件。現在,open 函數提供了 O_CREAT 和 O_TRUNC 於是也就不需要 creat 函數。

creat的一個不足之處是它以只寫方式打開所創建文件,在提供open的新版本之前,如果要創建一個臨時文件,並要先寫該文件,然後又讀該文件,則必須要先調用 creat、close,然後再調用 open。現在則可用下列方式調用 open:

open(pathname, O_RDWR | O_CREAT | O_TRUNC, mode);

3.5 close函數

關閉一個打開的文件:

#include <unistd.h>
int close(int filedes);

關閉一個文件時還會釋放該進程在該文件上的所有記錄鎖。14.3節將討論這一點。

當一個進程終止時,內核自動關閉它所有打開的文件。很多程序都利用了這一功能而不顯示地用close關閉打開文件。

3.6 lseek函數

每個打開地文件都有一個與其相關聯地“當前文件偏移量(current file offset)”。

它通常是一個非負整數,用以度量從文件開始處計算字節數(本節稍後將對“非負”這一修飾詞地某些例外進行說明)。

通常,讀、寫操作都從當前文件偏移量 處開始,並使偏移量增加所讀寫地字節數。按照系統默認地情況,打開一個文件時,除非指定 O_APPEND 選項,否則偏移量被設置為0。

可以調用lseek顯式地為一個打開文件設置其偏移量。

#include <unistd.h>
off_t lseek(int fileds, off_t offset, int whence);

對於參數 offset 的解釋與參數 whence 的值有關。

若 lseek成功執行,則返回新的文件偏移量,為此可用下列方式確定打開文件的當前偏移量:

off_t currpos;
currops = lseek(fd, 0, SEEK_CUR);

這種方法也可用來確定所涉及文件是否可以設置偏移量。如果文件描述符引用的是一個管道、FIFO或網絡套接字,則lseek返回-1,並將errno設置為 ESPIPE。

// 測試能否對標誌輸入設置偏移量

#include "apue.h"

int main(void)
{
   if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
      printf("cannot seek\n");
   else
      printf("seek OK\n");

   exit(0);
}

通常,文件的當前偏移量應當是一個非負整數,但是某些設備也可能允許負的偏移量。但對於普通文件,則其偏移量必須是非負值。因為偏移量可能是負值,所以在比較lseek的返回值時應當謹慎,不要測試它是否小於0,而要測試它是否等於-1。

lseek僅將當前文件偏移量記錄在內核中,它並不引起任何 I/O 操作。然後,該偏移量用於下一個讀或寫操作。

文件偏移量可以大於文件的當前長度,在這種情況下,對該文件的下一次寫將加長該文件,並在文件中構成一個空洞,這一點是允許的。位於文件中但沒有寫過的字節都會被讀為0。

文件中的空洞並不要求在磁盤上占用存儲區。具體處理方式與文件系統的實現有關,當定位到超出文件尾端之後寫時,對於新寫的數據需要分配磁盤塊,但是對於原文件尾端和新開始寫位置之間的部分則不需要分配磁盤塊。

// 創建一個具有空洞的文件

#include "apue.h"
#include <fcntl.h>

char buf1[] = "aaaaaaaaaa";
char buf2[] = "bbbbbbbbbb";

int main(void)
{
   int fd;
   
   if ((fd = creat("file.hole", FILE_MODE)) < 0)
      err_sys("creat error");
   if (write(fd, buf1, 10) != 10)
      err_sys("buf1 write error");
  /* offset now = 10 */
   
   if (lseek(fd, 16384, SEEK_SET) == -1)
      err_sys("lseek error");
   /* offset now = 16384 */
   
   if (write(fd, buf2, 10) != 10)
      err_sys("buf2 write error");
   /* offset now = 16394 */  

    exit(0);
}

使用 od(1)命令觀察該文件實際內容。命令行中 -c 標誌標識以字符方式打印文件內容。從中可以看出,設置為空洞的部分,即未被使用的字節,被以0的方式讀出。

技術分享圖片

觀察相同字節長度的空洞文件和非空洞文件的磁盤塊大小:

技術分享圖片

雖然兩個文件長度相同,但無空洞的文件占用了20個磁盤塊,而具有空洞的文件只占用了8個磁盤塊。4.12節將對具有空洞的文件進行更多說明。

lseek使用偏移量使用 off_t 類型表示,所以允許具體實現根據各自特定的平臺自行選擇大小合適的數據類型。

3.7 read函數

#include <unistd.h>

ssize_t read(int filedes, void *buf, size_t nbytes);

如read成功,則返回讀到的字節數。如已到文件結尾,則返回0。

有多種情況可使實際讀到的字節數少於要求讀的字節數:

(1)讀普通文件時,在讀到要求字節數之前已到達了文件尾端。例如,若在到達文件尾端之前還有30個字節,而要求讀100個字節,則read返回30,下一次再調用read時,它將返回0(文件尾端)。

(2)當從終端設備讀時,通常一次最多讀一行(第18章將介紹如何改變這一點)。

(3)從網絡讀時,網絡中的緩沖機構可能造成返回值小於所要求讀的字節數。

(4)當從管道或FIFO讀時,如若管道包含的字節少於所需的數量,那麽read將只返回實際可用的字節數。

(5)當從某些面向記錄的設備(如磁帶)讀時,一次最多返回一個記錄。

(6)當某一信號造成中斷,而已經讀了部分數據量時。我們將在10.5節進一步討論此種情況。

讀操作從文件當前偏移量處開始,再成功返回之前,該偏移量將增加實際讀到的字節數。

3.8 write函數

ssize_t write(int filedes, const void *buf, size_t nbytes);

其返回值通常與參數 nbytes 的值相同,否則表示出錯。write出錯的一個常見原因是:磁盤已寫滿,或者超過了一個給定進程的文件長度限制。

對於普通文件,寫操作從文件的當前偏移量處開始。如果在打開該文件時,指定了 O_APPEND 選項,則在每次寫操作之前,將文件偏移量設置在文件當前結尾處。在一次成功寫之後,該文件偏移量增加實際寫的字節數。

3.9 I/O 的效率

#include "apue.h"

#define BUFFSIZE 4096

int main(void)
{
   int n;
   char buf[BUFFSIZE];
   
   while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) != -1) 
      if  (write(STDOUT_FILENO, buf, n) != n)
         sys_err("write error");

   if (n < 0)
      err_sys("read error");

   exit(0);
}

測試選取 BUFFSIZE 值對效率的影響

技術分享圖片

可見在測試環境的文件系統下,buffer大小為4096是最佳的。

最後幾個記錄項,可以觀察到影響,是由於當 BUFFERSIZE為128KB之後,預讀技術停止了。預讀技術是系統檢測到在進行順序讀取時,就試圖讀入比應用程序所要求更多的數據,並假想應用程序很快會讀這些數據。

3.10 文件共享

UNIX系統支持在不同進程間共享打開的文件。在介紹dup函數之前,先要說明這種共享。為此先介紹內核用於所有 I/O 的數據結構。

下面的說明時概念性的,與特定實現可能不匹配,也可能匹配。

內核使用三種數據結構表示打開的文件,它們之間的關系決定了在文件共享方面一個進程對另一個進程可能產生的影響。

(1)每個進程在進程表中都有一個記錄項,記錄項中包含有一張打開文件描述符表,可將其視為一個矢量,每個描述符占用一項。與每個文件描述符相關聯的是:

(a)文件描述符標誌(close_on_exec)

(b)指向一個文件表項的指針。

(2)內核為所有打開文件維持一張文件表。每個文件表項包含:

(a)文件狀態標誌(讀、寫、添加、同步和阻塞等,關於這些標誌的更多信息參見3.14節)。

(b)當前文件偏移量。

(c)指向該文件v節點表現的指針。

(3)每個打開文件(或設備)都有一個v節點(v-node)結構。v節點包含了文件類型和對此文件進行各種操作的函數指針。對於大多數文件,v節點還包含了該文件的 i 節點(i-node,索引節點)。這些信息是在打開文件時從磁盤上讀入內存的。所以所有關於文件的信息都是快速可供使用的。例如,i 節點包含了文件的所有者、文件長度、文件所在的設備、指向文件實際數據塊在磁盤上所在位置的指針等待(4.14節較詳細地說明了典型UNIX系統文件系統,並將更多地介紹 i 節點)。

Linux沒有使用 v 節點,而是使用了通用 i 節點結構。雖然兩種實現有所不同,但在概念上,v 節點與 i 節點是一樣地。兩者都指向文件系統特有的 i 節點結構。

Linux沒有將相關數據結構分為 i 節點和 v 節點,而是采用了一個獨立於文件系統的 i 節點和一個依賴於文件系統的 i 節點。

我們忽略了某些實現細節,但這並不影響我們的討論。例如,打開文件描述符表可存放在用戶空間,而非進程表中。這些表也可以用多用方式實現,數組、鏈表。

下圖顯示了一個進程的三張表之間的關系。該進程有兩個不同的打開文件:一個文件打開為標誌輸入(文件描述符0),另一個打開的標誌輸出(文件描述符為 1 )。

技術分享圖片

如果兩個獨立進程各種打開了同一個文件,如下圖,我們假定第一個進程在文件描述符3上打開該文件,而另一個進程則在文件描述符4上打開該文件。打開該文件的每個進程都得到一個文件表項,但對一個給定的文件只有一個 v 節點表項。每個進程都有自己的文件表項的一個理由是:這種安排使每個進程都有它自己的對該文件的當前偏移量。

技術分享圖片

給出這些數據結構後,現在對前面所述的操作進一步說明。

(1)在完成每個 write 後,在文件表項中的當前文件偏移量即增加所寫的字節數。如果這使當前文件偏移量超過了當前文件長度,則在 i 節點表項中的當前長度被設置為當前文件偏移量(也就是該文件加長了)。

(2)如果用 O_APPEND 標誌打開一個文件,則相應標誌也被設置到文件項的文件狀態標誌中。每次對這種具有添寫標誌的文件執行寫操作時,在文件表項中的當前文件偏移量首先被設置為 i 節點表項中的文件長度。這就使得每次寫的數據都添加到文件的當前尾端處。

(3)若一個文件用 lseek 定位到文件當前的尾端,則文件表項中的當前文件偏移量被設置為 i 節點表項中的當前文件長度(註意,這與用O_APPEND標誌打開的文件是不同的)。

(4)lseek 函數只修改文件表項中的當前文件偏移量,沒有進行任何 I/O操作。

可能有多個文件描述符項指向同一個文件表項。在3.12節中討論dup函數時,能看到這一點。這fork後也會發生同樣的情況,此時父、子進程對於每一個打開文件描述符共享同一個文件表項。(見8.3節)

註意,文件描述符標誌和文件狀態標誌在作用域方面的區別,前者只用於一個進程的一個描述符,而後者則適用於該給定文件表項的任何進程中的所有描述符。

本節上面所述的一切對於多個進程讀同一文件都能正確工作。每個進程都有自己的文件表項,其中也有它自己的當前文件偏移量。但是,當多個進程寫同一個文件時,則可能闡述預想不到的結果。為了說明如何避免這種情況,需要理解原子操作的概念。

3.11 原子操作

(1)添寫至一個文件

考慮一個進程,它要將數據添加到一個文件尾端。早期UNIX系統不支持Open的O_APPEND選項,所以程序被編寫成下列形式:

if (lseek(fd, 0L, 2) < 0)
  err_sys("lseek error");
if (write(fd, buf, 100) != 100)
  err_sys("write error");

對於單個進程,這段程序能正常工作,但是若有多個進程,則會有問題。

問題出在邏輯操作“定位到文件尾端,然後寫”,它使用了兩個分開的函數調用,解決方法是使這兩個操作對其他進程而言成為一個原子操作。UNIX系統提供了一種方法使這種操作成為原子操作,該方法就是在打開文件時設置 O_APPEND標誌。

(2)pread和pwrite函數

原子性的定位搜索(seek)和進行 I/O。

ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int filedes, void *buf, size_t nbytes, off_t offset);

調用pread 相當於順序調用 lseek 和read,但是 pread 又與這種順序調用有下列重要區別:

(a)調用 pread 時,無法中斷其定位和讀操作。

(b)不更新文件指針。

調用pwrite 相當於順序調用 lseek 和write,但也與他們有類似的區別。

(3)創建一個文件

if ((fd = open(pathname, O_WRONLY)) < 0)  {
      if (errno == ENOENT) {
         if ((fd = creat(pathname, mode)) < 0)
            err_sys("creat error");
      }  else {
         err_sys("open error");
      }
}

這段代碼也有問題。應該使用 O_CREAT 和 O_EXCL 完成原子操作。

如果一組操作是原子的,那麽要麽執行完整個操作,要麽不執行。

3.12 dup和dup2函數

下面兩個函數都可用來復制一個現存的文件描述符:

#include <unistd.h>

int dup(int filedes);
int dup2(int filedes, int filedes2);

由dup返回的新文件描述符一定是當前可用文件描述符中最小數值。用dup2則可用filedes2參數指定新描述符的數值。如果filedes2已經打開,則先將其關閉,如若filedes等於filedes2,則dup2返回filedes2,而不關閉它。

這些函數返回的新文件描述符與參數filedes共享一個文件表項,如下圖

newfd = dup(1);

技術分享圖片

復制一個描述符的另一種方法是使用 fcntl 函數,3.14節將對該函數進行說明。實際上,

調用

dup(filedes);

等效於

fcntl(filedes, F_DUPFD, 0);

而調用

dup2(filedes, filedes2);

等效於

close(filedes2);

fcntl(filedes, F_DUPFD, filedes2);

後一種情況下,dup2並不完全等同於 close 加上 fcntl。它們之間的區別是:

(a)dup2是一個原子操作,而close及fcntl則包括兩個函數調用。有可能在close和fcntl之間插入執行吸納後捕獲函數,他可能修改文件描述符

(b)dup2和fcntl有某些不同的errno

3.13 sync、fsync和fdatasync函數

傳統的UNIX實現在內核中設有緩沖區高速緩存或頁面高速緩存,大多數磁盤 I/O都是通過緩存進行。當數據寫入文件時,內核通常先將數據復制到一個緩沖區中,如果該緩沖區尚未寫滿,則並不會將其排入輸出隊列,而是等待其寫滿或者當內核需要重用該緩沖區以便存放其他磁盤塊數據時,再將該緩沖排入輸出隊列,然後待其到達隊首時,才進行實際的 I/O 操作。這種輸出方式被稱為延遲寫(delayed write).

延遲寫減少了磁盤讀寫次數,但是卻降低了文件內容的更新 速度,使得欲寫到文件中的數據在一段時間內並沒有寫到磁盤上。當系統發生故障時,這種延遲可能會造成更新內容的丟失。為了保證磁盤上實際文件系統與緩沖區高速緩存中內容的一致性。UNIX系統提供了 sync、fsyn和fdatasync。

#include <unistd.h>

int fsync(int filedes);
int fdatasync(int filedes);
void sync(void);

sync函數只是將所有修改過的塊緩沖區排入寫入隊列,然後就返回,它並不等的實際寫磁盤操作結束。

通常稱為update的系統守護進程會周期性地(一般間隔30秒)調用sync函數。這就保證了定期沖洗內核的塊緩存區。命令sync(1)也調用sync函數。

fsync函數只對由文件描述符 fildes指定的單一文件起作用,並且等待磁盤操作結束,然後返回。fsync可用於數據庫這樣的應用程序,而這種應用程序需要確保將修改過的塊立即寫到磁盤上。

fdatasync函數類似於fsync,但它只影響文件的數據部分。而除數據外,fsync還會同步更新文件的屬性。

3.14 fcntl函數

用於改變已打開文件的性質

#include <unistd.h>

int fcntl(int filedes, int cmd, ... /* int arg */);

在本節的各個實例中,第三個參數總是一個整數,與上面所示函數原型中的註釋部分相對應。但是14.3節說明記錄鎖時,第三個參數則是指向一個結構的指針。

fcntl函數有5種功能:

(1)復制一個現有描述符(cmd = F_DUPFD)

(2)獲得/設置文件描述符標記(cmd = F_GETFD 或 F_SETFD)

(3)獲得/設置文件狀態標記(cmd = F_GETFL 或 F_SETFL)

(4)獲得/設置異步 I/O所有權(cmd = F_GETOWN 或 F_SETOWN)

(5)獲得/設置記錄鎖 (cmd = F_GETLK、F_SETLK 或 F_SETLKW)

fcntl的返回值與命令有關。如果出錯,所有命令都返回-1,如果成功則返回某個其他值。下列四個命令有特定返回值:F_DUPFD、F_GETFD、F_GETFL和F_GETOWN。第一個返回新的文件描述符,接下來的兩個返回相應標誌,最後一個返回一個正的進程ID或負的進程組ID。

#include "apue.h"
#include <fcntl.h>

int main(int argc, char **argv)
{
   int val;
   
   if (2 != argc)
      err_quit("usage: a.out <descriptor#>");
   
   if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)
      err_sys("fcntl error for fd %d", atoi(argv[1]));

   switch (val & O_ACCMODE) {
   case O_RDONLY:
      printf("read only");
      break;
   
    case O_WRONLY:
      printf("write only");
      break;
   
    case O_RDWR:
      printf("read write");
      break;

    default:
      err_dump("unknown access mode");
   }

   if (val & O_APPEND)
      printf(", append");
   if (val & O_NONBLOCK)
      printf(", nonblocking");
   if (val & O_SYNC)
      printf(", synchronous writes");
   putchar(\n);
   exit(0);
}

在修改文件描述符標誌 或文件狀態標誌時必須謹慎,先要取得現有的標誌值,然後根據需要修改它,最後設置新標誌值。不能只是執行F_SETFD或F_SETFL命令,這樣會關閉以前設置的標誌位。

void set_fl(int fd, int flags)
{
   int val;

   if ((val = fcntl(fd, F_GETFL, 0)) < 0)
      err_sys("fcntl F_GETFL error");
   val |= flags; /* turn on flags */
   if (fcntl(fd, F_SETFL, val) < 0)
      err_sys("fcntl F_SETFL error");
}

如果將中間一條語句改為:

val &= ~falgs; /* turn off flags */

就構成另一個函數,我們稱其為clr_fl。

如果:

set_fl(STDOUT_FILENO, O_SYNC)

這就使每次write都要等待,直至數據已寫到磁盤上再返回。在UNIX系統中,通常write只是將數據排入隊列,而實際的寫磁盤操作則可能在以後某個時刻進行。

程序運行時,設置 O_SYNC 標誌會增加時鐘時間。

3.15 ioctl函數

ioctl函數時 I/O 操作的雜物箱。不能用本章中其他函數表示的 I/O 操作通常都能用 ioctl 表示。終端 I/O 是 ioctl 的最大使用方面。

#include <sys/ioctl.h>

int ioctl(int filedes, int reques, ...);

3.16 /dev/fd

較新的系統都提供名為 /dev/fd 的目錄,其目錄項是名為 0,1,2等的文件。打開文件 /dev/fd/n等效於復制描述符 n

在下列函數調用中

fd = open("/dev/fd/0", mode);

等效於

fd = dup(0);

描述符0和fd共享同一文件表項。

例如,若描述符0先前被打開為只讀,那麽我們也只能對fd進行讀操作。即使系統忽略打開模式,並且下列調用成功:

fd = open("/dev/fd/0", O_RDWR);

我們仍然不能對fd進行寫操作。

/dev/fd 文件主要由 shell 使用,它允許使用路徑名作為調用參數的程序,能用處理其他路徑名相同方式處理標準輸入和輸出。

如:

filter file2 | cat file1 - file3 | lpr
filter file2 | cat file1 /dev/fd/0 file3 | lpr

3.17 小結

本章說明了 UNIX 系統提供的基本 I/O 函數。 read 和write都在內核執行,所以稱這些函數為不帶緩沖的 I/O 函數。在只使用 read 和 write 情況下,觀察了不同 I/O 長度對讀文件所需時間的影響。也觀察了許多將寫入的數據沖洗到磁盤的方法,說明了它們對應用程序性能的影響。

在說明多個進程對同一個文件進行添寫操作以及多個進程創建同一文件時,本章介紹了原子操作。也介紹了內核用來共享打開文件信息的數據結構。

我們還介紹了 ioctl和 fcntl 函數。

第三章 文件 I/O