歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> 《UNIX環境高級編程》---3.文件I/O

《UNIX環境高級編程》---3.文件I/O

日期:2017/3/1 11:43:05   编辑:關於Linux

一、打開、創建文件、關閉文件

文件描述符:一個非負整數,范圍是0~OPEN_MAX-1。內核用它來標識進程正在訪問的文件。當進程創建時,默認為它打開了3個文件描述符,它們都鏈接向終端:

0: 標准輸入 1: 標准輸出 2: 標准錯誤輸出

通常我們應該使用STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO來替代這三個幻數,從而提高可讀性。這三個常量位於中。

2. openopenat函數:打開文件

```
#include
int open(const char* path,int oflag,.../*mode_t mode*/);
int openat(int fd,const char*path,int oflag,.../*mode_t mode */);
```

- 參數:
    - `path`:要打開或者創建文件的名字
    - `oflag`:用於指定函數的操作行為,定義在``中。
    -  `mode`:文件訪問權限。文件訪問權限常量在 `` 中定義,有下列九個:
        - `S_IRUSR`:用戶讀
        - `S_IWUSR`:用戶寫
        - `S_IXUSR`:用戶執行
        - `S_IRGRP`:組讀
        - `S_IWGRP`:組寫          
        - `S_IXGRP`:組執行         
        - `S_IROTH`:其他讀
        - `S_IWOTH`:其他寫
        - `S_IXOTH`:其他執行 

- 對於`openat`函數,被打開的文件名由`fd`和`path`共同決定:
    **- 如果`path`指定的是絕對路徑,此時`fd`被忽略。`openat`等價於`open`**
    **- 如果`path`指定的是相對路徑名,則`fd`是一個目錄打開的文件描述符。被打開的文件的絕對路徑由該`fd`描述符對應的目錄加上`path`組合而成**
    **- 如果`path`是一個相對路徑名,而`fd`是常量`AT_FDCWD`,則`path`相對於當前工作目錄。被打開文件在當前工作目錄中查找。**

- 返回值:  
    - 成功:返回文件描述符。
    - 失敗:返回 -1

由 `open/openat` 返回的文件描述符一定是最小的未使用的描述符數字。

3. creat函數:創建一個新文件

```
#include
int creat(const char*path,mode_t mode);
```
該函數等價於`open(path,O_WRONLY|O_CREAT|O_TRUNC,mode)`。注意:
- 它以只寫方式打開,因此若要讀取該文件,則必須先關閉,然後重新以讀方式打開。
- 若文件已存在則將文件截斷為0。
- 

4. close函數:關閉文件

```
#include
int close(int fd);
```

- 參數:
    - `fd`:待關閉文件的文件描述符
- 返回值:
    - 成功:返回 0
    - 失敗:返回 -1

注意:
- 進程關閉一個文件會釋放它加在該文件上的所有記錄鎖。
- 當一個進程終止時,內核會自動關閉它所有的打開的文件。

二、定位、讀、寫文件

lseek函數:設置打開文件的偏移量


#include

off_t lseek(int fd, off_t offset,int whence);
參數:
fd:打開的文件的文件描述符 whence:必須是 SEEK_SETSEEK_CURSEEK_END三個常量之一 offset
如果 whenceSEEK_SET,則將該文件的偏移量設置為距離文件開始處offset個字節 如果 whenceSEEK_CUR,則將該文件的偏移量設置為當前值加上offset個字節,offset可正,可負 如果 whenceSEEK_END,則將該文件的偏移量設置為文件長度加上offset個字節,offset可正,可負 返回值:
成功: 返回新的文件偏移量 失敗:返回 -1

每個打開的文件都有一個與其關聯的“當前文件偏移量”。它通常是個非負整數,用於度量從文件開始處計算的字節數。通常讀、寫操作都從當前文件偏移量處開始,並且使偏移量增加所讀寫的字節數。注意:

