APUE學習筆記:第三章 文件I/O

3.1 引言node

術語不帶緩衝指的是每一個read和write都調用內核中的一個系統調用。這些不帶緩衝的I/O函數不是ISO C的組成部分,可是,它們是POSIX.1和Single UNIX Specification的組成部分linux

3.2 文件描述符shell

UNIX系統shell使用文件描述符0與進程的標準輸入相關聯、文件描述符1與標準輸出相關聯、文件描述符2與標準出錯輸出相關聯。數據庫

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

3.3 open函數網絡

調用open函數能夠打開或建立一個文件數據結構

1 #include<fcntl.h>
2 int open(const char *pathname,int oflag, .../*mode_t mode */);
3                                         返回值:若成功則返回文件描述符,若出錯則返回-1

pathname是要打開或建立文件的名字app

oflag參數可用來講明此函數的多個選項。用下列一個或多個常量進行「或」運算構成oflag參數(這些參數常量定義在<fcntl.h>頭文件中)異步

O_RDONLY  只讀打開async

O_WRONLY    只寫打開

O_RDWR     讀寫打開

大多數實現將O_RDONLY定義爲0,O_WRONLY定義爲1,O_RDWR定義爲2,以與早期的程序兼容。在這三個常量必須指定一個且只能指定一個)

下列常量是可選擇的:

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

O_CREAT    若此文件不存在,則建立它。使用此選項時,須要第三個參數mode,用其指定該新文件的訪問權限位

O_EXCL      若是同時指定了O_CREAT,而文件已經存在,則會出錯。用此能夠測試一個文件是否存在,若是不存在,則建立此文件,這使測試和建立二者成爲一個原子操做

O_TRUNC    若是此文件存在,並且只寫或讀寫成功打開,則將其長度截短爲0

O_NOCTTY  若是pathname指的是一個FIFO、一個塊特殊文件或一個字符特殊文件,則此選項爲文件的本次打開操做和後續的I/O操做設置非阻塞模式

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

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

O_RSYNC   使每個以文件描述符做爲參數的read操做等待,直至任何對文件同一部分進行的未決寫操做都完成(即read等待全部寫入同一區域的寫操做所有完成後再進行)

O_SYNC     使每次write都等到物理I/O操做完成,包括由write操做引發的文件屬性更新所需的I/O

當文件用O_DSYNC標誌打開,在重寫其現有的部份內容時,文件時間屬性不會同步更新。與此相反,若是文件是用O_SYNC標誌打開,那麼對該文件的每一次write操做都將在write返回前更新文件時間

3.4 creat函數

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

#include<fcntl.h>
int creat(const char *pathname,mode_t mode);
                               返回值:若成功則返回爲只寫打開的文件描述符,若出錯則返回-1

此函數等效於:open(pathname,O_WRONLY | O_CREAT | O_TRUNC,mode);//因此如今再也不須要creat函數

 

3.5 close函數

可調用close函數關閉一個打開的文件:

1 #include<unistd.h>
2 int close(int filedes);
3                         返回值:若成功則返回0,若出錯則返回-1

當一個進程終止時,內核自動關閉它全部打開的文件。不少程序都利用了這一功能而不顯式地用close關閉打開文件。

 

3.6 lseek函數

能夠調用lseek顯示地爲一個打開的文件設置其偏移量

1 #include<unistd.h>
2 off_t lseek(int filedes,off_t offset,int whence);
3                                     返回值:若成功則返回新的文件偏移量,若出錯則返回-1

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

若whence是SEEK_SET,則將該文件的偏移量設置爲距文件開始出offset個字節

若whence是SEEK_CUR,則將該文件的偏移量設置爲當前值加offset,offset可爲正或負

若whence是SEEK_END,則將該文件的偏移量設置爲文件長度加offset,offset可正可或負

能夠用下列方式肯定打開文件的當前偏移量:

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

這種方法也能夠用來肯定所涉及的文件是否能夠設置偏移量。若是文件描述符引用的是一個管道、FIFO或網絡套接字,則lseek返回-1,並將errno設置爲ESPIPE(illegal seek)

實例:3_1 測試可否對標準輸入設置偏移量

1 #include"apue.h"
2 int main()
3 {
4     if(lseek(STDIN_FILENO,0,SEEK_CUR)==-1)
5     printf("cannot seek\n");
6     else
7     printf("seek OK\n");
8     exit(0);
9 }

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

 

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

實例:3_2 建立一個具備空洞的文件

 1 #include"apue.h"
 2 #include<fcntl.h>
 3 #include<stdio.h>
 4 #include<unistd.h>
 5 
 6 char buf1[]="abcdefghij";
 7 char buf2[]="ABCDEFGHIJ";
 8 int main()
 9 {
10     int fd;
11     if((fd=creat("file.hole",FILE_MODE))<0)
12     err_sys("creat error");
13     if(write(fd,buf1,10)!=10)
14     err_sys("buf1 write error");
15     if(lseek(fd,16384,SEEK_SET)==-1)
16     err_sys("lseek error");
17     if(write(fd,buf2,10)!=10)
18     err_sys("buf2 write error");
19     exit(0);
20 }

