【Linux/Unix系統(tǒng)編程手冊(cè)筆記】文件I/O

1.文件描述符

所有執(zhí)行I/O操作的系統(tǒng)調(diào)用都以文件描述符(一個(gè)非負(fù)整數(shù))來指代打開的文件。文件描述符用以表示所有類型的已打開文件,包括管道FIFO、socket終端、設(shè)備、普通文件。它是一個(gè)索引值,指向內(nèi)核為每一個(gè)進(jìn)程所維護(hù)的該進(jìn)程打開文件的記錄表。當(dāng)程序打開一個(gè)現(xiàn)有文件或者創(chuàng)建一個(gè)新文件時(shí),內(nèi)核向進(jìn)程返回一個(gè)文件描述符。每個(gè)進(jìn)程,文件描述符都自成一套。

標(biāo)準(zhǔn)流(標(biāo)準(zhǔn)文件描述符)

3中標(biāo)準(zhǔn)的文件描述符:


image

當(dāng)linux啟動(dòng)后,會(huì)自動(dòng)打開三個(gè)文件,就是標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤。標(biāo)準(zhǔn)輸入流默認(rèn)是鍵盤,標(biāo)準(zhǔn)輸出流默認(rèn)是終端,向錯(cuò)誤流寫數(shù)據(jù),終端的默認(rèn)做法是打印出錯(cuò)誤內(nèi)容,當(dāng)然這些流可以更改的。

  • fprintf(stdout, "input someting") <=> printf("input someting")
    • 向標(biāo)準(zhǔn)輸出流(終端程序)輸出一個(gè)字符串
  • fscanf(stdin, "%d",&a) <=> scanf("%d", &a)
    • 向標(biāo)準(zhǔn)輸入流(鍵盤)讀入一個(gè)數(shù)據(jù)
  • fprintf(stderr, "a error occur")
    • 向標(biāo)準(zhǔn)錯(cuò)誤流寫入一個(gè)錯(cuò)誤信息

重定向標(biāo)準(zhǔn)流

  • ./demo.out 1>>a.txt 輸出流重定向
    • 將1代表的標(biāo)準(zhǔn)輸出流重定向(>>)到a.txt文件
    • ./demo.out 1>>a.txt 等價(jià)于 ./demo.out >>a.txt
    • ./demo.out >>a.txt 輸出流中的內(nèi)容是追加的,追加到結(jié)尾
    • ./demo.out >a.txt 輸出流中的內(nèi)容是覆蓋的,再次寫入會(huì)覆蓋之前的內(nèi)容
  • ./demo.out <a.txt 輸入流重定向

2.I/O模型

I/O的4個(gè)主要系統(tǒng)調(diào)用:

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

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

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

  • status = close(fd)

    • 文件描述符屬于有限資源,因此文件描述符關(guān)閉失敗可能會(huì)導(dǎo)致一個(gè)進(jìn)程將文件描述符資源消耗殆盡。

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

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

  • offset參數(shù)指定了一個(gè)以字節(jié)為單位的數(shù)值
  • whence參數(shù)則表明贏參照哪個(gè)基點(diǎn)來解釋offset參數(shù),應(yīng)為下列其中之一:
    • SEEK_SET:文件頭部開始
    • SEEK_CUR:當(dāng)前文件偏移量處
    • SEEK_END:文件結(jié)尾
    • image

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

ioctl()

ioctl()系統(tǒng)調(diào)用又為執(zhí)行文件和設(shè)備操作提供了一種多用途機(jī)制。

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

fcntl()

fcntl()系統(tǒng)調(diào)用對(duì)一個(gè)打開的文件描述符執(zhí)行一些列控制操作

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

5.原子操作和競(jìng)爭(zhēng)條件

原子操作:將某一系統(tǒng)調(diào)用所要完成的各個(gè)動(dòng)作作為不可中斷的操作,一次性加以執(zhí)行,期間不會(huì)為其他進(jìn)程或線程所中斷。所有的系統(tǒng)調(diào)用都是以原子操作方式執(zhí)行的。

