Linux 文件操作的系統調用接口


文件操作的系統調用接口:
 文件是Linux系統中的重要概念。它不僅僅是對普通文件的操作接口,也是設備通信、進程間通信、網絡通信的重要編程接口。因

此文件操作的相關調用也是Linux內核提供的最重要的編程接口。
 
 本節將重點敘述如下幾個常用的文件操作系統調用。
 open:打開文件。
 read:從已打開的文件中讀取數據。
 write:向已打開的文件中寫入數據。
 close:關閉已打開的文件。
 ioctl:向文件傳遞控制信息或發出控制命令。
 
 對文件的操作工程一般是這樣的:先打開文件,內核對打開的文件進行管理,打開成功后應用程序將獲得文件描述符;然后應用程

序使用文件描述符對文件進行讀寫操作;當全部操作完畢后,應用程序需要將文件關閉以釋放用於管理打開文件的內存。
 文件描述符是一個取值從0開始的整數。內核默認一個進程同時打開的文件數有一個上限,也就是文件描述符取值的上限,一般是

1024。
 每個進程在啟動后就默認有三個打開的文件描述符0,1,2,如果啟動程序時沒有進行重定向,則文件描述符0關聯到標准輸入,1關

聯到標准輸出,2關聯到標准錯誤輸出。在C庫函數中可以使用以下幾個宏來表示這幾個文件描述符:
 #define STDIN_FILENO   0
 #define STDOUT_FILENO  1
 #define STDERR_FILENO  2


打開文件:
 在訪問文件之前,首先應打開文件。可以使用open或creat函數來打開文件,它們的接口頭文件及函數原型如下:
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 int open(const char *pathname, int flags);
 int open(const char *pathname, int flags, mode_t mode);
 int creat(const char *pathname, mode_t mode);

 其各個參數及返回值的含義解釋如下.
 ◆ pathname:要打開的文件名稱。
 ◆ flags:標志位,指定打開文件的操作方式及打開時的一些行為。
 ◆ mode: 用於指定新文件的權限標志位。
 ◆ 返回值:操作成功則返回文件描述符,否則返回-1並設置標量errno的值。

 flags參數有以下幾個基本的取值。
 ◆ O_RDONLY:以只讀方式打開文件。
 ◆ O_WRONLY:以只寫方式打開文件。
 ◆ O_RDWR:以讀寫方式打開文件。

 這幾個標志位指定打開文件的操作方式,它們是互斥的,不能同時使用,但可以與下述標志用按位或的方式組合起來使用。
 ◆ O_CREAT:如果被打開的文件不存在,則自動創建這個文件。
 ◆ O_EXCL:如果O_CREAT標志已經使用,那么當由pathname參數指定的文件已經存在時open函數返回失敗。如果pathname給出的是 

  一個符號鏈接,無論它指向的文件是否存在,對open函數的調用都會返回失敗。
 ◆ O_NOCITY:如果被打開的文件是一個終端設備文件(如/dev/tty),它不會成為這個進程的控制終端。
 ◆ O_TRUNC:如果被打開的文件存在並且是以可寫的方式打開的,則清空文件原有內容。
 ◆ O_APPEND:新寫入的內容將被附加在文件原來的內容之后,即打開后文件的讀寫位置被置於文件尾。
 ◆ O_NONBLOCK:被打開的文件將以非阻塞的方式進行操作。
 ◆ O_NDELAY:同O_NONBLOCK。
 ◆ O_SYNC:被打開的文件將以同步I/O的方式進行操作,即任何寫操作都會先被同步到硬件設備上。同步完成后,對寫函數的調用 

  才返回。
 ◆ O_NOFOLLOW:如果pathname是一個符號鏈接,則對open函數的調用將返回失敗。
 ◆ O_DIRECTORY:如果pathname不是目錄,則對open函數的調用將返回失敗。

 需要注意的是,open函數有兩個原型,其中一個多出了個參數mode,它用於指定創建的新文件的訪問權限。如果打開時使用了

O_CREAT標志創建新文件,則一般都要給出mode參數,它的一些常用取值如表所示,這些值可以用按位或的方式組合使用。新文件的所屬用

戶和所屬組則是創建它的進程的所屬用戶和所屬組。
 權限標志定義  對應的八進制形式  含義
 S_IRWXU   00700    文件所屬用戶有讀寫和執行權限
 S_IRUSR(S_IREAD) 00400    文件所屬用戶有讀權限
 S_IWUSR(S_IWRITE) 00200    文件所屬用戶有寫權限
 S_IXUSR(S_IEXEC) 00100    文件所屬用戶有執行權限
 S_IRWXG   00070    組內用戶有讀寫和執行權限
 S_IRGRP   00040    組內用戶有讀權限
 S_IWGRP   00020    組內用戶有寫權限
 S_IXGRP   00010    組內用戶有執行權限
 S_IRWXO   00007    其他用戶有讀寫和執行權限
 S_IROTH   00004    其他用戶有讀權限
 S_IWOTH   00002    其他用戶有寫權限
 S_IXOTH   00001    其他用戶有執行權限

 鑒於在調用open函數時,O_WRONLY,O_CREAT,O_TRUNC三個標志位經常組合使用,因此由一個專門的函數creat來實現。如下:
 creat(pathname, mode); 
 實際上等價於:
 open(pathname,O_WRONLY|O_CREAT|O_TRUNC,mode);
 
 這兩個函數在打開文件成功時將返回一個文件描述符,可用於隨后的read/write或其他對文件的操作使用。兩個不同的進程打開同