打開一個文件時,除非指定O_APPEND選項,否則系統默認將該偏移量設為0 如果文件描述符指定的是一個管道、FIFO、或者網絡套接字,則無法設定當前文件偏移量,則lseek將返回 -1 ,並且將 errno 設置為 ESPIPE。 對於普通文件,其當前文件偏移量必須是非負值。但是某些設備運行負的偏移量出現。因此比較lseek的結果時,不能根據它小於0 就認為出錯。要根據是否等於 -1 來判斷是否出錯。 lseek 並不會引起任何 I/O 操作,lseek僅僅將當前文件的偏移量記錄在內核中。 當前文件偏移量可以大於文件的當前長度。此時對該文件的下一次寫操作將家常該文件,並且在文件中構成一個空洞。空洞中的內容位於文件中但是沒有被寫過,其字節被讀取時都被讀為0
/*
    test lseek function for std file io 
*/
#include "apue.h"
int main(void)
{
    if (lseek(STDIN_FILENO,0,SEEK_CUR) == -1) /*there is must be is equal operation*/
        printf("cannot seek\n");
    else
        printf("seek ok\n");
    exit(0);
}

read函數:讀取文件內容


#include

ssize_t read(int fd,void *buf,size_t nbytes);
參數:
fd:打開的文件的文件描述符 buf:存放讀取內容的緩沖區的地址(由程序員手動分配) nbytes:期望讀到的字節數 返回值:
成功:返回讀到的字節數,若已到文件尾則返回 0 失敗:返回 -1

讀操作從文件的當前偏移量開始,在成功返回之前,文件的當前偏移量會增加實際讀到的字節數。有多種情況可能導致實際讀到的字節數少於期望讀到的字節數:

讀普通文件時,在讀到期望字節數之前到達了文件尾端 當從終端設備讀時,通常一次最多讀取一行(終端默認是行緩沖的) 當從網絡讀時,網絡中的緩存機制可能造成返回值小於期望讀到的字節數 當從管道或者FIFO讀時,若管道包含的字節少於所需的數量,則 read只返回實際可用的字節數 當從某些面向記錄的設備(如磁帶)中讀取時,一次最多返回一條記錄 當一個信號造成中斷,而已讀了部分數據時。

write函數:想文件寫數據


#include

ssize_t write(int fd,const void *buf,size_t nbytes);
參數:
fd:打開的文件的文件描述符 buf:存放待寫的數據內容的緩沖區的地址(由程序員手動分配) nbytes:期望寫入文件的字節數 返回值:
成功:返回已寫的字節數 失敗:返回 -1

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

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

三、 原子操作、同步、復制、修改文件描述符

內核使用三種數據結構描述打開文件。它們之間的關系決定了一個進程與另一個進程在打開的文件之間的相互影響。

內核為每個進程分配一個進程表項(所有進程表項構成進程表),進程表中都有一個打開的文件描述符表。每個文件描述符占用一項,其內容為:
文件描述符標志 指向一個文件表項的指針 內核為每個打開的文件分配一個文件表項(所有的文件表項構成文件表)。每個文件表項的內容包括:
文件狀態標志(讀、寫、添寫、同步和阻塞等) 當前文件偏移量 指向該文件 v 結點表項的指針 每個打開的文件或者設備都有一個 v 結點結構。 v 結點結構的內容包括:
文件類型和對此文件進行各種操作函數的指針。 對於大多數文件, v 結點還包含了該文件的 i 結點。

這些信息都是在打開文件時從磁盤讀入內存的。如 i 結點包含了文件的所有者、文件長度、指向文件實際數據在磁盤上所在位置的指針等等。 v 結點結構和 i 結點結構實際上代表了文件的實體。

file_descriptor

現在假設進程 A 打開文件 file1,返回文件描述符 3;進程 B 也打開文件 file2,返回文件描述符 4:

內核在文件表上新增兩個表項:
這兩個文件表項指向同一個 v 結點表項 進程 A 、B 各自的文件描述符表項分別指向這兩個文件表項; 對文件的操作結果:
進程 A 每次 write 之後,進程 A 對應的文件表項的當前文件偏移量即增加所寫入的字節數。
若這導致當前文件偏移量超過當前文件長度,則修改 i 節點的當前文件長度,設為當前文件偏移量 如果進程 B 用 O_APPEND標志打開一個文件,在相應標志也設置到進程 B 對於的文件表項的文件狀態標志中。
每次進程 B 對具有追加寫標志的文件執行寫操作時,文件表項中的當前文件偏移量首先被置為 i 結點中的文件長度。 若進程 B 用 lseek 定位到文件當前的尾端,則進程 B 對應的文件表項的當前文件偏移量設置為 i 結點中的當前長度 lseek 函數只是修改文件表項中的當前文件偏移量,不進行任何 I/O 操作

