linux系統(tǒng)編程-day06-文件IO(1)

  • Linux遵循一切皆文件的理念担猛,任何你能讀寫的東西都可以用文件描述符來訪問松蒜。

內(nèi)核為每個進程維護一個打開文件的列表,該表被稱為文件表(file table)读串。該表由一些叫做文件描述符(file descriptors)(沉募牵縮寫為fds)的非負整數(shù)進行索引撒妈。
用戶空間和內(nèi)核空間都把文件描述符作為每個進程的唯一cookies。

  • 每個進程按照慣例會至少有三個打開的文件描述符:0:標準輸入(stdin)排监,1:標準輸出(stdout)狰右,2:標準錯誤(stderr)。C標準庫提供了預(yù)處理器宏:STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO宏舆床,以取代對以上整數(shù)的直接引用棋蚌。
  • 文件描述符不僅僅用于普通文件的訪問,也用于訪問設(shè)備文件挨队、管道谷暮、目錄以及快速用戶空間鎖、FIFOs和套接字瞒瘸。

打開文件

最基本的訪問文件的方法是read( )和write( )系統(tǒng)調(diào)用坷备,在一個文件能被訪問之前,必須通過open( )或者creat( )系統(tǒng)調(diào)用打開它情臭,一旦使用完畢,就應(yīng)該用close( )系統(tǒng)調(diào)用來關(guān)閉文件赌蔑。

  • open( )系統(tǒng)調(diào)用
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *name, int flags);
int open(const char *name, int flags, mode_t mode);

open( )系統(tǒng)調(diào)用將路徑名name給出的文件與一個成功返回的文件描述符想關(guān)聯(lián)俯在,文件位置指針被設(shè)定為零,而文件則根據(jù)flags給出的標志位打開娃惯。

  • flags參數(shù)必須是以下之一:O_RDONLY(只讀), O_WRONLY(只寫), O_RDWR(可讀可寫)
  • flags參數(shù)可以和以下一個或多個值進行按位或運算跷乐, 用以修改打開文件請求的行為:
  1. O_APPEND: 文件以追加模式打開。在每次寫操作之前趾浅,文件位置指針將被置于文件末尾愕提。
  2. O_ASYNC: 當(dāng)指定文件可寫或者可讀時產(chǎn)生一個信號(默認是SIGIO),這個標志僅用于終端和套接字皿哨,不能用于普通文件浅侨。
  3. O_CREAT: 當(dāng)name指定的文件不存在時,將由內(nèi)核來創(chuàng)建证膨。
  4. O_DIRECT: 用于直接IO如输。
  5. O_DIRECTORY: 如果name不是一個目錄,open( )調(diào)用將會失敗央勒。這個標志用于在opendir( )內(nèi)部使用不见。
  6. O_EXCL: 和O_CREAT一起給出的時候,如果由name給定的文件已經(jīng)存在崔步,則open( )調(diào)用失敗稳吮。用來防止文件創(chuàng)建時出現(xiàn)競爭。
  7. O_LARGEFILE: 給定文件打開時將使用64位偏移量井濒,這樣大于2G的文件也能被打開灶似。
  8. O_NOCTTY: 如果name指向一個終端設(shè)備(/dev/tty)列林,它將不會成為這個進程的控制終端,即時該進程目前沒有控制終端喻奥。
  9. O_NOFOLLOW: 如果name是一個符號鏈接席纽,open( )調(diào)用會失敗。
  10. O_NONBLOCK: 如果可以撞蚕,文件將在非阻塞模式下打開润梯。open( )調(diào)用不會,任何其它操作都不會使該進程
  11. O_SYNC: 打開文件用于同步IO甥厦。
  12. O_TRUNC: 如果文件存在纺铭,且為普通文件,并允許寫刀疙,將文件的長度截斷為0舶赔。
  • 新文件所有者
    文件所有者的用戶ID就是創(chuàng)建該文件的進程的有效用戶id。
    創(chuàng)建文件的進程的組id賦予該文件的用戶組谦秧。

  • 新文件權(quán)限
    除非創(chuàng)建了新文件竟纳,否則mode參數(shù)會被忽略。而當(dāng)O_CREAT給出時則必須提供mode參數(shù)疚鲤。

  • 如果在使用O_CREAT時忘記了提供mode參數(shù)锥累,結(jié)果是未定義的,會很糟糕!

