Linux文件系統(tǒng)之緩沖區(qū)詳解
一、先看現(xiàn)象
#include <stdio.h> #include <string.h> #include <unistd.h> int main() { const char* fstr = "Hello fwrite\n"; const char* str = "Hello write\n"; printf("Hello printf\n"); fprintf(stdout, "Hello fprintf\n"); fwrite(fstr, strlen(fstr), 1, stdout); // 返回值是寫(xiě)入成功的快數(shù) write(1, str, strlen(str)); // 返回值是寫(xiě)入成功的字節(jié)數(shù) // fork(); return 0; }
結(jié)構(gòu)分析:帶 fork 的輸出重定向最終把有一些內(nèi)容向 log.txt 文件中寫(xiě)入了多次,并且打印順序也有所不同。
int main() { const char* fstr = "Hello fwrite"; const char* str = "Hello write"; printf("Hello printf"); fprintf(stdout, "Hello fprintf"); fwrite(fstr, strlen(fstr), 1, stdout); // 返回值是寫(xiě)入成功的快數(shù) close(1); // write(1, str, strlen(str)); // 返回值是寫(xiě)入成功的字節(jié)數(shù) // fork(); return 0; }
結(jié)果分析:代碼中只使用了庫(kù)函數(shù)向顯示器中進(jìn)行寫(xiě)入,并且在字符串的結(jié)尾沒(méi)有加 \n
,在最后面將標(biāo)準(zhǔn)輸出對(duì)應(yīng)的文件描述符進(jìn)行了關(guān)閉,最終顯示器上什么也沒(méi)有。上一段代碼在字符串的結(jié)尾加上了 \n
最終字符串被成功的打印到了屏幕上。
int main() { const char* str = "Hello write"; write(1, str, strlen(str)); // 返回值是寫(xiě)入成功的字節(jié)數(shù) close(1); return 0; }
結(jié)果分析:字符串的結(jié)尾依然不加 \n
,但是這一次采用系統(tǒng)調(diào)用接口,最后仍然將標(biāo)準(zhǔn)輸出對(duì)應(yīng)的文件描述符進(jìn)行關(guān)閉,這一次字符串被成功的打印了出來(lái)。
二、用戶緩沖區(qū)的引入
write
為什么能將不帶 \n
的字符串寫(xiě)入到顯示器文件中。首先我們需要明確一點(diǎn)進(jìn)程打開(kāi)的每一個(gè)文件都有一個(gè)屬于自己的操作系統(tǒng)級(jí)別的文件緩沖區(qū),該緩沖區(qū)的存在,可以減少對(duì)外設(shè)的讀寫(xiě)操作以提高計(jì)算機(jī)的效率。舉個(gè)栗子,在一個(gè)進(jìn)程中向磁盤(pán)里的同一個(gè)文件進(jìn)多次行寫(xiě)入,文件緩沖區(qū)的存在,可以將每次寫(xiě)入的內(nèi)容先存儲(chǔ)在文件緩沖區(qū)中,最后在程序退出或者調(diào)用 close
的時(shí)候,一次性將文件緩沖區(qū)中的所有內(nèi)容刷新到磁盤(pán)。如果沒(méi)有該文件緩沖區(qū),那在進(jìn)程里對(duì)文件進(jìn)行 n 次寫(xiě)操做,就要對(duì)應(yīng) n 次向磁盤(pán)的寫(xiě)操作,CPU 和外設(shè)之間是存在非常大的速度差的,這樣效率會(huì)非常低。
write
作為系統(tǒng)調(diào)用接口,它就是直接向文件緩沖區(qū)中寫(xiě)入,最后在調(diào)用 close
接口或者程序退出的時(shí)候,會(huì)將文件緩沖區(qū)的內(nèi)容刷新到對(duì)應(yīng)的外設(shè)中。
printf
、fprintf
、fwrite
底層一定是封裝了 write
系統(tǒng)調(diào)用接口,那為什么使用 write
系統(tǒng)調(diào)用接口就可以將字符串寫(xiě)入到顯示器,使用 C 庫(kù)函數(shù)沒(méi)能把字符串寫(xiě)入到顯示器文件?原因在進(jìn)度條的那篇文章中講過(guò),我們使用的這些 C 庫(kù)函數(shù),是把字符串寫(xiě)入到了緩沖區(qū)中,這個(gè)緩沖區(qū)和上面的文件緩沖區(qū)有所不同,這里說(shuō)的緩沖區(qū)是 C 語(yǔ)言給我們提供的語(yǔ)言層面的緩沖區(qū),也叫做用戶級(jí)緩沖區(qū),\n
具有刷新用戶級(jí)緩沖區(qū)的作用,因此不加 \n
并且在程序結(jié)束前將顯示器對(duì)應(yīng)的文件描述符進(jìn)行了關(guān)閉,最終就導(dǎo)致字符串在用戶級(jí)緩沖區(qū)中,沒(méi)有被刷新到文件緩沖區(qū),所以屏幕上就什么也沒(méi)有。這里我們可以肯定,在這些 C 庫(kù)函數(shù)中,并不是立即調(diào)用 write
接口,而是在遇到 \n
后才去調(diào)用 write
接口將用戶緩沖區(qū)的內(nèi)容刷新到文件緩沖區(qū)中。
總結(jié):使用 C 系統(tǒng)調(diào)用接口向文件中寫(xiě)入,寫(xiě)入的內(nèi)容先被存儲(chǔ)在用戶緩沖區(qū)中,在合適的時(shí)候(遇到 \n
)才會(huì)進(jìn)行刷新,這里刷新的本質(zhì)是調(diào)用 write
將數(shù)據(jù)從用戶緩沖區(qū)寫(xiě)入內(nèi)核。
之前說(shuō)的 exit
會(huì)刷新緩沖區(qū),其實(shí)就是刷新用戶緩沖區(qū),因?yàn)?exit
作為 C 庫(kù)函數(shù),可以看見(jiàn)用戶緩沖區(qū),而 _exit
作為系統(tǒng)調(diào)用接口,無(wú)法看到語(yǔ)言層面的用戶緩沖區(qū),因此也就無(wú)法刷新用戶緩沖區(qū)。
三、用戶緩沖區(qū)的刷新策略
- 無(wú)緩沖:直接刷新,數(shù)據(jù)不在用戶緩沖區(qū)中停留。
- 行緩沖:不刷新,直到碰到
\n
。 - 全緩沖:緩沖區(qū)滿了才刷新。
所謂刷新就是調(diào)用 write
接口將數(shù)據(jù)寫(xiě)入操作系統(tǒng)中的文件緩沖區(qū)。顯示器文件對(duì)應(yīng)采用的就是行緩沖,向磁盤(pán)文件中寫(xiě)入采用的是全緩沖。進(jìn)程在退出的時(shí)候也會(huì)刷新用戶緩沖區(qū),還可以調(diào)用 fflush
進(jìn)行刷新。
四、為什么要有用戶緩沖區(qū)
- 解決效率問(wèn)題,緩沖區(qū)就像菜鳥(niǎo)驛站,不需要我們自己坐火車(chē)坐飛機(jī)去送東西,而是直接交給菜鳥(niǎo)驛站,然后就可以干自己的事情了,菜鳥(niǎo)驛站可以選擇攢上一大批快遞然后統(tǒng)一寄送出去。用戶緩沖區(qū)的存在本質(zhì)上提高了 C 語(yǔ)言的效率,也就是提高了用戶的效率,因?yàn)?C 語(yǔ)言是程序員在使用,在使用 C 庫(kù)函數(shù)進(jìn)行文件寫(xiě)入時(shí),大部分情況只需要把數(shù)據(jù)交給緩沖區(qū),然后就可以快速的返回,不需要每一次都親力親為的去和操作系統(tǒng)打交道。
- 配合格式化,有些和文件寫(xiě)入相關(guān)的 C 庫(kù)函數(shù)是格式化輸出函數(shù),在我們看來(lái),它可以寫(xiě)入整形、符點(diǎn)型,但是最終都是以字符串的形式進(jìn)行寫(xiě)入。格式化就是將類(lèi)型全都轉(zhuǎn)化成字符串,先寫(xiě)入到用戶緩沖區(qū),用戶緩沖區(qū)中存的一定都是字符串。
用戶緩沖區(qū),有進(jìn)也有出,將數(shù)據(jù)寫(xiě)入到用戶緩沖區(qū)中就就叫做進(jìn),將用戶緩沖區(qū)中的數(shù)據(jù)刷新到內(nèi)核中的文件緩沖區(qū)中,被刷新的數(shù)據(jù)就可以從用戶緩沖區(qū)中刪掉,這就叫做出。用戶緩沖就像就像水流一樣源源不斷,流的概念就是因此而來(lái)。
小Tips:FILE
里面就有對(duì)應(yīng)打開(kāi)文件的緩沖區(qū)字段和維護(hù)信息。每個(gè)被進(jìn)程打開(kāi)文件都有自己對(duì)應(yīng)的文件緩沖區(qū)。FILE
對(duì)象屬于用戶,用戶緩沖區(qū)可以看作是在堆上申請(qǐng)的一塊空間。
五、現(xiàn)象解釋
這下再來(lái)解釋上面代碼中有 fork 然后重定向,寫(xiě)入了多次的原因。首先重定向后,將本來(lái)向顯示器文件寫(xiě)入的內(nèi)容,寫(xiě)到了磁盤(pán)文件,顯示器文件的緩沖區(qū)采用行緩沖,即遇到 \n
就會(huì)刷新,而磁盤(pán)文件采用的是全緩沖,當(dāng)緩沖區(qū)滿了才刷新。因此在重定向后,會(huì)把三條 C 庫(kù)函數(shù)寫(xiě)入的內(nèi)容全部保存到緩沖區(qū)中,然后調(diào)用 fork
創(chuàng)建子進(jìn)程,此時(shí)父子進(jìn)程代碼共享,數(shù)據(jù)寫(xiě)時(shí)拷貝,在程序退出的時(shí)候回去刷新用戶緩沖區(qū),上面說(shuō)過(guò),刷新就是將用戶緩沖區(qū)中的數(shù)據(jù)寫(xiě)入到內(nèi)核,然后將用戶緩沖區(qū)中的內(nèi)容清空,上面還說(shuō)過(guò),緩沖區(qū)就是在堆上申請(qǐng)的一段空間,可以看作數(shù)據(jù)部分,因?yàn)橐獎(jiǎng)h除數(shù)據(jù),所以就會(huì)進(jìn)行寫(xiě)時(shí)拷貝,此時(shí)之前父進(jìn)程用戶緩沖區(qū)中的內(nèi)容就會(huì)給子進(jìn)程拷貝一份,然后父子進(jìn)程都執(zhí)行刷新動(dòng)作,各自刷新自己的緩沖區(qū)數(shù)據(jù),這就是為什么最終出現(xiàn)多份的原因。沒(méi)有重定向,只向顯示器打印四條消息,是因?yàn)轱@示器采用的是行刷新策略,在調(diào)用 fork
前,對(duì)應(yīng)的字符串就已經(jīng)被刷新出去了。在 fork
的時(shí)候,父進(jìn)程的用戶緩沖區(qū)中是空的,什么也沒(méi)有。
磁盤(pán)文件全緩沖驗(yàn)證:
int main() { const char* fstr = "Hello fwrite\n"; const char* str = "Hello write\n"; printf("Hello printf\n"); sleep(2); fprintf(stdout, "Hello fprintf\n"); sleep(2); fwrite(fstr, strlen(fstr), 1, stdout); // 返回值是寫(xiě)入成功的快數(shù) sleep(2); write(1, str, strlen(str)); // 返回值是寫(xiě)入成功的字節(jié)數(shù) sleep(5); fork(); return 0; }
分析:最先將 write
內(nèi)容寫(xiě)入到文件中,因?yàn)樗侵苯訉?xiě)入到文件緩沖區(qū),而剩下的 C 庫(kù)函數(shù)對(duì)應(yīng)的內(nèi)容是統(tǒng)一一次全部刷新到內(nèi)核,即使每個(gè)字符串后面都有 \n
,但最后還是統(tǒng)一全部刷新,這就證明了磁盤(pán)文件采用的是全刷新策略。
六、結(jié)語(yǔ)
以上就是Linux文件系統(tǒng)之緩沖區(qū)詳解的詳細(xì)內(nèi)容,更多關(guān)于Linux緩沖區(qū)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Linux中進(jìn)程在后臺(tái)可靠運(yùn)行的方法總結(jié)
本篇文章主要給大家介紹了Linux中進(jìn)程在后臺(tái)可靠運(yùn)行的方法以及示例代碼分析,一起學(xué)習(xí)參考下吧。2017-12-12CentOS 7.2搭建VNC遠(yuǎn)程桌面服務(wù)的方法
本篇文章主要介紹了CentOS 7.2搭建VNC遠(yuǎn)程桌面服務(wù)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03Git 刪除遠(yuǎn)程服務(wù)器文件同時(shí)保留本地文件實(shí)例詳解
這篇文章主要介紹了Git 刪除遠(yuǎn)程服務(wù)器文件同時(shí)保留本地文件實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05Linux使用watch命令監(jiān)控Docker容器狀態(tài)的操作方法
在現(xiàn)代的開(kāi)發(fā)和運(yùn)維環(huán)境中,容器化技術(shù)已經(jīng)成為一種重要的趨勢(shì),而Docker作為最流行的容器化平臺(tái)之一,Linux中的watch命令就是一個(gè)非常有用的工具,它可以幫助我們定期執(zhí)行指定的命令,并全屏顯示輸出,本文給大家介紹了在Linux中使用watch命令監(jiān)控Docker容器狀態(tài)2024-10-10Linux內(nèi)核設(shè)備驅(qū)動(dòng)之Linux內(nèi)核基礎(chǔ)筆記整理
今天小編就為大家分享一篇關(guān)于Linux內(nèi)核設(shè)備驅(qū)動(dòng)之Linux內(nèi)核基礎(chǔ)筆記整理,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12ubuntu16.04自動(dòng)設(shè)置行號(hào)的步驟詳解
這篇文章主要介紹了ubuntu16.04自動(dòng)設(shè)置行號(hào)的步驟,文中給大家提到了Ubuntu vi設(shè)置行號(hào)的方法,感興趣的朋友跟隨腳本之家小編一起看看吧2018-08-08tomcat 5.5連接池配置,如何讓工程為默認(rèn)工程
把驅(qū)動(dòng)程序拷貝到Tomcat 5.5\common\lib目錄下2009-06-06