Linux文件I/O(creat/open/read/write/lseek/close/dup/sync)

先來提一下文件標識符的概念,對內核而言,所有打開的文件都通過文件描述符引用。文件描述符是一個非負整數,打開或創建一個文件的時候,內核向進程返回它的描述符。我們要進行讀寫操作的時候,把這個描述符傳給read和write即可對文件內容進行操作。
按照慣例,UNIX系統shell把文件描述符0與進程的標準輸入關聯,1和標準輸出關聯,2和標準錯誤關聯。在unistd.h中有相關的STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO宏定義。文件描述符的變化範圍是0~OPEN_MAX-1。


open函數:

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

兩函數的返回值都是打開的文件描述符,如果打開失敗,返回-1。

path:文件名稱
oflag:選項
- - - 必選:
- O_RDONLY:只讀打開;
- O_WRONLY:只寫打開;
- O_RDWR:讀寫打開;
- O_EXEC:執行打開;
- O_SEARCH:搜索打開。
- - - 可選:
- O_APPEND:追加寫入,每次寫入的時候偏移量都會固定在最尾端,lseek和write組成了原子操作,每次寫入的時候都調用一次(所以lseek不管用了),而不是之前想的打開後lseek就不變了,以後只write(請看附1代碼);
- O_CREAT:創建文件時設定第三個參數mode,指定訪問權限;
- O_DIRECTORY:保證path是個目錄,如果不是目錄就會報錯;
- O_EXCL:將測試文件是否存在和創建文件組成原子操作,如果creat時文件已存在,出錯(這樣的設計就不會出現兩個進程同時創建覆蓋的後果了);
- O_NONBLOCK:如果path引用的是FIFO、塊特殊文件或字符特殊文件,將I/O操作設置爲非阻塞狀態;
- O_SYNC:使每次write等待霧裏I/O操作完成,包括由該write操作引起的文件屬性更新所需的I/O;
- O_TRUNC:用寫或讀寫方式打開時將文件長度截斷爲0(等同於創建覆蓋文件)。
// 所有的參數都在fcntl.h中進行了宏定義

fd參數把open和openat函數區分開,共有三種可能:
1. path參數指定的是絕對路徑,在這種情況下fd被忽略,openat就相當於open;
2. path參數指定的是相對路徑,fd指出了相對路徑名在文件系統中開始的位置(fd參數通過打開相對路徑名所在的目錄獲取的O_SEARCH | O_DIRECTORY);
3. path參數指定的是相對路徑,fd爲特殊值AT_FDCWD,此時就在工作目錄獲取,與open一樣。
【使用openat函數可以讓我們使用相對路徑去在不同的目錄進行工作,解決了多線程工作在不同目錄的問題】


creat函數:

int creat(const char *path, mode_t mode);
// open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
// 現在open添加了O_CREAT和O_TRUNC這些選項,也不再需要單獨的creat了

如果創建成功,返回文件描述符,出錯返回-1。

creat的不足是使用只寫的方式打開文件,如果要讀和寫同時進行,就只能進行切換,十分麻煩。現在可以使用open(path, O_RDWR | O_CREAT | O_TRUNC, mode)來實現。


close函數:

int close(int fd);

當一個進程終止的時候,內核自動關閉它所有打開的文件,很多程序利用這一點而不去主動close。


lseek函數:

off_t lseek(int fd, off_t offset, int whence);

如果成功,返回文件新的偏移量,否則返回-1。通常open一個文件都會將偏移量設置爲0,除非指定了O_APPEND選項。如果對FIFO或者套接字管道等設置偏移量,會返回-1並將errno設置爲ESPIPE。

whence參數:SEEK_SET, SEEK_CUR, SEEK_END

文件的偏移量是可以大於文件長度的,這樣對文件的下一次寫將會加長該文件,並在中間構成一個用0填充的空洞(雖然在存儲的時候跟文件系統壓縮方式有關,但它就是有那麼大)。


read函數:

ssize_t read(int fd, void *buf, size_t nbytes);

返回值是讀取到的字節數,如果已經到達文件的尾端,返回0。需要使用返回值來確定長度而不是nbytes,因爲有很多情況是讀不滿的。


write函數:

ssize_t write(int fd, void *buf, size_t nbytes);

返回值是成功寫入的字節數,如果出錯返回-1。與read不同,write返回值通常是與nbytes相等的,如果不相等則表示出了一些問題,如磁盤寫滿了,或者單進程文件寫太長了受限。


dup函數:

int dup(int fd);
int dup2(int fd1,int fd2);

兩個函數均爲複製一個現存的文件的描述。若成功返回值爲新的文件描述符,若出錯爲-1。由dup返回的新文件描述符一定是當前可用文件描述中的最小數值。用dup2則可以用fd2參數指定新的描述符數值。如果fd2已經打開,則先關閉。若fd1=fd2,則dup2返回fd2,而不關閉它。通常使用這兩個系統調用來重定向一個打開的文件描述符(構成選擇通道,篩選器等等)。

dup可以看作是close和fcntl的原子集合操作。

我們的系統還提供了/dev/fd目錄,這個目錄裏有數字0、1、2等文件,在程序中打開文件就相當於複製對應的描述符,注意此時的mode必須爲之前打開對應fd的mode的子集,不可越限。使用這種方法creat的時候可能會導致底層文件被截斷,因爲linux實現使用指向實際文件的符號鏈接。

fd = open("/dev/fd/0", O_RDONLY);

sync函數:

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

由於大多數磁盤I/O都採用了緩衝區寫入的辦法,I/O操作都是內核對緩衝區的寫入操作,只有在內核需要重用緩衝區來存放其他磁盤塊數據時,纔會把所有延遲寫數據塊寫入磁盤。這時就會出現一些一致性的問題,所以UNIX提供了sync、fsync和fdatasync三個函數將緩衝區文件內容寫入磁盤。通常系統守護進程update就是週期性的調用sync函數來保證定期flush緩衝區。

  1. sync:將所有修改過的塊緩衝區排入寫隊列,直接返回(不等待實際磁盤寫操作結束);
  2. fsync:只對fd一個文件起作用,並且等待寫操作結束後才返回,適用於數據庫等需要高度一致性的程序;
  3. fdatasync:與fsync相同,但它同時還會更新文件的屬性。

還有fnctl和ioctl函數可以執行多種操作,但由於太過複雜,並且功能大多使用前面的函數就可以完成,暫時不說了。詳情查看APUE-3.14,APUE-3.15。


附1代碼:append標識符實驗(不知爲何左鍵不能用了虛擬機,就直接截圖)
在這裏我是創建了一個文件,讓程序寫了兩次,可以看到每次write的時候seek纔會更新。
Code
代碼結果:
Result

發佈了34 篇原創文章 · 獲贊 7 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章