一個文件是允許的,但它們得到的文件描述符一般是不同的。如果它們都對文件進行寫操作,就會出現數據不一致的情況,也就是最后寫入

的可能覆蓋先前其他進程寫入的內容,這就涉及到進程間數據共享和同步的概念了。
 如果打開操作失敗,這兩個函數會返回-1,並將errno變量設置為一個合適的錯誤值。

從文件讀取數據:
 文件打開后就可以進行讀寫操作了。讀操作的接口頭文件及函數原型如下:
 #include <unistd.h>
 ssize_t read(int fd, void *buf, size_t count);

 其各個參數及返回值的含義解釋如下:
 ◆ fd:要讀取的文件描述符。
 ◆ buf:指向讀取到的數據要放入的緩沖區。(buf指向的內存空間必須事先分配好)
 ◆ count:要讀取的字節數(緩沖區大小)。
 ◆ 返回值:讀取到的字節數,失敗返回-1,並設置標量errno的值。

 這里的size_t型實際上就是無符號整型,而ssize_t型就是有符號整型。
 這個函數將從fd代表的文件的當前讀寫位置讀取不超過count個字節到buf指向的內存中,並返回讀到的字節數。

 對於普通文件來說,讀操作完成后,文件的讀寫位置會向后移動,移動的長度就是讀取的字節數,下一次讀操作將從新的讀寫位置

開始。
 read函數的返回值小於指定的count是可能的,並不是錯誤。出現這種情況有各種原因,比如,文件本身可供讀取的字節數比count

小或者read系統調用被信號打斷等。read系統調用看似簡單,但實際上對於各種可能情況的處理是比較復雜的,因為I/O操作有很多異常情

況要考慮到。下面列出了read系統調用中可能遇到的情況及其處理方法。
 ◆ 調用返回值等於count,讀取的數據存放在buf指向的內存中,結果與預期一致。
 ◆ 調用返回一個大於0小於count的值,讀取的字節數存放在buf指向的內存中。出現這種情況可能是一個信號打斷了讀取過程,或 

  在讀取中發生了一個錯誤,或讀取的有效字節數大於0但不足count個,或在讀入count個字節前文件已經結束。如果讀取 

  的過程被信號打斷則可以再次進行讀取。
 ◆ 調用返回0,說明文件已結束,沒有可以讀入的數據。
 ◆ 調用阻塞,說明沒有可讀的數據,這種情況下如果以非阻塞方式操作文件,那么會立即返回錯誤。
 ◆ 調用返回-1,並且errno變量被設置為EINTR,表示在讀入有效字節前收到一個信號,這種情況可以重新進行讀操作。
 ◆ 調用返回-1,並且errno變量被設置為EAGAIN,這說明是在非阻塞方式下讀文件,並且沒有可讀的數據。
 ◆ 調用返回-1,並且errno變量被設置為非EINTR或EAGAIN的值,表示有其他類型的錯誤發生,必須根據具體情況進行處理。

 由於有各種異常情況的存在,為了從文件中可靠的讀取指定的字節數,就必須對這些情況進行處理,必要時需重新進行讀操作。這

對於設備文件、管道或socket來說尤其有意義。例如,用下面的代碼能夠可靠的從文件中讀取指定的字節數(假設文件是以阻塞的方式進行

操作的):
 ssize_t ret;
 while(len != 0 && (ret = read(fd,buf,len)) != 0)
 {
  if(ret == -1)
  {
   if(errno == EINTR) continue;
   perror("read");
   break; 
  }
  len -= ret;
  buf += ret; 
 }
 這里把操作放在循環中,當一次讀操作沒有得到len個字節時,將調整len和buf的值繼續進行操作。如果讀操作返回-1,說明有錯

誤發生,這時如果錯誤碼是EINTR,說明只是被信號打斷,可以繼續讀,其他情況則被認為是嚴重的錯誤,不能再繼續讀。
 以上是以阻塞方式讀文件的例子,這時,如果文件是一個設備文件並且設備沒有可讀的數據,進程將進入睡眠狀態不再繼續執行,

或者說阻塞在read系統調用處,直到設備有了可讀的數據才會被喚醒繼續執行。很多時候我們需要進程能夠立刻返回以處理其他的事物,那

么就需要采用非阻塞的方式來操作文件,舉例如下:
 ssize_t nr;
 start:
 nr = read(fd, buf, len);
 if(nr == -1)
 {
  if(errno == EINTR)  goto start;
  if(errno == EAGAIN)  
  {
   /*處理其他事物,在恰當時再調用read*/
  }
  else
  {
   /*有錯誤發生,處理錯誤*/
  }
 } 
 可以看到,在采用非阻塞的方式讀文件時,如果讀操作返回-1,我們必須檢查錯誤碼是否為EINTR或EAGAIN,如果是EINTR,可以再

