Unix/Linux程式設計-系統呼叫I/O
系統呼叫I/O
1.1 檔案描述符
對於核心而言,所有開啟的檔案都通過檔案描述符引用。檔案描述符是一個非負整數,當開啟一個現有檔案或建立一個新檔案時,核心向程序返回一個檔案描述符。在符合POSIX.1的應用程式中,幻數0,1,2雖然已經被標準化,但應當把它們替換成符號常量STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO以提高可讀性。這些常量都在標頭檔案<unistd.h>中。1.2 開啟或建立一個檔案
#include <fcntl.h> 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引數用來說明函式的多個選項,用下列一個或多個常量進行或運算構成oflag引數。這些常量在<fcntl.h>中定義:
oflag |
說明 |
備註 |
O_RDONLY |
只讀開啟 |
|
O_WRONLY |
只寫開啟 |
|
O_RDWR |
讀寫開啟 |
|
O_EXEC |
只執行開啟 |
|
O_SEARCH |
只搜尋開啟(用於目錄) |
以上有且僅有一個存在,O_SEARCH常量的母的在於在目錄了開啟時驗證它的搜尋許可權。 |
O_APPEND |
將檔案偏移量設定到檔案末尾處 |
|
O_CLOEXEC |
把FD_CLOEXEC常量設定為檔案描述符標誌 |
|
O_CREAT |
檔案不存在則建立它。使用這個選項需要設定最後的mode引數 |
|
O_DIRECTORY |
Path不是目錄則出錯 |
|
O_EXCL |
如果同時指定了O_CREAT,而檔案已經存在則出錯,不存在則建立它。 |
|
O_NOCTTY |
如果path引用的是終端裝置,則不將該裝置分配作為此程序的控制終端。 |
|
O_NOFOLLOW |
如果path引用的是一個符號連結則出錯 |
|
O_NONBLOCK |
如果path引用的是一個FIFO、一塊特殊檔案或一個字元特殊檔案,則此選項為文字的本次開啟操作和後續I/O操作設定非阻塞方式。 |
|
O_SYNC |
使每次write等待物理I/O操作完成,包括由該write操作引起的檔案屬性更新所需的I/O |
|
O_DSYNC |
使每次write要等待物理I/O操作完成,但是如果該操作並不影響讀取剛寫入的資料,則不需要等待檔案屬性被更新。 |
當檔案用O_DSYN標誌開啟,在重寫其現有的部分內容時,檔案時間屬性不會同步更新。於此相反,如果檔案使用O_SYNC標誌開啟,那麼對該檔案的每一次write都將在write返回前更新檔案時間,這與是否改寫現有位元組或追加檔案無關。 |
O_TRUNC |
如果檔案存在,而且為只寫或讀寫成功開啟,則將其長度截斷為0 |
|
O_TTY_INIT |
如果開啟一個還未開啟的終端裝置,設定非標準termios引數值。 |
|
1.2.1 open與openat的區別
fd引數把open和openat函式區分開,有2種可能性。(1) path引數指定的是絕對路徑名,在這種情況下,fd引數被忽略,兩個函式沒有區別。
(2) path引數指定的是相對路徑,fd引數指出了相對路徑名在檔案系統中的開始地址,fd引數是通過開啟相對路徑名所在的路徑來獲取。
(3) path引數之指定了相對路徑名,fd引數具有特殊值AT_FDCWD。這種情況下,路徑名在當前目錄中獲取,openat函式在操作上與open函式類似。
1.3 改變檔案偏移量
每個開啟檔案都對應有檔案表項,紀錄了當前檔案偏移量,通常是非負整數,用以度量從檔案開始處計算的位元組數。通常,讀、寫操作都從當前檔案偏移量處開始,並使偏移量增加所讀寫的位元組數。 #include <unistd.h> off_t lseek(int fd, off_t offset, int whence); 返回值:成功返回新的檔案偏移量,錯誤返回-1 |
whence引數:
選項 |
說明 |
SEEK_SET |
檔案偏移量設定為檔案開始出offset個位元組 |
SEEK_CUR |
檔案偏移量設定為檔案當前值加offset個位元組,offset可正可負 |
SEEK_END |
檔案偏移量設定為檔案長度加offset個位元組,offset可正可負 |
檔案偏移量可以大於檔案的當前長度,對該檔案的下一次寫將加長該檔案,並在檔案中構成一個空洞。位於檔案中但沒有寫過的位元組都被讀為0。
檔案中的空洞並不要求在磁碟上佔用儲存區,具體處理方式與檔案系統的實現有關,當定位到超出檔案尾端之後寫時,對於新寫的資料需要分配磁碟塊,但對於原檔案尾端和新開始寫位置之前的部分則不需要分配磁碟塊。
1.4 讀取檔案資料
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t nbyts); 返回值:成功則返回已寫的位元組數,出錯返回-1 |
有多種情況可使實際讀到的位元組數少於要求讀的位元組數:
(1) 讀普通檔案,在讀到要求位元組數之前已到達檔案尾端。
(2) 讀終端裝置時,通常一次最多讀一行。
(3) 讀網路時,網路中的緩衝機制可能造成返回值小於要求讀的位元組數。
(4) 讀管道或FIFO時,若管道包含的位元組少於所需的數量,那麼read將只返回可用的位元組數。
(5) 當訊號中斷,而已經讀了部分資料量。
1.5 向檔案寫資料
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t nbyts); 返回值:成功則返回已寫的位元組數,出錯返回-1 |
返回值通常與引數nbytes的值相同,否則表示出錯。磁碟已滿或超過一個給定程序的檔案長度限制都可能導致出錯。在一次成功寫之後,檔案偏移量增加實際寫的位元組數。
1.6 檔案共享
核心使用了3種資料結構來表示開啟檔案。分別是開啟檔案描述符表、檔案表和v節點(i節點)。(1) 每個程序在程序表中都有一個記錄項,記錄項中包含一張開啟檔案描述符表,每個描述符佔用一項,與每個檔案描述符相關聯的是:
a. 檔案描述符標誌(close_on_exec)
b. 指向一個檔案表項的指標
(2) 核心為所有開啟檔案維持一張檔案表,每個檔案表項包含:
a. 檔案狀態標誌(讀、寫、添寫、同步和非阻塞等)
b. 當前檔案偏移量
c. 指向該檔案v節點的指標。
(3) 每個開啟檔案(或裝置)都有一個v節點結構,v節點包含了檔案型別和對此檔案進行各種操作函式的指標。對於大多數檔案,v節點還包含了該檔案的i節點。Linux中沒有v節點,而是使用了通用的i節點結構。
下圖展示了一個程序對應3張表之間的關係:
如果兩個獨立程序各自開啟 了同一個檔案,關係入下所示:
1.7 原子操作
原子操作指的是由多不組成的一個操作,如果該操作原子執行,則要麼執行完所有步驟,要麼都不執行。Single UNIX Specification包括了XSi擴充套件,該擴充套件允許原子性地定位並執行I/O。pread和pwrite就是這種擴充套件。
1.7.1 pread和pwrite函式
#include <unistd.h> ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset); 返回值:讀到的位元組數,若到檔案尾則返回0,出錯返回-1 ssize_t pwite(int fd, const void *buf, size_t nbytes, off_t offset); 返回值:返回已寫的位元組數,錯誤返回-1 |
與read和write不同的是,無法終端其定位和讀(寫)操作。
1.7.2 建立一個檔案
對於open函式,同時指定O_CREAT和O_EXCL選項時,而該檔案已經存在則open失敗,否則會原子操作地建立並開啟檔案。1.8 複製現有的檔案描述符
#include <unistd.h> int dup(int fd); int dup2(int fd, int fd2); 返回值:成功則返回新的檔案描述符;錯誤返回-1。 |
dup函式返回的新檔案描述符一定是當前可用檔案描述符中最小的整數。dup2函式使用fd2引數指定新描述符的值。如果fd2已經開啟,則會先將其關閉(不報錯)。如果fd2等於fd,返回fd2,而不關閉它。這些函式返回的新檔案描述符與引數fd共享同一個檔案表項:
複製現有的檔案描述符的另一種方式fcntl(fd, F_DUPFD, fd2)。
1.9 函式fcntl
fcntl函式可以改變已經開啟檔案的屬性。 #include <fcntl.h> int fcntl(int fd, int cmd,… /* int arg */); 返回值:成功,則依賴於cmd,出錯則返回-1 |
fcntl函式有一下5個作用:
(1) 複製一個已有的檔案描述符 (cmd=F_DUPFD 或 F_DUPFD_CLOEXEC )。
(2) 獲取/設定檔案描述符標誌(cmd=F_GETFD 或 F_SETFD)。
(3) 獲取/設定檔案狀態標誌(cmd=F_GETFL 或 F_SETFL)。
(4) 獲取/設定非同步I/O所有權(cmd=F_GETOWN 或 F_SETOWN)。
(5) 獲取/設定記錄鎖(cmd=F_GETFK、F_SETLK或F_SETLKW)。
Cmd |
說明 |
F_DUPFD |
複製檔案描述符fd。新檔案描述符作為函式值返回。它是尚未開啟的各描述符中大於或等於第3個引數值中的最小值。 |
F_DUPFD_CLOEXEC |
複製檔案描述符,設定與新描述符關聯的FD_CLOEXEC檔案描述符標誌的值,返回新檔案描述符。 |
F_GETFD |
對應於fd的 檔案描述符標誌作為函式的返回值,當前只有FD_CLOEXEC這個標誌。 |
F_SETFD |
對應fd設定檔案描述符的標誌。,新的值按第三個引數設定。 |
F_GETFL |
對應fd的檔案狀態標誌作為函式值返回。遺憾的是O_RDONLY,O_WRONLY,O_RDWR,O_EXEC,O_SEARCH這五個標誌並不是各佔一位,因此首先使用遮蔽字O_ACCMODE取得訪問方式位,然後將結果與5個值做相等比較,其他的標誌與函式返回值做與運算。 |
F_SETFL |
將檔案狀態標誌設定為第三個引數的值,可以更改的標誌是:O_APPEND,O_NONBLOCK,O_SYNC,O_DSYNC,O_RSYNC,O_FSYNC和O_ASYNC。 |
F_GETOWN |
獲取當前接收SIGIO和SIGURG訊號的程序ID或程序組ID。 |
F_SETOWN |
設定接收SIGIO和SIGURG訊號的程序ID或程序組ID,正的arg表示一個程序ID,負的 表示等於絕對值的一個程序組ID。 |
1.10 sync、fsync和fdatasync函式
核心中設有緩衝區快取記憶體或頁快取記憶體,大多數磁碟I/O都通過緩衝區進行。當我們向檔案寫入資料時,核心通常先將資料複製到緩衝區,然後排入佇列,晚些再寫入磁碟。這種方式稱為延遲寫。通常,當核心需要重用緩衝區來存放其他磁碟塊資料時,會把所有延遲寫資料寫入磁碟。為了保證磁碟上實際檔案系統與緩衝區中內容的一致性,Unix提供了這三個函式。
#include <unistd.h> int fsync(int fd); int fdatasync(int fd); 返回值:成功返回0,出錯返回-1 void sync(void); |
sync只是將所有修改過的塊緩衝區排入寫佇列,然後就返回,並不等待實際寫磁碟操作結束。通常稱為update的系統守護程序週期性的呼叫sync函式定期沖洗核心的塊緩衝區。
fsync函式只對由檔案描述符fd指定的一個檔案起作用,並且等待寫磁碟結束才返回。
fdatasync函式類似於fsync,但他隻影響檔案的資料部分,而除了資料外,fsync還會同步更新檔案的屬性。