編譯運行後,可用$ od -c file.hole查看文件的實際內容

 

3.7 read函數

調用read函數從打開文件中讀數據

1 #include<unistd.h>
2 
3 ssize_t read(int fiedes,void *buf,size_t nbytes);
4                     
5             返回值:若成功則返回讀到的字節數,若已到文件結尾則返回0,若出錯則返回-1

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

-讀普通文件時,在讀到要求字節數以前已達到了文件尾端

-當從終端設備讀時,一般一次最多讀一行

-當從網絡讀時,網絡中的緩衝機構可能形成返回值小於所要求讀的字節數

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

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

-當某一信號形成中斷,而已經讀了部分數據量時。

 

3.8 write函數

調用write函數想打開的文件寫數據

#include<unistd.h>

ssize_t write(int filedes,const void *buf,size_t nbytes);
                
                            返回值:若成功則返回已寫的字節數,若出錯則返回-1

其返回值一般與參數nbytes的值相同,不然表示出錯。

write出錯的一個常見緣由是:磁盤已寫滿,或者超過了一個給定進程的文件長度限制

 

3.9 I/O的效率

大多數文件系統爲改善其性能都採用某種預取技術,當檢測到正進行順序讀取時,系統就試圖讀入比應用程序要求的更多數據,並假想應用程序很快會讀這些數據。

 

3.10 文件共享

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

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

a.文件描述符標示

b.指向一個文件表項的指針

(2)內核爲全部文件維持一張文件表。每一個文件表包含:

a.文件狀態標誌(讀、寫、添寫、同步和非阻塞等)

b.當前文件偏移量

c.指向該文件v節點表項的指針

(3)每一個打開文件(或設備)都有一個v節點(v-node)結構。v節點包含了文件類型和對比文件進行各類操做函數的指針。對於大多數文件,v節點還包含了該文件的i節點。這些信息是在打開文件時從磁盤上讀入內存的,因此全部關於文件的信息都是快速可供使用的。例如,i節點包含了文件的全部者、文件長度、文件所在的設備、指向文件實際數據塊在磁盤所在位置的指針等等

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

 

給出了數據結構後,如今對前面所述的操做做進一步說明:

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

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

-若一個文件用lseek定位到文件當前的尾端,則文件表項中的當前文件偏移量被設置爲i節點表項中的當前文件長度

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

 

3.11 原子操做

1.添加一個文件

早期的UNIX系統版本並不支持open的O_APPEND選項,因此程序被編寫成下列形式:

if(lseek(fd,0L,2)<0)         /*position to EOF*/
err_sys("lseek error");
if(write(fd,buf,100)!=100)    /*and write*/
err_sys("write error");    

這段程序對於單個進程而言能夠正常運行,可是對於多進程而言,就會產生問題,由於不是原子操做。

UNIX提供了一種方法使這種操做成爲原子操做,該方法是在打開文件時設置O_APPEND標誌。這就使內核對這種文件進行寫以前,都將進程的當前偏移量設置到該文件的尾端處,因而在每次寫以前都不在須要調用lseek

 

2.pread和pwrite函數

SUS包括了XSI擴展,該擴展容許原子性地定位搜索(seek)和執行I/O。pread和pwrite就是這種擴展

#include<unistd.h>

ssize_t pread(int fileds,void *buf,size_t nbytes, off_t offset);
                    返回值:讀到的字節數,若已到文件結尾則返回0,若出錯則返回-1

ssize_t pwrite(int filedes,const void *buf,size_t nbytes,off_t offset);
                    返回值:若成功則返回已寫的字節數,若出錯則返回-1

調用pread至關於順序調用lseek和read,可是pread又與這種順序調用有如下區別:

-調用pread時,沒法中斷其定位和讀操做

-不更新文件指針

(調用pwrite狀況也相似)

 

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");
        }
}

若是在open和creat之間,另外一個進程建立了該文件,那麼就會引發問題。例如,若在這兩個函數調用之間,另外一個進程建立了該文件,而且寫進了一些數據,而後,原先的進程執行這段程序中的creat,這是,剛由另外一個進程寫上去的數據就會被擦去。

通常而言,原子操做指的是由多步組成的操做。若是該操做原子地執行,則要麼執行完全部步驟,要麼一步也不執行,不可能只執行全部步驟的一個子集。

 

3.12 dup和dup2函數

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

#include<unistd.h>

int dup(int filedes);

int dup2(int filedes, int filedes2);

            兩函數的返回值:若成功則返回新的文件描述符,若出錯則返回-1

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

這些函數返回的新文件描述符與參數filedes共享同一個文件表項(即返回的文件描述符與原來的文件描述符共用同一個文件表項)

複製一個描述符的另外一種方法是使用fcntl函數。

dup(filedes)     等效於  fcntl(filedes,F_DUPFD,0);

dup2(filedes,filedes2);  等效於  close(filedes2);   fcntl(filedes,F_DUPFD,filedes2);

 