舉例:當(dāng)同時(shí)制定O_EXCLO_CREAT作為open()標(biāo)志位時(shí),如果要打開已經(jīng)存在的文件,就會(huì)返回一個(gè)錯(cuò)誤,這提供了一種機(jī)制,對(duì)文件是否存在的檢查和創(chuàng)建文件屬于同一原子操作。區(qū)別于先檢查文件再創(chuàng)建可能會(huì)造成其他進(jìn)程在這個(gè)過程中搶占資源。

O_EXCL確保調(diào)用者就是文件的創(chuàng)建者。
O_APPEND標(biāo)志,確保多個(gè)進(jìn)程在對(duì)同一文件追加數(shù)據(jù)時(shí)不會(huì)覆蓋彼此的輸出。

6.打開文件的狀態(tài)標(biāo)志

獲取訪問模式和狀態(tài)標(biāo)志

fcntl()的用途之一是針對(duì)一個(gè)打開的文件,獲取或修改其訪問模式和狀態(tài)標(biāo)志(這些值是通過open()調(diào)用的flag參數(shù)設(shè)置的),應(yīng)將fcntl()的cmd參數(shù)設(shè)置為F_GETTFL,并且獲取的標(biāo)志中總是包含O_LARGEFILE標(biāo)志

flags = fcntl(fd, F_GETFL);

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

if (flags & O_SYNC)

判定文件的訪問模式稍微復(fù)雜一點(diǎn),因?yàn)镺_RDONLY(0) O_WRONLY(1) O_RDWR(2)這三個(gè)常量并不與打開文件狀態(tài)標(biāo)志中的單個(gè)比特位對(duì)應(yīng),需使用掩碼O_ACCMODE與flag相與

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

修改訪問模式和狀態(tài)標(biāo)志

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

  • O_APPEND
  • O_NONBLOCK
  • O_NOATIME
  • A_ASYNC
  • O_DIRECT

適用的場(chǎng)景:

  • 文件不是由調(diào)用程序打開的,所以無法使用open來控制這些標(biāo)志(文件是3個(gè)標(biāo)準(zhǔn)描述符,這些描述符在程序啟動(dòng)之前就被打開)
  • 文件描述符獲取是通過open之外的系統(tǒng)調(diào)用,比如pipe()、socket()

修改標(biāo)志位代碼如下:

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

7.文件描述符和打開文件之間的關(guān)系

文件描述符和打開的文件不一定是一一對(duì)應(yīng)的關(guān)系,多個(gè)文件描述符可以指向同一打開文件。這些文件描述符可在相同或不同的進(jìn)程中打開。

內(nèi)核維護(hù)的3個(gè)數(shù)據(jù)結(jié)構(gòu):

  • 進(jìn)程級(jí)的文件描述符表
  • 系統(tǒng)級(jí)的打開文件表
  • 文件系統(tǒng)的i-node表

針對(duì)每個(gè)進(jìn)程,內(nèi)核為其維護(hù)打開的文件描述符表,每一條記錄的相關(guān)信息:

  • 控制文件描述符操作的一組標(biāo)志
  • 對(duì)打開文件句柄的引用

內(nèi)核對(duì)所有打開的文件維護(hù)一個(gè)系統(tǒng)級(jí)的描述表格(打開文件表),并將表中各條目稱為打開文件句柄,一個(gè)打開文件句柄存儲(chǔ)了與一個(gè)打開文件相關(guān)的全部信息:

  • 當(dāng)前文件偏移量
  • 打開文件時(shí)所使用的狀態(tài)標(biāo)識(shí)(flags參數(shù))
  • 文件訪問模式(只讀只寫等)
  • 與信號(hào)驅(qū)動(dòng)I/O相關(guān)的設(shè)置
  • 對(duì)該文件i-node對(duì)象的引用

文件系統(tǒng)會(huì)為駐留其上的所有文件建立一個(gè)i-node表:

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

文件描述符、打開的文件句柄、i-node的關(guān)系:


image