可能一個進程中有多個文件描述符指向同一個文件表項。<喎?http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPtSt19Oy2df3o7o8L3A+DQrXt7zT0ru49s7EvP7KsaOssrvE3M2ouf08Y29kZT5sc2VlazwvY29kZT61vcSpzrLIu7rzPGNvZGU+d3JpdGU8L2NvZGU+oaPSqtPDPGNvZGU+T19BUFBFTkQ8L2NvZGU+0aHP7rTyv6rOxLz+o6zIu7rz1rG90zxjb2RlPndyaXRlPC9jb2RlPqGjPGJyIC8+DQrNqLn9PGNvZGU+bHNlZWs8L2NvZGU+tb3Eqc6yyLu68zxjb2RlPndyaXRlPC9jb2RlPsqxo6zI57n7tuC49r34s8zNrMqx1rTQ0NXiwb249rLZ1/ejrNTyu+HS/cbwvrrV+cz1vP4gzai5/SA8Y29kZT5PX0FQUEVORDwvY29kZT7Roc/utPK/qs7EvP6jrMi7uvPWsb3TPGNvZGU+d3JpdGU8L2NvZGU+yrGjrMTausvDv9K7tM7U2tC0stnX99aux7CjrLa8u+G9q734s8y1xLWxx7DGq9LGwb/J6NbDtb3OxLz+tcTEqc6yo6zT2srHvs2yu9Do0qrWtNDQPGNvZGU+bHNlZWs8L2NvZGU+tqjOu7LZ1/cgPGNvZGU+cHJlYWQvcHdyaXRlPC9jb2RlPr/J0tTWtNDQ1K3X09DUtcS2qM67tsEvtqjOu9C0IDxjb2RlPk9fQ1JFQVQ="O_EXCL選項打開文件時,可以原子性的檢查文件是否存在和創建文件這兩個操作。

pread/pwrite:原子定位讀和原子定位寫


#include

ssize_t pread(int fd,void*buf,size_t nbytes,off_t offset);
ssize_t pwrite(int fd,const void*buf,size_t nbytes,off_t offset);
參數:
fd:打開的文件描述符 buf:讀出數據存放的緩沖區/ 寫到文件的數據的緩沖區 nbytes:預期讀出/寫入文件的字節數 offset:從文件指定偏移量開始執行read/write 返回:
成功:讀到的字節數/已寫的字節數 失敗: -1

調用pread相當於先調用lseek再調用read.但是調用pread時,無法中斷其定位和讀操作,並且不更新當前文件偏移量;調用pwrite相當於先調用lseek再調用write.但是調用pwrite時,無法中斷其定位和寫操作,並且不更新當前文件偏移量

dup/dup2:復制一個現有的文件描述符:


#include

int dup(int fd);
int dup2(int fd,int fd2);
參數:
fd:被復制的文件描述符(已被打開) fd2:指定的新的文件描述符(待生成)
-返回值: 成功: 返回新的文件描述符 失敗: 返回 -1

對於dup函數,返回的新的文件描述符一定是當前可用的文件描述符中最小的數字。對於dup2函數:

如果 fd2已經是被打開的文件描述符且不等於fd,則先將其關閉,然後再打開(注意關閉再打開是一個原子操作) 如果 fd2等於fd,則直接返回fd2(也等於fd),而不作任何操作

任何情況下,這個返回的新的文明描述符與參數fd共享同一個文件表項(因此文件狀態標志以及文件偏移量都會共享)。 任何情況下,這個返回的新的文明描述符的close-on-exec標志總是被清除

UNIX操作系統在內核中設有緩沖區,大多數磁盤 I/O 都通過緩沖區進行。當我們想文件寫入數據時,內核通常都首先將數據復制到緩沖區中,然後排入隊列,晚些時候再寫入磁盤。這種方式稱為延遲寫。

當內核需要重用緩沖區來存方其他數據時,它會把所有延遲寫的數據庫寫入磁盤 你也可以調用下列函數來顯式的將延遲寫的數據庫寫入磁盤

