【Linux/Unix系統編程手冊筆記】文件I/O

1.文件描述符

所有執行I/O操作的系統調用都以文件描述符(一個非負整數)來指代打開的文件。文件描述符用以表示所有類型的已打開文件,包括管道FIFOsocket終端設備普通文件。它是一個索引值,指向內核為每一個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。每個進程,文件描述符都自成一套。

標準流(標準文件描述符)

3中標準的文件描述符:


image

當linux啟動后,會自動打開三個文件,就是標準輸入、標準輸出、標準錯誤。標準輸入流默認是鍵盤,標準輸出流默認是終端,向錯誤流寫數據,終端的默認做法是打印出錯誤內容,當然這些流可以更改的。

  • fprintf(stdout, "input someting") <=> printf("input someting")
    • 向標準輸出流(終端程序)輸出一個字符串
  • fscanf(stdin, "%d",&a) <=> scanf("%d", &a)
    • 向標準輸入流(鍵盤)讀入一個數據
  • fprintf(stderr, "a error occur")
    • 向標準錯誤流寫入一個錯誤信息

重定向標準流

  • ./demo.out 1>>a.txt 輸出流重定向
    • 將1代表的標準輸出流重定向(>>)到a.txt文件
    • ./demo.out 1>>a.txt 等價于 ./demo.out >>a.txt
    • ./demo.out >>a.txt 輸出流中的內容是追加的,追加到結尾
    • ./demo.out >a.txt 輸出流中的內容是覆蓋的,再次寫入會覆蓋之前的內容
  • ./demo.out <a.txt 輸入流重定向

2.I/O模型

I/O的4個主要系統調用:

  • fd = open(pathname,flags,mode) 打開或創建一個新文件

    • flags標志
      • image
      • image
    • 位掩碼參數mode指定了新創建文件的權限,若open()并未指定O_CREAT標志,則忽略該參數
      • S_IRUSER
      • S_IWUSER
    • 返回文件描述符值
      • SUSv3規定,如果open()成功,必須保證其返回值為進程未用文件描述符中數值最小者,如果文件描述符0未使用,那么open一定會使用此文件描述符打開文件。
    • 錯誤處理
      • open()返回-1,錯誤號errno標識錯誤原因
      • EACCES
      • EISDIR
      • EMFILE
      • ENFILE
      • ENOENT
      • EROFS
      • ETXTBSY
  • numread = read(fd,buffer,count) 讀取fd所指代的文件中之多count字節的數據,并存儲到buffer中

    • count參數指定最多能讀取的字節數
    • buffer參數提供用來存放輸入數據的內存緩存地址
    • 返回
      • 遇到文件結束(EOF)則返回0
      • 出錯返回 -1
      • 正確返回存放讀取的字節數
  • numwritten = write(fd,buffer,count)

  • status = close(fd)

    • 文件描述符屬于有限資源,因此文件描述符關閉失敗可能會導致一個進程將文件描述符資源消耗殆盡。

3.改變文件偏移量:lseek()

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

  • offset參數指定了一個以字節為單位的數值
  • whence參數則表明贏參照哪個基點來解釋offset參數,應為下列其中之一:
    • SEEK_SET:文件頭部開始
    • SEEK_CUR:當前文件偏移量處
    • SEEK_END:文件結尾
    • image

4.通用I/O模型以外的操作:ioctl()、fcntl()

ioctl()

ioctl()系統調用又為執行文件和設備操作提供了一種多用途機制。

  • int ioctl(int fd, int request,...);
    • request指定了將在fd上執行的控制操作
    • 第三個參數...(argp)可以是任意數據類型,根據request的參數值來確定argp所期望的類型。通常情況,argp指向整數或結構的指針

fcntl()

fcntl()系統調用對一個打開的文件描述符執行一些列控制操作

  • int fcntl(intn fd, int cmd, ...)
    • cmd參數所支持的操作范圍很廣