總結(jié)以下要點(diǎn):

  • 不同文件描述符(1和2)可以指向同一打開文件句柄,可能是通過調(diào)用dup() dup2()或fcntl()形成的
  • 不同進(jìn)程文件描述符可以指向同一打開文件句柄,可能調(diào)用fork()出現(xiàn)
  • 不同的文件句柄指向同一i-node表?xiàng)l目,換言之,指向同一文件,可能因?yàn)槊總€(gè)進(jìn)程各自對(duì)同一文件發(fā)起了open調(diào)用
  • 兩個(gè)不同的文件描述符,若指向同一打開文件句柄,將共享同一文件偏移量
  • 文件描述符標(biāo)志(close_on_exec標(biāo)志)為進(jìn)程和文件描述符所私有

8.復(fù)制文件描述符

int dup(int oldfd)

dup()調(diào)用復(fù)制一個(gè)打開的文件描述符oldfd,并返回一個(gè)新描述符,二者都指向同一打開文件句柄。系統(tǒng)會(huì)保證新描述一定是編號(hào)值最低的未用文件描述符

int dup2(int oldfd, int newfd)

dup2()系統(tǒng)調(diào)用會(huì)為oldfd參數(shù)所指定的文件描述符創(chuàng)建副本,其編號(hào)由newfd參數(shù)指定,如果newfd已經(jīng)打開,那么dup2會(huì)將其先關(guān)閉

newfd = fcntl(oldfd, FU_DUPFD, startfd)

該調(diào)用為oldfd創(chuàng)建一個(gè)副本,且將使用大于等于startfd的最小未使用值作為描述符編號(hào)。

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

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

dup3與dup2相同,只是增加了一個(gè)附加參數(shù)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(),會(huì)直接設(shè)置offset參數(shù),是一個(gè)原子操作,且性能更好

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

readv()和writev()系統(tǒng)調(diào)用分別實(shí)現(xiàn)了分散輸入和集中輸出的功能

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

這些系統(tǒng)調(diào)用并非只對(duì)單個(gè)緩沖區(qū)進(jìn)行讀寫操作,而是一次即可傳輸多個(gè)緩存區(qū)的數(shù)據(jù)。數(shù)組iov定義了一組用來傳輸數(shù)據(jù)的緩沖區(qū)。iovcnt指定了iov的成員個(gè)數(shù),iov中的數(shù)據(jù)結(jié)構(gòu):

struct iovec {
  void *iov_base;
  size_t iov_len;
}

下圖展示關(guān)系:


image

分散輸入

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

集中輸出

將iov所指定的緩沖區(qū)中的數(shù)據(jù)拼接起來,然后寫入fd中。

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

preadv()、pwirtev()

11.截?cái)辔募簍runcate()和ftruncate()

truncate()和ftruncate()系統(tǒng)調(diào)用將文件大小設(shè)置為length指定長(zhǎng)度

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

若長(zhǎng)度大于length則丟棄超出部分,若小于length,則在文件尾追加一系列字節(jié)或一個(gè)文件空洞。

12.非阻塞I/O

打開文件時(shí)指定O_NONBLOCK標(biāo)志的作用:

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

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

由于內(nèi)核緩沖區(qū)保證了普通文件I/O陷入阻塞,故而打開普通文件會(huì)忽略O(shè)_NONBLOCK標(biāo)志

13.大文件I/O

LFS規(guī)范定義了一套擴(kuò)展功能,允許在32位系統(tǒng)中運(yùn)行的進(jìn)程來操作無法以32位表示的大文件。

14./dev/fd 目錄

對(duì)于每個(gè)進(jìn)程,內(nèi)核都提供一個(gè)特殊的虛擬目錄/dev/fd/n,n是與進(jìn)程中的打開文件描述符相對(duì)應(yīng)的編號(hào)。

打開/dev/fd目錄中的一個(gè)文件等同于復(fù)制相應(yīng)的文件描述符:

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

/dev/fd實(shí)際上是一個(gè)符號(hào)鏈接,鏈接到linux所專有的/proc/self/fd目錄

15.創(chuàng)建臨時(shí)文件

mkstemp()、tmpfile()

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容