3.13 sync、fsync和fdatasync函數

這三個函數,主要是爲了保證磁盤上實際文件系統與緩衝區高速緩存中內容的一致性。

#include<unistd>

int fsync(int filedes);

int fdatasync(int filedes);

void sync(void);

                        返回值:若成功則返回0,若出錯則返回-1

 

 sync函數只是將全部修改過的塊緩衝區排入寫隊列,而後就返回,它並不等待實際寫磁盤操做結束

(一般稱爲update的系統守護進程會週期性地(通常每隔30秒)調用那個sync函數。這就保證了按期沖洗內核的塊緩衝區。)

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

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

 

3.14 fcntl函數

fcntl函數能夠改變已打開文件的性質

#include<fcntl.h>

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

                    返回值:若成功則依賴於cmd,若出錯則返回-1

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).

F_DUPFD   複製文件描述符filedes。新文件描述符做爲函數值返回。它是還沒有打開的各描述符中大於或等於第三個參數值中各值的最小值。

F_GETFD   對應於filedes的文件描述符標誌做爲函數值返回。當前只定義了一個文件描述符標誌FD_CLOEXEC.

F_SETFD   對於filedes設置文件描述符標誌。新標誌值按第三個參數(取爲整數)設置

F_GETFL    對應於filedes的文件狀態標誌做爲函數值返回。

F_SETFL    將文件狀態標誌設置爲第三個參數的值。能夠更改的幾個標誌是:O_APPEND,O_NONBLOCK,O_SYNC,O_DSYNC,O_RSYNC,O_FSYNC和O_ASYNC

F_GETOWN  取當前接收SIGIO和SIGURG信號的進程ID或進程組ID。

F_SETOWN    設置接受SIGIO和SIGURG信號的進程ID或進程組ID。正的arg指定一個進程ID,負的arg表示等於arg絕對值的一個進程組ID

實例:3_4 對於指定的描述符打印文件標誌

 1 #include"apue.h"
 2 #include<fcntl.h>
 3 int main(int argc,char *argv[])
 4 {
 5     int val;
 6     if(argc!=2)
 7     err_quit("usage:a.out < descriptor#>");
 8     if((val=fcntl(atoi(argv[1]),F_GETFL,0))<0)
 9     err_sys("fcntl error for fd %d",atoi(argv[1]));    
10     switch(val & O_ACCMODE){   //三個訪問標誌位並不各佔一位,所以首先必須用屏蔽字O_ACCMODE取得訪問模式位
11     case O_RDONLY:
12         printf("read only");
13         break;
14     case O_WRONLY:
15         printf("write only");
16         break;
17     case O_RDWR:
18         printf("read write");
19         break;
20     default:
21         err_dump("unknown access mode");
22     }
23     if(val & O_APPEND)
24         printf(", append");
25     if(val & O_NONBLOCK)
26         printf(", nonblocking");
27     #if defined(O_SYNC)
28         if(val & O_SYNC)
29         printf(", synchronous writes");
30     #endif
31     #if !defined(_POSIX_C_SOURCE)&&defined(O_FSYNC)
32         if(val & O_FSYNC)
33         printf(", synchronous writes");
34     #endif
35         putchar('\n');
36         exit(0);
37 }

 

實例:

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

程序3_5 對一個文件描述符打開一個或多個文件狀態標誌

 1 #include"apue.h"
 2 #include<fcntl.h>
 3 void set_fl(int fd,int flag)
 4 {
 5     int val;
 6     if((val=fcntl(fd,F_GETFL,0))<0)
 7         err_sys("fcntl F_GETFL error");
 8     val |=flags;     //turn on flags
 9     if(fcntl(fd,F_SETFL,val)<0)
10         err_sys("fcntl F_SETFL error");
11 }

若是將中間的一條語句改成:val &= ~flags; //turn flags off

就構成另外一個函數,咱們稱其爲clr_fl。

若是在程序開始處,加上下面一行調用set_fl,則打開了同步寫標誌:set_fl(STDOUT_FILENO,O_SYNC);

這就使每次write都要等待,直至數據已寫到磁盤上再返回。

程序運行時,設置O_SYNC標誌會增長時鐘時間。在寫磁盤時,系統時間增長了,其緣由是內核須要從進程中複製數據,並將數據排入隊列以便由磁盤驅動器將其寫到磁盤上。當寫至磁盤文件時,咱們指望時鐘時間也會增長。

 

3.15 ioctl函數

ioctl函數是I/O函數的雜物箱。

#include<unistd.h>
#include<sys/ioctl.h>
#include<stropts.h>

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

                    返回值:若出錯則返回-1,若成功則返回其餘值

每一個設備驅動程序均可以定義它本身專用的一組ioctl命令。系統則爲不一樣種類的設備提供通用的ioctl命令

 

3.16 /dev/fd

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

某些系統提供路徑名/dev/stdin,/dev/stdout,/dev/stderr,這些等效於/dev/fd/0,/dev/fd/1,/dev/fd/2

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

相關文章
相關標籤/搜索