一、提問:
- 如何使用C語言訪問UNIX系統下的文件?
- 內核是如何記錄文件打開的信息的?
- 使用中有什麼需要注意的地方?
1.1參考資料
- Linux/UNIX系統編程手冊(上冊) 第4章
1.2基本知識:
- 所有執行I/O操作的系統調用都以文件描述符,一個非負整數(通常是小整數),來指代打開的文件(類似於代號,別名)。
- 文件描述符用以表示所有類型的已打開的文件,包括管道(pipo),FIFO,socket,終端,設備和普通文件。針對每個進程,文件描述符都自成一套(進程間文件描述符是獨立的,互不干擾)。
- 程序中始終默認打開3個常用的文件描述符,使用中建議採用<unistd.h>所定義的POSIX標準名稱。
文件描述符 | 用 途 | POSIX名稱 | stdio流 |
0 | 標準輸入 | STDIN_FILENO | stdin |
1 | 標準輸出 | STDOUT_FILENO | stdout |
2 | 標準錯誤 | STDERR_FILENO | stder |
二、系統調用
2.1文件打開
int fd = open(const char* pathname, int flags, mode_t mode); //文件名,打開方式 權限
open 出錯時返回-1。反之,返回一個正數,表示文件描述符 fd,用以在後續函數中調用指代打開的文件。
打開對應文件,並由內核維護一個文件對象。以fd指代這個文件對象。
flags 對位掩碼參數,
1、O_RDONLY, O_WRONLY, O_RDWR 可以指定文件打開方式分別爲:只讀,只寫或讀寫。
2、O_CREAT 表示當文件不存在時,創建文件。
3、O_APPEND 表示總是在文件末尾追加數據。
4、O_NONBLOCK 表示以非阻塞方式打開
mode參數指定由open()調用創建文件的訪問權限,如果open()並未創建文件,可以忽略或省略mode參數。
1、新建文件的訪問權限還會受到進程的umask值和(可能存在的)父目錄的默認控制訪問列表的影響。
2、通常爲八進制,即 0777 來表示。
- 更多的flags參數——參考資料1 的 p60。
- open()函數錯誤——參考資料1 的 p63。
int creat(const char* pathname, mode_t mode)
成功返回文件描述符,失敗返回-1.
- 早期的UNIX實現中,open()只有兩個參數,無法創建新文件。通常使用creat()系統調用來創建並打開新文件。現已不多見。
2.2文件關閉
int close(int fd);
關閉文件描述符,釋放對應的文件對象
成功返回0,失敗返回-1
if(close(fd) == -1)
perror("close");
上述代碼能夠捕獲的錯誤:
1、企圖關閉一個未打開的文件描述符。
2、企圖兩次關閉同一個文件描述符。
3、特定文件系統在關閉操作中診斷出的錯誤條件
2.3文件的讀取
#include<unistd.h>
ssize_t read(int fd, void* buf, size_t count);
成功返回讀取的字節數,讀到EOF(文件結束或網絡通信中對端斷開)返回0,失敗返回-1.
參數buf 指定用來存放輸入數據的內存緩衝區地址,緩衝區應至少有count個字節。
參數count 指定最多能讀取的字節數。
- ssize_t 數據類型屬於有符號的整數類型, size_t 數據類型屬於無符號整數類型。
- 一次read()調用所讀取的字節數可以小於請求的字節數。默認情況下從終端讀取字符,一遇到換行符(\n),read()調用就會結束。
- read()調用在網絡通信,阻塞IO情況下,當對端斷開時,read返回0 。於此類似的還有 recv函數。
- read()能夠從文件中讀取任意序列的字節。可能是文本數據,也可能是二進制整數或二進制形式的C語言數據結構。故無法遵循C語言對字符串處理的約定,在字符串尾部追加標識字符串結束的空字符。所以,如果輸入緩衝區結尾需要一個表示終止的空字符,必須顯示追加;也可以在使用前,通過memset或bzero將緩衝區每個字符設置爲'\0' 。
使用案例:
char buffer[MAX_READ + 1];
ssize_t numRead;
numRead = read(STDIN_FILENO, buffer, MAX_READ);
if(-1 == numRead)
{
perror("read");
}
buffer[numRead] = '\0';
printf("The input data was: %s\n", buffer);
2.4 文件的寫入
#include<unistd.h>
ssize_t write(int fd, void* buf, size_t count);
成功返回實際寫入的字節數,失敗返回-1 。
參數fd 指代數據要寫入的文件。
參數buf 指定欲從buffer寫入文件的數據字節數。
參數count 指定最多能讀取的字節數。
- "部分寫" , write()調用返回值小於count參數值。對於磁盤文件,原因可能是磁盤已滿或者是進程資源對文件大小的限制(RLIMIT_FSIZE)。
- 與printf和scanf有所區別,read和write是將文件讀至緩衝區或從緩衝區寫入文件,是否會偏移呢?
a.讀寫過程中文件描述詞fd不會改變,始終指向文件。
b. read 過程中雖然是相同的文件描述詞,但是會存在一個偏移,不會讀取已經讀取過的文件內容。也就是說對於文件中存 放“HelloWorld”,調用兩次相同的read,第一次會讀進緩衝區“Hello”,第二次讀進緩衝區"World" 。緩衝區中內容會被覆蓋。
c. write過程類似,文件中會產生偏移,而對於緩衝區不會,兩次調用write,緩衝區中存放“HelloWorld”, 文件中會 是"HelloHello"。
d.write和read共用一個文件中的偏移。
2.5 改變文件偏移量
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
whence /wens/ adv 從哪裏;從何處。
offset參數 表示從whence開始的偏移量,單位爲字節數。
whence參數 可以取值爲 SEEK_SET,SEEK_CUR,SEEK_END,分別表示文件開頭,光標當前位置以及文件尾。
- 文件第一個字節的偏移量爲0 。
- SEEK_END指向文件末尾的最後一個字節的下一個字節。
- 文件空洞 (參考資料1 p68) 減少磁盤空間的佔用;與mmap聯合使用,加快將文件寫入文件的速度。
#include<unistd.h>
int ftruncate(int fd, off_t length);
成功返回0, 失敗返回-1。
將fd指定文件的大小,改爲參數length指定大小。
- fd參數必須表示的是打開的可寫入的文件描述詞。
- 如果原來文件比length大,則超過部分會被刪除。