#include

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

參數(前兩個函數):

fd:指定的打開的文件描述符

返回值(前兩個函數):

成功:返回 0 失敗: 返回 -1

區別:

sync:將所有修改過的塊緩沖區排入寫隊列,然後返回,它並不等待時機寫磁盤結束 fsync:只對由fd指定的單個文件起作用,等待寫磁盤操作結束才返回 fdatasync:只對由fd指定的單個文件起作用,等待寫磁盤操作結束才返回,但是它只影響文件的數據部分(fsync會同步更新文件的屬性)

update 守護進程會周期性的調用sync函數。命令sync也會調用sync函數

fcntl函數:改變已經打開的文件的屬性


#include

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

參數:

fd:已打開文件的描述符 cmd:有下列若干種:
F_DUPEF常量:復制文件描述符 fd。新文件描述符作為函數值返回。它是尚未打開的個描述符中大於或等於arg中的最小值。新文件描述符與fd共享同一個文件表項,但是新描述符有自己的一套文件描述符標志,其中FD_CLOEXEC文件描述符標志被清除 F_DUPFD_CLOEXEC常量:復制文件描述符。新文件描述符作為函數值返回。它是尚未打開的個描述符中大於或等於arg中的最小值。新文件描述符與fd共享同一個文件表項,但是新描述符有自己的一套文件描述符標志,其中FD_CLOEXEC文件描述符標志被設置 F_GETFD常量:對應於fd的文件描述符標志作為函數值返回。當前只定義了一個文件描述符標志FD_CLOEXEC F_SETFD常量:設置fd的文件描述符標志為arg F_GETFL常量:返回fd的文件狀態標志。文件狀態標志必須首先用屏蔽字 O_ACCMODE 取得訪問方式位,
然後與O_RDONLYO_WRONLYO_RDWRO_EXECO_SEARCH比較
(這5個值互斥,且並不是各占1位)。剩下的還有:O_APPENDO_NONBLOCKO_SYNC
O_DSYNCO_RSYNCF_ASYNCO_ASYNC F_SETFL常量:設置fd的文件狀態標志為 arg。可以更改的標志是:
O_APPENDO_NONBLOCKO_SYNCO_DSYNCO_RSYNCF_ASYNCO_ASYNC F_GETOWN常量:獲取當前接收 SIGIOSIGURG信號的進程 ID或者進程組 ID F_SETOWN常量:設置當前接收 SIGIOSIGURG信號的進程 ID或者進程組 IDarg。若 arg是個正值,則設定進程 ID;若 arg是個負值,則設定進程組ID F_GETLKF_SETLKF_SETLKW:獲取/設置文件記錄鎖 arg:依賴於具體的命令

返回值:

成功: 依賴於具體的命令 失敗: 返回 -1

#include 


#include


#include


#include


#include

void print_error(int fd,const char* action,int result)
{
   if(result==-1)
   {
       printf("\t %s on fd(%d) error:beause %s!\n",action,fd,strerror(errno));
   }
}

