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

1.文件描述符

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

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

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


image

當(dāng)linux啟動后,會自動打開三個文件沛鸵,就是標(biāo)準(zhǔn)輸入括勺、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯誤曲掰。標(biāo)準(zhǔn)輸入流默認(rèn)是鍵盤疾捍,標(biāo)準(zhǔn)輸出流默認(rèn)是終端,向錯誤流寫數(shù)據(jù)栏妖,終端的默認(rèn)做法是打印出錯誤內(nèi)容乱豆,當(dāng)然這些流可以更改的。

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

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

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

2.I/O模型

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

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

    • 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一定會使用此文件描述符打開文件。
    • 錯誤處理
      • open()返回-1,錯誤號errno標(biāo)識錯誤原因
      • EACCES
      • EISDIR
      • EMFILE
      • ENFILE
      • ENOENT
      • EROFS
      • ETXTBSY
  • numread = read(fd,buffer,count) 讀取fd所指代的文件中之多count字節(jié)的數(shù)據(jù)勇边,并存儲到buffer中

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

  • status = close(fd)

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

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

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

  • offset參數(shù)指定了一個以字節(jié)為單位的數(shù)值
  • whence參數(shù)則表明贏參照哪個基點來解釋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è)備操作提供了一種多用途機制。

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

fcntl()

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

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

5.原子操作和競爭條件

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

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

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

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

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

fcntl()的用途之一是針對一個打開的文件藐俺,獲取或修改其訪問模式和狀態(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ù)雜一點吟吝,因為O_RDONLY(0) O_WRONLY(1) O_RDWR(2)這三個常量并不與打開文件狀態(tài)標(biāo)志中的單個比特位對應(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

適用的場景:

  • 文件不是由調(diào)用程序打開的剑逃,所以無法使用open來控制這些標(biāo)志(文件是3個標(biāo)準(zhǔn)描述符浙宜,這些描述符在程序啟動之前就被打開)
  • 文件描述符獲取是通過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)系

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

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

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

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

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

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

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

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

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

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


image

總結(jié)以下要點:

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

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

int dup(int oldfd)

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

int dup2(int oldfd, int newfd)

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

newfd = fcntl(oldfd, FU_DUPFD, startfd)

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

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

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

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

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

readv()和writev()系統(tǒng)調(diào)用分別實現(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)用并非只對單個緩沖區(qū)進(jìn)行讀寫操作央串,而是一次即可傳輸多個緩存區(qū)的數(shù)據(jù)。數(shù)組iov定義了一組用來傳輸數(shù)據(jù)的緩沖區(qū)碗啄。iovcnt指定了iov的成員個數(shù)质和,iov中的數(shù)據(jù)結(jié)構(gòu):

struct iovec {
  void *iov_base;
  size_t iov_len;
}

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


image

分散輸入

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

集中輸出

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

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

preadv()仗阅、pwirtev()

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

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

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

若長度大于length則丟棄超出部分,若小于length国夜,則在文件尾追加一系列字節(jié)或一個文件空洞减噪。

12.非阻塞I/O

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

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

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

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

13.大文件I/O

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

14./dev/fd 目錄

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

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

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

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

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

mkstemp()歹河、tmpfile()

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掩浙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子秸歧,更是在濱河造成了極大的恐慌厨姚,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件键菱,死亡現(xiàn)場離奇詭異谬墙,居然都是意外死亡,警方通過查閱死者的電腦和手機经备,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門拭抬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侵蒙,你說我怎么就攤上這事造虎。” “怎么了纷闺?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵算凿,是天一觀的道長份蝴。 經(jīng)常有香客問我,道長澎媒,這世上最難降的妖魔是什么搞乏? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮戒努,結(jié)果婚禮上请敦,老公的妹妹穿的比我還像新娘。我一直安慰自己储玫,他們只是感情好侍筛,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撒穷,像睡著了一般匣椰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上端礼,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天禽笑,我揣著相機與錄音,去河邊找鬼蛤奥。 笑死佳镜,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凡桥。 我是一名探鬼主播蟀伸,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缅刽!你這毒婦竟也來了啊掏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤衰猛,失蹤者是張志新(化名)和其女友劉穎迟蜜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體啡省,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡娜睛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了冕杠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡酸茴,死狀恐怖分预,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情薪捍,我是刑警寧澤笼痹,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布配喳,位于F島的核電站,受9級特大地震影響凳干,放射性物質(zhì)發(fā)生泄漏晴裹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一救赐、第九天 我趴在偏房一處隱蔽的房頂上張望涧团。 院中可真熱鬧,春花似錦经磅、人聲如沸泌绣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阿迈。三九已至,卻和暖如春轧叽,著一層夾襖步出監(jiān)牢的瞬間苗沧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工炭晒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留待逞,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓腰埂,卻偏偏與公主長得像飒焦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子屿笼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容