5.原子操作和競爭條件

原子操作:將某一系統調用所要完成的各個動作作為不可中斷的操作,一次性加以執行,期間不會為其他進程或線程所中斷。所有的系統調用都是以原子操作方式執行的。

舉例:當同時制定O_EXCLO_CREAT作為open()標志位時,如果要打開已經存在的文件,就會返回一個錯誤,這提供了一種機制,對文件是否存在的檢查和創建文件屬于同一原子操作。區別于先檢查文件再創建可能會造成其他進程在這個過程中搶占資源。

O_EXCL確保調用者就是文件的創建者。
O_APPEND標志,確保多個進程在對同一文件追加數據時不會覆蓋彼此的輸出。

6.打開文件的狀態標志

獲取訪問模式和狀態標志

fcntl()的用途之一是針對一個打開的文件,獲取或修改其訪問模式和狀態標志(這些值是通過open()調用的flag參數設置的),應將fcntl()的cmd參數設置為F_GETTFL,并且獲取的標志中總是包含O_LARGEFILE標志

flags = fcntl(fd, F_GETFL);

要判斷是否包含某一標志位,只需要將flags于其相&即可。如下可以判斷文件是否以同步方式打開:

if (flags & O_SYNC)

判定文件的訪問模式稍微復雜一點,因為O_RDONLY(0) O_WRONLY(1) O_RDWR(2)這三個常量并不與打開文件狀態標志中的單個比特位對應,需使用掩碼O_ACCMODE與flag相與

accessMode = flags & O_ACCMODE
if (accessMode == O_WRONLY) {...}

修改訪問模式和狀態標志

使用fcntl()的F_SETFL來修改,允許更改的標志有:

  • O_APPEND
  • O_NONBLOCK
  • O_NOATIME
  • A_ASYNC
  • O_DIRECT

適用的場景:

  • 文件不是由調用程序打開的,所以無法使用open來控制這些標志(文件是3個標準描述符,這些描述符在程序啟動之前就被打開)
  • 文件描述符獲取是通過open之外的系統調用,比如pipe()、socket()

修改標志位代碼如下:

flags = fcntl(fd, F_GETFL)
flags |= O_APPEND
if(fcntl(fd, F_SETFL, flags) == -1) { errExit()}

7.文件描述符和打開文件之間的關系

文件描述符和打開的文件不一定是一一對應的關系,多個文件描述符可以指向同一打開文件。這些文件描述符可在相同或不同的進程中打開。

內核維護的3個數據結構:

  • 進程級的文件描述符表
  • 系統級的打開文件表
  • 文件系統的i-node表

針對每個進程,內核為其維護打開的文件描述符表,每一條記錄的相關信息:

  • 控制文件描述符操作的一組標志
  • 對打開文件句柄的引用

內核對所有打開的文件維護一個系統級的描述表格(打開文件表),并將表中各條目稱為打開文件句柄,一個打開文件句柄存儲了與一個打開文件相關的全部信息:

  • 當前文件偏移量
  • 打開文件時所使用的狀態標識(flags參數)
  • 文件訪問模式(只讀只寫等)
  • 與信號驅動I/O相關的設置
  • 對該文件i-node對象的引用

文件系統會為駐留其上的所有文件建立一個i-node表:

  • 文件類型(例如,普通文件、套接字、FIFO)和訪問權限
  • 一個指針,指向該文件所持有的鎖的列表
  • 文件的各種屬性,包括文件大小以及與不同類型操作相關的時間戳

文件描述符、打開的文件句柄、i-node的關系:


image

總結以下要點:

  • 不同文件描述符(1和2)可以指向同一打開文件句柄,可能是通過調用dup() dup2()或fcntl()形成的
  • 不同進程文件描述符可以指向同一打開文件句柄,可能調用fork()出現
  • 不同的文件句柄指向同一i-node表條目,換言之,指向同一文件,可能因為每個進程各自對同一文件發起了open調用
  • 兩個不同的文件描述符,若指向同一打開文件句柄,將共享同一文件偏移量
  • 文件描述符標志(close_on_exec標志)為進程和文件描述符所私有