次進行讀操作,而如果是EAGAIN,表示要讀取的(設備)文件現在沒有可供讀取的數據,因此進程可以繼續處理其他事物,而后在恰當的時

機再來讀取這個文件。
  
寫數據到文件:
 向打開文件中寫入數據的接口頭文件及函數原型如下:
 #include <unistd.h>
 ssize_t write(int fd, const void *buf, size_t count);

 其各個參數及返回值的含義解釋如下。
 ◆ fd:要寫入的文件的描述符。
 ◆ buf:指向要寫入的數據所存放的緩沖區。
 ◆ count:要寫入的字節數。
 ◆ 返回值:實際寫入的字節數,失敗則返回-1,並設置變量errno的值。

 這個函數會從fd所代表的文件當前讀寫位置開始,把buf指向的內存中最多count個字節寫入文件。寫入成功則返回寫入的字節數,

並更新文件的讀寫位置。
 write系統調用返回大於0而小於count的值是合法的,並不表示有錯誤發生。
 ◆ 調用返回值等於count,說明數據全部寫入成功。
 ◆ 調用返回一個大於0小於count的值,說明部分數據沒有寫入。這可能是因為寫入過程被信號打斷,或者底層的設備暫時沒有足 

  夠的空間存放寫入的數據。
 ◆ 調用阻塞,說明暫時不能寫入數據,這種情況下如果以非阻塞方式操作文件,那么會立即返回錯誤。
 ◆ 調用返回-1,並且errno變量被設置為EINTR,表示在寫入一個有效字節前,收到一個信號,應用程序可以再次進行寫操作。
 ◆ 調用返回-1,並且errno變量被設置為EAGAIN,說明是在非阻塞方式下寫文件但文件暫時不能寫入數據。
 ◆ 調用返回-1,並且errno變量被設置為EBADF,表示給定的文件描述符非法,或者文件不是以寫方式打開。
 ◆ 調用返回-1,並且errno變量被設置為EFAULT,表示buf是無效的指針。
 ◆ 調用返回-1,並且errno變量被設置為EFBIG,表示寫入的數據超過了最大的文件尺寸,或者超過了允許的文件讀寫位置。
 ◆ 調用返回-1,並且errno變量被設置為EPIPE,說明寫入時發生了數據通道斷層的錯誤,這種情況只在文件是管道或者是socket 

  的情況下發生。在這種情況下,進程還將收到一個SIGPIPE信號,信號的默認處理程序是使進程退出。
 ◆ 調用返回-1,並且errno變量被設置為ENOSPC,說明底層設備沒有足夠的空間。
 
 寫文件舉例如下:
 ssize_t ret;
 while(len != 0 && (ret = write(fd, buf, len)) != 0)
 {
  if(ret == -1)
  {
   if(errno == EINTR) continue;
   perror("write"); 
   break;
  } 
  len -= ret;
  buf += ret;
 }
 這里假定寫操作是以阻塞的方式進行的。如果以非阻塞方式進行寫操作,則當函數返回-1時,必須檢測errno變量的值是否為

EAGAIN,以決定能否再進行寫操作。
 

發送控制命令:
 在Linux系統上,那些不能被抽象為讀和寫的文件操作統一由ioctl操作代表。ioctl操作用於向文件發送控制命令,這些命令不能

被視為是輸入輸出流的一部分,而只是影響文件的操作方式。對於設備文件來說,ioctl操作常用於修改設備的參數。
 ioctl系統調用的接口頭文件及函數原型如下:
 #include <sys/ioctl.h>
 int ioctl(int fd, int request, ...);
 
 ◆ fd:要操作的文件描述符。
 ◆ request:代表要進行的操作,不同的(設備)文件有不同的定義。
 ◆ 可變參數:取決於request參數,通常是一個指向變量或結構體的指針。
 ◆ 返回值:成功返回0,有效ioctl操作返回其他非負值,錯誤返回-1。

 ioctl能夠進行的操作根據fd所代表的文件的具體類型而變化,非常繁多。下面舉一個例子,使用TIOCGWINSZ命令獲得終端的窗口

大小,如下:
 

/*文件名:console_size.c*/
 /*說明:使用ioctl獲得控制台窗口的大小*/
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/ioctl.h>
 
 int main(void)
 {
             struct winsize size;
             /*判斷標准輸出是否為tty設備,防止輸出被重定向的情況*/
             if(!isatty(STDOUT_FILENO) < 0) return -1;
             /*獲得窗口大小*/
            if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0)
           {
                    perror("ioctl TIOCGWINSZ error");
                    return -1; 
           }
           /*輸出結果*/
           printf("rows is %d, columns is %d\n", size.ws_row, size.ws_col);

          return 0;
 }

 

 
關閉文件:
 程序完成對文件的操作后,要使用close系統調用將文件關閉,其接口頭文件與函數原型如下:
 #include <unistd.h>
 int close(int fd);
 
 fd是要關閉的文件的描述符,返回值在操作成功的情況下是0,否則是-1.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM