1. 程式人生 > >Unix/Linux程式設計-系統呼叫I/O

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還會同步更新檔案的屬性。