3. open/close

open函數可以打開或創建一個檔案。

#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);
返回值:成功返回新分配的檔案描述符,出錯返回-1並設置errno

在Man Page中open函數有兩種形式,一種帶兩個參數,一種帶三個參數,其實在C代碼中open函數是這樣聲明的:

int open(const char *pathname, int flags, ...);

最後的可變參數可以是0個或1個,由flags參數中的標誌位決定,見下面的詳細說明。

pathname參數是要打開或創建的檔案名,和fopen一樣,pathname既可以是相對路徑也可以是絶對路徑。flags參數有一系列常數值可供選擇,可以同時選擇多個常數用按位或運算符連接起來,所以這些常數的宏定義都以O_開頭,表示or。

必選項:以下三個常數中必須指定一個,且僅允許指定一個。

以下可選項可以同時指定0個或多個,和必選項按位或起來作為flags參數。可選項有很多,這裡只介紹一部分,其它選項可參考open(2)的Man Page:

注意open函數與C標準I/O庫的fopen函數有些細微的區別:

第三個參數mode指定檔案權限,可以用八進制數表示,比如0644表示-rw-r--r--,也可以用S_IRUSRS_IWUSR等宏定義按位或起來表示,詳見open(2)的Man Page。要注意的是,檔案權限由openmode參數和當前進程的umask掩碼共同決定。

補充說明一下Shell的umask命令。Shell進程的umask掩碼可以用umask命令查看:

$ umask
0022

touch命令創建一個檔案時,創建權限是0666,而touch進程繼承了Shell進程的umask掩碼,所以最終的檔案權限是0666&~022=0644。

$ touch file123
$ ls -l file123
-rw-r--r-- 1 akaedu akaedu 0 2009-03-08 15:07 file123

同樣道理,用gcc編譯生成一個執行檔時,創建權限是0777,而最終的檔案權限是0777&~022=0755。

$ gcc main.c
$ ls -l a.out
-rwxr-xr-x 1 akaedu akaedu 6483 2009-03-08 15:07 a.out

我們看到的都是被umask掩碼修改之後的權限,那麼如何證明touchgcc創建檔案的權限本來應該是0666和0777呢?我們可以把Shell進程的umask改成0,再重複上述實驗:

$ umask 0
$ touch file123
$ rm file123 a.out
$ touch file123
$ ls -l file123
-rw-rw-rw- 1 akaedu akaedu 0 2009-03-08 15:09 file123
$ gcc main.c
$ ls -l a.out
-rwxrwxrwx 1 akaedu akaedu 6483 2009-03-08 15:09 a.out

現在我們自己寫一個程序,在其中調用open("somefile", O_WRONLY|O_CREAT, 0664);創建檔案,然後在Shell中運行並查看結果:

$ umask 022
$ ./a.out
$ ls -l somefile
-rw-r--r-- 1 akaedu akaedu 6483 2009-03-08 15:11 somefile

不出所料,檔案somefile的權限是0664&~022=0644。有幾個問題現在我沒有解釋:為什麼被Shell啟動的進程可以繼承Shell進程的umask掩碼?為什麼umask命令可以讀寫Shell進程的umask掩碼?這些問題將在第 1 節 “引言”解釋。

close函數關閉一個已打開的檔案:

#include <unistd.h>

int close(int fd);
返回值:成功返回0,出錯返回-1並設置errno

參數fd是要關閉的檔案描述符。需要說明的是,當一個進程終止時,內核對該進程所有尚未關閉的檔案描述符調用close關閉,所以即使用戶程序不調用close,在終止時內核也會自動關閉它打開的所有檔案。但是對於一個長年累月運行的程序(比如網絡伺服器),打開的檔案描述符一定要記得關閉,否則隨着打開的檔案越來越多,會占用大量檔案描述符和系統資源。

open返回的檔案描述符一定是該進程尚未使用的最小描述符。由於程序啟動時自動打開檔案描述符0、1、2,因此第一次調用open打開檔案通常會返回描述符3,再調用open就會返回4。可以利用這一點在標準輸入、標準輸出或標准錯誤輸出上打開一個新檔案,實現重定向的功能。例如,首先調用close關閉檔案描述符1,然後調用open打開一個常規檔案,則一定會返回檔案描述符1,這時候標準輸出就不再是終端,而是一個常規檔案了,再調用printf就不會打印到屏幕上,而是寫到這個檔案中了。後面要講的dup2函數提供了另外一種辦法在指定的檔案描述符上打開檔案。

習題

1、在系統標頭檔中查找flagsmode參數用到的這些宏定義的值是多少。把這些宏定義按位或起來是什麼效果?為什麼必選項只能選一個而可選項可以選多個?

2、請按照下述要求分別寫出相應的open調用。

  • 打開檔案/home/akae.txt用於寫操作,以追加方式打開

  • 打開檔案/home/akae.txt用於寫操作,如果該檔案不存在則創建它

  • 打開檔案/home/akae.txt用於寫操作,如果該檔案已存在則截斷為0位元組,如果該檔案不存在則創建它

  • 打開檔案/home/akae.txt用於寫操作,如果該檔案已存在則報錯退出,如果該檔案不存在則創建它