8.復制文件描述符

int dup(int oldfd)

dup()調用復制一個打開的文件描述符oldfd,并返回一個新描述符,二者都指向同一打開文件句柄。系統會保證新描述一定是編號值最低的未用文件描述符

int dup2(int oldfd, int newfd)

dup2()系統調用會為oldfd參數所指定的文件描述符創建副本,其編號由newfd參數指定,如果newfd已經打開,那么dup2會將其先關閉

newfd = fcntl(oldfd, FU_DUPFD, startfd)

該調用為oldfd創建一個副本,且將使用大于等于startfd的最小未使用值作為描述符編號。

文件描述符的正、副本之間共享同一打開文件句柄所含的文件偏移量和狀態標志,新的文件描述符有其自己一套文件描述符標志,且其close-onexec標志(FD_CLOEXEC)總是處于關閉

int dup3(int oldfd, int newfd, int flags)

dup3與dup2相同,只是增加了一個附加參數flag

9.在文件特定偏移量出的I/O:pread()和pwrite()

ssize_t pread(int fd, void *buf, size_t count, off_t offset)
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset)

相比于read()和write(),會直接設置offset參數,是一個原子操作,且性能更好

10.分散輸入和集中輸出:readv()和writev()

readv()和writev()系統調用分別實現了分散輸入和集中輸出的功能

ssize_t readv(int fd, const struct iovec *iov, int iovcnt)
ssize_t writev(int fd, const struct iovec *iov, int iovcnt)

這些系統調用并非只對單個緩沖區進行讀寫操作,而是一次即可傳輸多個緩存區的數據。數組iov定義了一組用來傳輸數據的緩沖區。iovcnt指定了iov的成員個數,iov中的數據結構:

struct iovec {
  void *iov_base;
  size_t iov_len;
}

下圖展示關系:


image

分散輸入

從文件描述符fd所指代的文件中讀取一片連續的字節,然后將其散置于iov指定的緩沖區中,這一散置動作從iov[0]開始依次填滿每個緩沖區。是原子性操作。

集中輸出

將iov所指定的緩沖區中的數據拼接起來,然后寫入fd中。

在指定offset處分散輸入和集中輸出

preadv()、pwirtev()

11.截斷文件:truncate()和ftruncate()

truncate()和ftruncate()系統調用將文件大小設置為length指定長度

int truncate(const char *pathname, off_t length)
int ftruncate(int fd, off_t length)

若長度大于length則丟棄超出部分,若小于length,則在文件尾追加一系列字節或一個文件空洞。

12.非阻塞I/O

打開文件時指定O_NONBLOCK標志的作用:

  • 若open()未能立即打開文件,則返回錯誤,而非陷入阻塞
  • 調用open()成功后,后續I/O操作也是非阻塞的

由于管道、FIFO、套接字、設備都支持非阻塞模式,因無法通過open()設置標志,只能通過fcntl()的F_SETFL命令來修改。

由于內核緩沖區保證了普通文件I/O陷入阻塞,故而打開普通文件會忽略O_NONBLOCK標志

13.大文件I/O

LFS規范定義了一套擴展功能,允許在32位系統中運行的進程來操作無法以32位表示的大文件。

14./dev/fd 目錄

對于每個進程,內核都提供一個特殊的虛擬目錄/dev/fd/n,n是與進程中的打開文件描述符相對應的編號。

打開/dev/fd目錄中的一個文件等同于復制相應的文件描述符:

fd = open("/dev/fd/1", O_WRONLY)
fd = dup(1)

/dev/fd實際上是一個符號鏈接,鏈接到linux所專有的/proc/self/fd目錄

15.創建臨時文件

mkstemp()、tmpfile()

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容