void test_get_fd(int fd)
{
    printf("\tget_fd on fd(%d):",fd);
    int result;
    result=fcntl(fd,F_GETFD);
    print_error(fd,"F_GETFD",result);
    if(result!=-1)
       printf("return:%d !\n",result);

}
void test_set_fd(int fd, int flag)
{
   printf("\tset_fd on fd(%d) of flag(%d):",fd,flag);
  int result;
   result=fcntl(fd,F_SETFD,flag);
  print_error(fd,"F_SETFD",result);
  if(result!=-1)
        printf("set_fd ok !\n");
}
int test_dup_fd(int fd,int min_fd)
{
    printf("\tdup_fd on fd(%d),set min_fd(%d),:",fd,min_fd);
   int result;
   result=fcntl(fd,F_DUPFD,min_fd);
   print_error(fd,"F_DUPFD",result);
  if(result!=-1)
        printf("return:%d !\n",result);
  return result;
}
int test_dup_exec_fd(int fd,int min_fd)
{
   printf("\tdup_exec_fd on fd(%d),set min_fd(%d),:",fd,min_fd);
  int result;
  result=fcntl(fd,F_DUPFD_CLOEXEC,min_fd);
  print_error(fd,"F_DUPFD_CLOEXEC",result);
  if(result!=-1)
     printf("return:%d !\n",result);
  return result;
}
void test_get_fl(int fd)
{
   printf("\tget_fl on fd(%d):",fd);
   int result;
  result=fcntl(fd,F_GETFL);
  print_error(fd,"F_GETFL",result);
  if(result!=-1)
  {
     printf("F_GETFL on fd(%d) has ",fd);
        if(result&O_APPEND) printf("\tO_APPEND;");
        if(result&O_NONBLOCK) printf("\tO_NONBLOCK;");
        if(result&O_SYNC) printf("\tO_SYNC;");
        if(result&O_DSYNC) printf("\tO_DSYNC;");
        if(result&O_RSYNC) printf("\tO_RSYNC;");
        if(result&O_FSYNC) printf("\tO_FSYNC;");
        if(result&O_ASYNC) printf("\thas O_ASYNC;");
        if((result&O_ACCMODE)==O_RDONLY)printf("\tO_RDONLY;");
       if((result&O_ACCMODE)==O_WRONLY)printf("\thas O_WRONLY;");
       if((result&O_ACCMODE)==O_RDWR)printf("\tO_RDWR;");
       printf("\n");
  }
}
void test_set_fl(int fd,int flag)
{
    printf("\tset_fl on fd(%d) of flag(%d):",fd,flag);
   int result;
   result=fcntl(fd,F_SETFL,flag);
  print_error(fd,"F_SETFL",result);
   if(result!=-1)
     printf("set_fl ok !\n");
}
void test_get_own(int fd)
{
   printf("\tget_own on fd(%d):",fd);
  int result;
  result=fcntl(fd,F_GETOWN);
  print_error(fd,"F_GETOWN",result);
  if(result!=-1)
           printf("return:%d !\n",result);
}
void test_set_own(int fd,int pid)
{
    printf("\tset_own on fd(%d) of pid(%d):",fd,pid);
   int result;
   result=fcntl(fd,F_SETOWN,pid);
   print_error(fd,"F_SETOWN",result);
  if(result!=-1)
         printf("set_own ok !\n");
}
int main(int argc, char *argv[])
{
    int fd;
  fd=openat(AT_FDCWD,"test.txt",O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);
  printf("Test dup:\n");
  test_get_fd(test_dup_fd(fd,10));
  test_get_fd(test_dup_fd(fd,0));
  test_get_fd(test_dup_exec_fd(fd,10));
  test_get_fd(test_dup_exec_fd(fd,0));
 printf("Test set_get_fd:\n");
 test_get_fd(fd);
  test_set_fd(fd,~FD_CLOEXEC);
  test_get_fd(fd);
  test_set_fd(fd,FD_CLOEXEC);
  test_get_fd(fd);
  printf("Test set_get_fl:\n");
  test_get_fl(fd);
    test_set_fl(fd,O_RDWR);
 test_get_fl(fd);
    test_set_fl(fd,O_RDONLY|O_NONBLOCK);
 test_get_fl(fd);
 printf("Test set_get own:\n");
 test_get_own(fd);
 test_set_fl(fd,1);
 test_get_own(fd);
    return 0;
}

注意:

Linux 下,不支持文件狀態標志: F_EXEC與F_SEARCH (result&O_ACCMODE)==O_RDONLY 表達式中, &優先級較低 F_SETFL命令:當文件讀打開時,你無法將文件狀態標志修改為O_WRONLYO_WRWR這兩種中任何一個。你只能修改:O_APPENDO_NONBLOCKO_SYNCO_DSYNCO_RSYNCF_ASYNCO_ASYNC等標志

/dev/fd目錄:該目錄下是名為0、1、2等的文件。打開文件/dev/fd/n等效於復制描述符(假定描述符n是打開的)

fd=open("/dev/fd/0",mod)fd和文件描述符0共享同一個文件表項。 大多數系統忽略mod參數 在 Linux 操作系統上, /dev/fd/0是個例外,它是個底層物理文件的符號鏈接。因此在它上面調用creat 會導致底層文件被截斷
Copyright © Linux教程網 All Rights Reserved