當(dāng)文件創(chuàng)建時,mode參數(shù)提供新建文件的權(quán)限客们。mode參數(shù)是常見的Unix權(quán)限位集合居灯,像八進制數(shù)0644(所有者可以讀寫,其他人只能讀)。例如:

int fd;
fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP | S_IROTH);
if (fd == -1)
    /* error */
  • creat( ) 函數(shù)
    O_WRONLY | O_CREAT | O_TRUNC組合經(jīng)常被使用,以至于專門有個系統(tǒng)調(diào)用來實現(xiàn):
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat (const char *name, mode_t mode);

對creat的調(diào)用:

int fd;
fd = creat(file, 0644);
if (fd == -1)
    /* error */

等價于:

int fd;
fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1)
   /* error */
  • 返回值和錯誤碼
    open( )和creat( )系統(tǒng)調(diào)用成功時都返回一個fd,錯誤時都返回-1鹅心,并且將errno設(shè)置為一個合適的錯誤值。

用read( )讀取文件

最基本它掂、最常見的讀取文件的機制是使用read( )系統(tǒng)調(diào)用:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t len);

該系統(tǒng)調(diào)用從fd指向的文件的當(dāng)前偏移量至多讀len個字節(jié)到buf中巴帮。成功時,將返回寫入buf中的字節(jié)數(shù)虐秋。出錯時榕茧,返回-1,并設(shè)置errno。

  • 一次讀后客给,fd所指文件位置指針將會向前移動用押,移動的長度由之前讀取的字節(jié)數(shù)決定。(如果無法在該文件(比如一個字符設(shè)備文件)中確定文件位置靶剑,讀操作總是從“當(dāng)前”位置開始)

  • 返回值
    返回值可能有以下結(jié)果:

  1. 等于len蜻拨。則與預(yù)期一致
  2. 大于0但是小于len池充。合法,讀取的字節(jié)存入buf中缎讼。
  3. 返回0收夸。標志著EOF,沒有可以讀入的數(shù)據(jù)
  4. 返回-1血崭,errno被設(shè)置為EINTR卧惜。表示在讀入字節(jié)之前收到了一個信號,可以重新進行調(diào)用夹纫。
  5. 返回-1咽瓷, errno被設(shè)置為EAGAIN。這表示讀取會因為沒有可用的數(shù)據(jù)而阻礙舰讹,而讀請求應(yīng)該在之后重開茅姜。這只在非阻塞模式下發(fā)生。
  6. 返回-1月匣, errno被設(shè)置不同于EINTR或EAGAIN的值钻洒。這表示某種更嚴重的錯誤。
  • 讀入所有的字節(jié)
    讀入所有l(wèi)en個字節(jié)(至少讀到EOF)的一個例子:
ssize_t ret;
while (len != 0 && (ret = read(fd, buf, len) != 0)) {
  if (ret == -1) {
    if (errno == EINTR)
      continue;
    perror("read");
    break;
  }
  len -= ret;
  buf += ret;
}
  • 非阻塞讀
    有時候锄开,程序員不希望當(dāng)沒有可讀數(shù)據(jù)時讓read( )調(diào)用阻塞航唆。相反,他們傾向于在沒有可讀數(shù)據(jù)時院刁,讓調(diào)用立即返回。這種情況被稱為非阻塞I/O粪狼。
  • 如果給定的fd在非阻塞模式下打開(open( )中給定O_NONBLOCK)并且沒有可讀數(shù)據(jù)退腥,
    read( )調(diào)用會返回-1,且設(shè)置errno為EAGAIN而不是阻塞掉。
  • 在進行非阻塞I/O時再榄,必須檢查EAGAIN狡刘,否則將可能因數(shù)據(jù)缺失而導(dǎo)致嚴重的錯誤。
char buf[BUFSIZE];
ssize_t nr;
start:
  nr = read(fd, buf, BUFSIZE);
  if (nr == -1) {
    if (errno == EINTR)
      goto start; 
    if (errno == EAGAIN)
      /* resubmit later */
    else
      /* error */
  }
  • read( )大小限制
    在32系統(tǒng)上困鸥,size_t是unsigned int類型嗅蔬,ssize_t是有符號的size_t類型。
    size_t的最大值為SIZE_MAX;ssize_t的最大值為SSIZE_MAX疾就。如果len比SSIZE_MAX大澜术,read( )調(diào)用的結(jié)果是未定義的。
  • 在大部分linux系統(tǒng)上猬腰,SSIZE_MAX是LONG_MAX,在32位系統(tǒng)上即0x7fffffff鸟废。
  • 可增加如下代碼:
