![[Linux系統(tǒng)] 一切皆文件 | 緩沖區(qū)機(jī)制 | FILE 結(jié)構(gòu)](http://pic.xiahunao.cn/yaotu/[Linux系統(tǒng)] 一切皆文件 | 緩沖區(qū)機(jī)制 | FILE 結(jié)構(gòu))
先說結(jié)論一切皆文件不是口號而是內(nèi)核把每個設(shè)備都裝進(jìn)struct file再用file_operations里的函數(shù)指針做回調(diào)于是開發(fā)者拿著一個 fd 就能讀文件、讀系統(tǒng)狀態(tài)、讀管道緩沖區(qū)是一塊內(nèi)存讓低速外設(shè)和高速 CPU 協(xié)調(diào)工作——它由 C 標(biāo)準(zhǔn)庫提供所以庫函數(shù)有緩沖、write沒有后面兩個怪現(xiàn)象都源于此。一、一切皆文件Linux 的終極抽象在 Windows 里是文件的在 Linux 里也是文件Windows 里不是文件的進(jìn)程、磁盤、顯示器、鍵盤在 Linux 里同樣被抽象成文件可以用訪問文件的方式取到信息。管道是文件將來網(wǎng)絡(luò)編程里的 socket接口也和文件一致。好處很直接一套 API 通吃。幾乎所有讀都能用read讀文件、讀系統(tǒng)狀態(tài)、讀管道幾乎所有改都能用write改文件、改系統(tǒng)參數(shù)、寫管道。二、內(nèi)核憑什么做到struct file file_operations打開文件時操作系統(tǒng)會為它創(chuàng)建一個file結(jié)構(gòu)體定義在include/linux/fs.h它同時屬于所有設(shè)備structfile{structinode*f_inode;conststructfile_operations*f_op;// 函數(shù)指針表atomic_long_tf_count;// 引用計數(shù)unsignedintf_flags;// 打開權(quán)限fmode_tf_mode;// 訪問模式loff_tf_pos;// 當(dāng)前讀寫位置};關(guān)鍵在于f_op它指向的file_operations里除了owner幾乎全是函數(shù)指針structfile_operations{loff_t(*llseek)(structfile*,loff_t,int);// 改讀寫位置ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);int(*open)(structinode*,structfile*);int(*release)(structinode*,structfile*);};每個成員都對應(yīng)一個系統(tǒng)調(diào)用。用戶調(diào)read內(nèi)核讀出相應(yīng)的函數(shù)指針并把控制權(quán)交出去設(shè)備驅(qū)動的工作就完成了。每個設(shè)備都可以有自己的 read、write 實現(xiàn)但通過這層回調(diào)開發(fā)者只用 file 就能調(diào)取系統(tǒng)絕大部分資源——這就是Linux 下一切皆文件的核心理解。本小節(jié)復(fù)盤和圖示三、緩沖區(qū)給高速 CPU 配一條緩沖帶緩沖區(qū)是內(nèi)存空間的一部分在內(nèi)存中預(yù)留一塊存儲空間用來緩沖輸入或輸出的數(shù)據(jù)。按對應(yīng)設(shè)備方向分為輸入緩沖區(qū)和輸出緩沖區(qū)。為什么要引入它不設(shè)緩沖區(qū)時每次讀寫都要對磁盤發(fā)起系統(tǒng)調(diào)用而一次系統(tǒng)調(diào)用意味著 CPU 從用戶空間切到內(nèi)核空間、發(fā)生上下文切換CPU 時間是白花的頻繁的磁盤訪問會嚴(yán)重拖慢程序。有了緩沖機(jī)制從磁盤取信息時一次讀入大量數(shù)據(jù)進(jìn)緩沖區(qū)后續(xù)訪問不再走系統(tǒng)調(diào)用取完再讀下一批磁盤讀寫次數(shù)大幅減少打印機(jī)這類慢設(shè)備則先把文檔送進(jìn)緩沖區(qū)自己慢慢打CPU 轉(zhuǎn)身去干別的事。所以緩沖區(qū)的本質(zhì)就是一塊用在設(shè)備和 CPU 之間、緩存數(shù)據(jù)的內(nèi)存區(qū)讓低速外設(shè)與高速 CPU 協(xié)調(diào)工作。3.1 三種緩沖類型全緩沖填滿整個緩沖區(qū)才做 I/O 系統(tǒng)調(diào)用磁盤文件默認(rèn)如此行緩沖遇換行符就刷新終端設(shè)備默認(rèn)如此默認(rèn)大小 1024無緩沖不緩存直接調(diào)系統(tǒng)調(diào)用stderr就是如此保證出錯信息盡快顯示。除默認(rèn)規(guī)則外緩沖區(qū)滿、執(zhí)行flush、進(jìn)程結(jié)束也會觸發(fā)刷新。復(fù)盤和圖示四、兩個實驗看穿緩沖行為4.1 重定向后 printf 的內(nèi)容去哪了close(1);intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);printf(hello world: %d\n,fd);close(fd);// cat log.txt —— 空的明明把 1 號描述符重定向到了文件為什么文件里什么都沒有因為 1 號指向磁盤文件后刷新方式從行緩沖變成了全緩沖內(nèi)容沒填滿緩沖區(qū)自然不會落盤加一句fflush(stdout)強(qiáng)制刷新即可。另一個驗證把 2 號描述符重定向到文件用perror輸出close(2);intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);perror(hello world);// 不用 fflush直接寫進(jìn)文件因為stderr沒有緩沖區(qū)內(nèi)容立刻落盤反向驗證了無緩沖這一類型。4.2 fork 之后為什么輸出兩遍printf(%s,hello printf\n);fwrite(hello fwrite\n,13,1,stdout);write(1,hello write\n,12);fork();直接運行輸出三行但執(zhí)行./hello file重定向到文件后出現(xiàn)五行——printf和fwrite各輸出兩次write只有一次。原因鏈條重定向到普通文件后緩沖方式變?yōu)槿彌_數(shù)據(jù)先留在庫函數(shù)的用戶級緩沖區(qū)fork時父子寫時拷貝各自持有一份未刷新的數(shù)據(jù)進(jìn)程退出時統(tǒng)一刷新兩份都寫進(jìn)了文件write是系統(tǒng)調(diào)用沒有緩沖區(qū)不受影響順帶回答一個疑問write沒有緩沖而printf有說明該緩沖區(qū)是二次加上的由 C 標(biāo)準(zhǔn)庫提供這里討論的都是用戶級緩沖區(qū)OS 另有內(nèi)核級緩沖區(qū)。五、FILE 結(jié)構(gòu)fd 與緩沖區(qū)的載體訪問文件最終都要走 fd所以FILE內(nèi)部必然封裝了 fdtypedefstruct_IO_FILEFILE;// stdio.hstruct_IO_FILE{int_flags;char*_IO_read_ptr,*_IO_read_end,*_IO_read_base;// 讀緩沖區(qū)指針char*_IO_write_base,*_IO_write_ptr,*_IO_write_end;// 寫緩沖區(qū)指針char*_IO_buf_base,*_IO_buf_end;// 緩沖區(qū)首尾int_fileno;// 封裝的文件描述符};一組指針描述讀寫緩沖區(qū)的起點與當(dāng)前位置_fileno則是通往內(nèi)核的鑰匙。結(jié)論閉環(huán)用戶調(diào)printf數(shù)據(jù)先進(jìn)FILE的緩沖區(qū)滿足刷新條件后再由write交給 fd最后落到設(shè)備上。六、總結(jié)一切皆文件進(jìn)程、磁盤、鍵盤、管道、socket 都被抽象成文件read/write一套 API 通吃內(nèi)核依據(jù)f_op指向file_operations函數(shù)指針表把系統(tǒng)調(diào)用與設(shè)備驅(qū)動掛鉤緩沖區(qū)內(nèi)存中預(yù)留的一塊空間用空間換系統(tǒng)調(diào)用次數(shù)讓慢外設(shè)與快 CPU 協(xié)調(diào)三種類型全緩沖磁盤文件、行緩沖終端1024、無緩沖stderr緩沖區(qū)滿、flush、進(jìn)程退出也會刷新兩張驗方重定向后 printf 不輸出全緩沖需fflush、fork后庫函數(shù)輸出兩遍寫時拷貝帶走未刷新數(shù)據(jù)write始終只一次。