在 Linux 中,一切(或幾乎一切)都是文件,因此,文件操作在 Linux 中是十分重要的,為此,Linux 系統直接提供了一些函數用於對文件和設備進行訪問和控制,這些函數被稱為系統調用(syscall),它們也是通向操作系統本身的接口。
一、系統調用
系統調用就是 Linux 內核提供的一組用戶進程與內核進行交互的接口。這些接口讓應用程序受限的訪問硬件設備,提供了創建新進程並與已有進程進行通信的機制,也提供了申請操作系統其他資源的能力。
系統調用工作在內核態,實際上,系統調用是用戶空間訪問內核空間的唯一手段(除異常和陷入外,它們是內核唯一的合法入口)。系統調用的主要作用如下:
1)系統調用為用戶空間提供了一種硬件的抽象接口,這樣,當需要讀寫文件時,應用程序就可以不用管磁盤類型和介質,甚至不用去管文件所在的文件系統到底是哪種類型;
2)系統調用保證了系統的穩定和安全。作為硬件設備和應用程序之間的中間人,內核可以基於權限、用戶類型和其他一些規則對需要進行的訪問進行判斷;
3)系統調用是實現多任務和虛擬內存的前提。
要訪問系統調用,通常通過 C 庫中定義的函數調用來進行。它們通常都需要定義零個、一個或幾個參數(輸入),而且可能產生一些副作用(會使系統的狀態發生某種變化)。系統調用還會通過一個 long 類型的返回值來表示成功或者錯誤。通常,用一個負的值來表明錯誤,0表示成功。系統調用出現錯誤時,C 庫會把錯誤碼寫入 errno 全局變量,通過調用 perror() 庫函數,可以把該變量翻譯成用戶可理解的錯誤字符串。
二、幾種常用的系統調用函數
2.1 write 系統調用
系統調用 write 的作用是把緩沖區 buf 的前 nbytes 個字節寫入與文件描述符 fildes 關聯的文件中。它返回實際寫入的字節數。如果文件描述符有錯或者底層的設備驅動程序對數據塊長度比較敏感,該返回值可能會小於 nbytes。如果函數返回值為 0,就表示沒有寫入任何數據;如果返回值為 -1,則表明 write 系統調用出現了錯誤,錯誤代碼保存在全局變量 errno 里。 write 系統調用的原型如下:
#include <unistd.h> size_t write(int fildes,const void *buf,size_t nbytes);
其中,size_t 是標准 C 庫中定義的一個數據類型,實際上就是 unsigned int。
fildes 是文件描述符,內核利用文件描述符來訪問文件,它是一個非負的整數,當打開現存文件或者新建一個文件時,都會返回一個文件描述符。有多少文件描述符取決於系統的配置情況,當一個程序開始運行時,它一般有 3 個已經打開的文件描述符:標准輸入 0;標准輸出 1;標准錯誤 2。
#include <unistd.h> #include <stdlib.h> #include <stdio.h> int main() { size_t x = write(1,"my name is tongye!\n",20); printf("you have writed %d words to the buffer\n",x); exit(0); } /* 輸出結果: my name is tongye! you have writed 20 words to the buffer */
這段代碼簡單演示了一下 write 系統調用函數的用法:從緩沖區 buffer 中讀取前 20 個字節寫入標准輸出中,write 返回了實際寫入的字節數。
2.2 read 系統調用
系統調用 read 的作用是:從文件描述符 fildes 相關聯的文件里讀入 nbytes 個字節的數據,並把它們放到數據區 buf 中。它返回實際讀入的字節數,這可能會小於請求的字節數。如果 read 調用返回 0,就表示沒有讀入任何數據,已到達了文件尾;如果返回 -1,則表示 read 調用出現了錯誤。read 系統調用的原型如下:
#include <unistd.h> size_t read(int fildes,void *buf,size_t nbytes);
用一段代碼演示一下用法:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> int main() { char buffer[30]; size_t x = read(0,buffer,30); write(1,buffer,x); exit(0); } /* 輸出結果: hello ,my name is tongye! hello ,my name is tongye! */
這段代碼使用 read 系統調用函數從標准輸入讀取 30 個字節到緩沖區 buffer 中去(輸出結果中的第一行是從標准輸入鍵入的),然后使用 write 系統調用函數將 buffer 中的字節寫到標准輸出中去。
2.3 open 系統調用
系統調用 open 用於創建一個新的文件描述符。
#include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> int open(const char *path,int oflags); int open(const char *path,int oflags,mode_t mode); // oflags 標志為 O_CREAT 時,使用這種格式
open 建立了一條到文件或設備的訪問路徑。如果調用成功,它將返回一個可以被 read、write 和其他系統調用使用的文件描述符。這個文件描述符是唯一的,不會與任何其他運行中的進程共享。在調用失敗時,將返回 -1 並設置全局變量 errno 來指明失敗的原因。
使用 open 系統調用時,准備打開的文件或設備的名字作為參數 path 傳遞給函數,oflags 參數用於指定打開文件所采取的動作。oflags 參數是通過命令文件訪問模式與其他可選模式相結合的方式來指定的,open 調用必須指定以下文件訪問模式之一:
1)O_RDONLY:以只讀方式打開;
2)O_WRONLY:以只寫方式打開;
3)O_RDWR :以讀寫方式打開。
另外,還有以下幾種可選模式的組合( 用按位或 || 來操作 ):
4)O_APPEND:把寫入數據追加在文件的末尾;
5)O_TRUNC:把文件長度設置為零,丟棄已有的內容;
6)O_CREAT:如果需要,就按照參數 mode 中給出的訪問模式創建文件;
7)O_EXCL:與 O_CREAT 一起使用,確保調用者創建出文件。使用這個模式可以防止兩個程序同時創建同一個文件,如果文件已經存在,open 調用將失敗。
當使用 O_CREAT 標志的 open 調用來創建文件時,需要使用有 3 個參數格式的 open 調用。其中,第三個參數 mode 是幾個標志按位或后得到的,這些標志在頭文件 sys/stat.h 中定義,如下:
標志 | 說明 | 標志 | 說明 | 標志 | 說明 |
S_IRUSR | 文件屬主可讀 | S_IRGRP | 文件所在組可讀 | S_IROTH | 其他用戶可讀 |
S_IWUSR | 文件屬主可寫 | S_IWGRP | 文件所在組可寫 | S_IWOTH | 其他用戶可寫 |
S_IXUSR | 文件屬主可執行 | S_IWOTH | 文件所在組可執行 | S_IXOTH | 其他用戶可執行 |
用一個例子說明一下:
#include <unistd.h> #include <stdlib.h> #include <fcntl.h> int main() { open("file",O_CREAT,S_IRUSR | S_IWGRP); exit(0); }
執行這段代碼將在當前目錄下創建一個名為 file 的文件,該文件對文件屬主可讀,對文件所在組可寫,用 ls -l 命令查看如下:
可以看到有一個名為 file 的文件,該文件就是使用 open 系統調用創建的,文件的權限為文件屬主可讀,文件所在組可寫。
2.4 close 系統調用
系統調用 close 可以用來終止文件描述符 fildes 與其對應文件之間的關聯。當 close 系統調用成功時,返回 0,文件描述符被釋放並能夠重新使用;調用出錯,則返回 -1。
#include <unistd.h> int close(int fildes);
2.5 ioctl 系統調用
系統調用 ioctl 提供了一個用於控制設備及其描述符行為和配置底層服務的接口。終端、文件描述符、套接字甚至磁帶機都可以有為它們定義的 ioctl。
#include <unistd.h> int ioctl(int fildes,int cmd,...);
ioctl 對描述符 fildes 引用的對象執行 cmd 參數中給出的操作。
參考資料:
《Linux程序設計 第四版》