if (len > SSIZE_MAX)
  len = SSIZE_MAX;
  • 一個len為0的read( )調(diào)用的結(jié)果是立即返回且返回值為0.

用write( )來寫

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

一個write( )調(diào)用從由文件描述符fd引用文件的當(dāng)前位置開始,將buf中至多count個字節(jié)寫入文件姑荷。

  • 對于普通文件來說盒延,除非發(fā)生一個錯誤缩擂,否則write( )將保證寫入所有的請求。
  • 對于其他類型的文件--例如套接字--得有個循環(huán)來保證你真的寫入了所有請求的字節(jié)添寺。使用循環(huán)的另一個好處是胯盯,第二個write( )調(diào)用可能會返回一個錯誤值用來說明第一次調(diào)用為什么進行了一次部分寫(盡管這種情況并不常見)。以下是一個實例代碼:
ssize_t ret, nr;
while (len != 0 && (ret = write(fd, buf, len)) != 0) {
  if (ret == -1) {
    if (errno == EINTR)
      continue;
    perror("write");
    break;
  }
  len -= ret;
  buf += ret;
}
  • 追加模式
    打開文件時计露,open( )通過指定O_APPEND參數(shù)博脑,則在追加模式下打開。寫操作就總是從當(dāng)前文件末尾開始薄坏。

  • 追加寫模式可使多個進程訪問同個文件不出錯趋厉。

  • ** 非阻塞寫**
    當(dāng)fd在非阻塞模式下打開時(通過設(shè)置O_NONBLOCK參數(shù)),并且發(fā)起的寫操作會正常阻塞時胶坠,write( )系統(tǒng)調(diào)用返回-1君账,并設(shè)置errno值為EAGAIN。請求應(yīng)該在稍后重新發(fā)起沈善。通常普通文件不會出現(xiàn)這種情況乡数。

  • write( )大小限制
    如果count比SSIZE_MAX還大,write( )調(diào)用的結(jié)果是未定義的闻牡。count值為0的write( )調(diào)用將會立即返回且返回值為0净赴。

  • write( )的行為
    write是先寫入page cache后就立刻返回了,然后由內(nèi)核的后臺線程來writeback所有的"臟"緩沖區(qū)罩润,將它們排好序玖翅,并寫入到磁盤上。

同步IO

由于一般都使用page cache & writeback的機制割以,然而又有應(yīng)用想要控制數(shù)據(jù)寫入磁盤的時間金度,于是Linux提供了同步機制,用性能來換取同步操作严沥。

  1. fsync( )
#include <unistd.h>
int fsync(fd);

調(diào)用fsync( )可保證fd對應(yīng)文件的臟數(shù)據(jù)回寫到磁盤上猜极。

  1. fdatasync( ):
#include <unistd.h>
int fdatasync(fd);

和fsync( )的區(qū)別在于,它僅僅寫入數(shù)據(jù)消玄,不保證元數(shù)據(jù)同步到磁盤上跟伏。

  • 注:這兩個調(diào)用都不保證任何已經(jīng)更新的包含該文件的目錄項同步到磁盤,如果要保證目錄項也同步翩瓜,必須對目錄本身也調(diào)用fsync( )進行同步受扳。
  1. sync( ):
#include <unistd.h>
void sync(void);

sync( )用來對磁盤上的所有緩沖區(qū)進行同步!

  • sync( )沒有參數(shù)奥溺,也沒有返回值辞色。它總是成功返回,并確保所有的緩沖區(qū)--包括數(shù)據(jù)和元數(shù)據(jù)--都能寫入磁盤。
  • sync( )并非一直等待到所有緩沖區(qū)都寫到磁盤才返回相满,只需要調(diào)用它來啟動將整個緩沖區(qū)寫入磁盤的過程即可层亿。
  1. O_SYNC標志
    O_SYNC標志在open( )中使用,使所有在文件上的I/O操作同步立美。例如:
int fd;
fd = open(file, O_WRONLY | O_SYNC);
if (fd == -1){
  perror("open");
  return -1;
}
  • O_SYNC看起來像是在每個write( )操作后都隱式地執(zhí)行fsync( ).
  • O_SYNC會使總耗時增加一到兩個數(shù)量級
  • 如果一般要確保數(shù)據(jù)寫入磁盤的應(yīng)用可以使用fsync( )或者fdatasync( )匿又。相比O_SYNC來說,開銷更小一點建蹄。
  1. O_DSYNC和O_RSYNC
  • O_DSYNC:只有普通數(shù)據(jù)被同步(與fdatasync( )類似)
  • O_RSYNC: 保證讀請求同步碌更。該標志只能和O_SYNC或O_DSYNC一起使用。O_RSYNC保證每次讀操作后洞慎,元數(shù)據(jù)也立刻更新痛单。

直接IO

由于Linux內(nèi)核實現(xiàn)了一個復(fù)雜的緩存、緩沖以及設(shè)備和應(yīng)用之間的I/O管理的層次結(jié)構(gòu)劲腿。通過O_DIRECT標志使內(nèi)核最小化I/O管理的影響旭绒。

  • 使用O_DIRECT標志時,I/O操作將忽略page cache機制焦人,直接對用戶空間緩沖區(qū)和設(shè)備進行初始化挥吵,所有的I/O將是同步的,操作在完成前不會返回花椭。
  • 當(dāng)使用直接I/O時忽匈,請求長度,緩沖區(qū)對齊矿辽,和文件偏移必須是設(shè)別扇區(qū)大械ぴ省(通常是512字節(jié))的整數(shù)倍。

關(guān)閉文件

程序完成對某個文件的操作后袋倔,可以使用close( )系統(tǒng)調(diào)用將文件描述符和對應(yīng)的文件解除關(guān)聯(lián)嫌松。

#include <unistd.h>
int close(int fd);

close( )調(diào)用解除了已打開的文件描述符的關(guān)聯(lián),并分離進程和文件的關(guān)聯(lián)奕污。

  • 關(guān)閉文件和文件被寫入磁盤沒有關(guān)系
  • 關(guān)閉文件也有些副作用:fd關(guān)閉后,在內(nèi)核中表示該文件的數(shù)據(jù)結(jié)構(gòu)就被釋放了液走。當(dāng)它釋放時碳默,與文件關(guān)聯(lián)的inode的內(nèi)存拷貝被清除.
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缘眶,隨后出現(xiàn)的幾起案子嘱根,更是在濱河造成了極大的恐慌,老刑警劉巖巷懈,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件该抒,死亡現(xiàn)場離奇詭異,居然都是意外死亡顶燕,警方通過查閱死者的電腦和手機凑保,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門冈爹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人欧引,你說我怎么就攤上這事频伤。” “怎么了芝此?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵憋肖,是天一觀的道長。 經(jīng)常有香客問我婚苹,道長岸更,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任膊升,我火速辦了婚禮怎炊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘用僧。我一直安慰自己结胀,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布责循。 她就那樣靜靜地躺著糟港,像睡著了一般。 火紅的嫁衣襯著肌膚如雪院仿。 梳的紋絲不亂的頭發(fā)上秸抚,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音歹垫,去河邊找鬼剥汤。 笑死,一個胖子當(dāng)著我的面吹牛排惨,可吹牛的內(nèi)容都是我干的吭敢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼暮芭,長吁一口氣:“原來是場噩夢啊……” “哼鹿驼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辕宏,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤畜晰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瑞筐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凄鼻,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了块蚌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闰非。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖匈子,靈堂內(nèi)的尸體忽然破棺而出河胎,到底是詐尸還是另有隱情,我是刑警寧澤虎敦,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布游岳,位于F島的核電站,受9級特大地震影響其徙,放射性物質(zhì)發(fā)生泄漏胚迫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一唾那、第九天 我趴在偏房一處隱蔽的房頂上張望访锻。 院中可真熱鬧,春花似錦闹获、人聲如沸期犬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽龟虎。三九已至,卻和暖如春沙庐,著一層夾襖步出監(jiān)牢的瞬間鲤妥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工拱雏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棉安,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓铸抑,卻偏偏與公主長得像贡耽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鹊汛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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