系列文章目錄
文章目錄
- 系列文章目錄
- 前言
- 一、緩沖區
- 1.1 示例1
- 1.2 緩沖區的概念
- 二、緩沖區刷新方案
- 三、緩沖區的作用及存儲
前言
上篇我們介紹了,文件的重定向操作以及文件描述符的概念,今天我們再來學習一個和文件相關的知識-----------用戶緩沖區。
在操作系統中,緩沖區是實現高效資源管理的關鍵進制,緩沖區可以幫助用戶、系統暫存讀取及寫入數據,規避了用戶頻繁的I/O操作,可以很好的提高系統的性能和用戶的體驗。
一、緩沖區
由于我們對緩沖區接觸的比較少,所以在講解這部分知識時,我們會引入大量的代碼示例,后面我們會對這些示例及結果逐一分析。
1.1 示例1
下列函數均向標準輸出打印
1 #include<stdio.h>2 #include<string.h>3 #include<unistd.h>4 int main()5 {6 char *fstr="hello fwrite\n";7 char *wstr="hello witer\n";8 //c函數9 printf("hello printf\n");10 fprintf(stdout,"hello fprintf\n");11 fwrite(fstr,1,strlen(fstr),stdout); 12 //系統調用接口 13 write(1,wstr,strlen(wstr)); 14 return 0; 15 }
執行結果1:
將輸出重定向至log1.txt
./myfile >log1.txt
執行結果2:
到現在執行結果都是我們可以接受的,不要著急繼續向下看。
1 #include<stdio.h>2 #include<string.h>3 #include<unistd.h>4 int main()5 {6 char *fstr="hello fwrite\n";7 char *wstr="hello witer\n";8 //c函數9 printf("hello printf\n");10 fprintf(stdout,"hello fprintf\n");11 fwrite(fstr,1,strlen(fstr),stdout);12 //系統調用接口13 write(1,wstr,strlen(wstr));14 fork(); 15 return 0; 16 }
我們在文件末尾處創建了一個子進程,重復上面實驗:
執行結果3:
將文件重定向輸入到log2.txt
./myfile >log2.txt
執行結果4:
通過和前三次執行結果對比,我們可以看到向文件log2.txt
打印的結果,庫函數打印了兩次,系統調用接口只打印了一次,通過對比結果2和結果4,我們可以知道一定是fork()
函數產生的影響,那么為什么fork()
沒有對結果3產影響呢?帶著這兩問題我們繼續往下看。
1.2 緩沖區的概念
我們接著來看下面這兩個示例:
1 #include<stdio.h> 2 #include<string.h> 3 #include<unistd.h> 4 int main() 5 { 6 char *fstr="hello fwrite\n"; 7 char *wstr="hello witer\n"; 8 //c函數 9 printf("hello printf\n"); 10 fprintf(stdout,"hello fprintf\n"); 11 fwrite(fstr,1,strlen(fstr),stdout); 12 //系統調用接口13 write(1,wstr,strlen(wstr)); 14 //fork();15 close(1);16 return 0; 17 }
當執行完上面四個調用后我們使用close()
函數關閉文件描述符1的文件。
此時并沒有對程序執行結果造成影響,下面我們將\n
全部去掉,繼續執行程序。
1 #include<stdio.h>2 #include<string.h>3 #include<unistd.h>4 int main()5 {6 char *fstr="hello fwrite";7 char *wstr="hello witer";8 //c函數9 printf("hello printf");10 fprintf(stdout,"hello fprintf"); 11 fwrite(fstr,1,strlen(fstr),stdout);12 //系統調用接口13 write(1,wstr,strlen(wstr));14 //fork();15 close(1);16 return 0;17 }
可以看到此時只有系統調用接口成功將內容打印了出來,這又是怎么回事呢,相信大家早在學習C語言時,就聽說過緩沖區,下面我們就來慢慢的回答上面一系列問題。
結合下面的解釋看這個圖,一定要仔細看,精華全在圖上!!!!!
首先我們要清楚的一點是,printf/fprintf/fwrite
全部是封裝的系統調用write
。在我們的內核中,進程會擁有對于的task_struct
結構體,這個結構體對象包含一個文件結構體指針(上篇我們講了,這里我們認為此時它指向顯示器文件的file對象),通過這個文件對象可以找到內核緩沖區,所以的輸入、輸出,都需要經過這個緩沖區才能到達對應的磁盤中(包含硬件設備),當我們在執行C語言函數時,結果并不會直接打印在屏幕上,而是先存入C語言的緩存區,這一點通過上面的示例也能感受到,當程序執行到合適的時間,就會調用系統接口write
,write
通過文件描述符找到對應的文件對象,然后才能將c語言緩沖區的內容輸出到內核緩沖區,當數據到達內核緩沖區,符合條件,就會被刷新到顯示器上(磁盤),這個條件我們后面會介紹。
當程序執行系統調用write
時,它會根據我們給他提供的文件描述符,找到對應的文件對象,直接將內容輸出到內核緩沖區。有了這些概念,我們來分析上面代碼。
當我們執行的程序執行c函數時,它會先將內容存入C語言的緩沖區,但程序執行系統調用時,他是將內容直接刷新到了系統緩沖區,程序繼續向下執行close(1)
顯示器文件被關閉,此時c函數調用系統調用write
想要將處在C語言緩沖區的數據輸出道系統緩沖區,但是此時write
已經無法找到顯示器結構體對象了,素以無法實現,最后程序結束,系統緩沖區被刷新到顯示器,結果表現為只有系統調用打印成功。到了這里我們算是回答了一個問題,那么為什么打印數據后加\n
,就可以輸出成功呢?要回答這個問題我們就要來談一談緩沖區刷新方案了。
二、緩沖區刷新方案
在這里我們只談C語言的緩沖區刷新方案,我們將這種語言及的緩沖區稱為用戶及緩沖區(每個語言都會提供)。
緩沖區刷新方案主要有三種:
- 無緩沖-------直接刷新
- 行緩沖--------不刷新,直到碰見
\n
(一般為向顯示器打印時采用) - 全緩沖----------緩沖區滿了,才刷新(一般為向文件打印時采用)
此外當程序執行結束后也會進行刷新。
現在我們可以來回答為什么,這里不受文件關閉的影響了。
當程序執行C函數時,會先將數據存入用戶及緩沖區,但用戶及緩沖區判斷數據存在\n
就會立即調用write
將數據刷新到系統的緩沖區(此時文件還沒有關閉)。
下面我們來回答這個問題
為什么我們對程序進行重載后,C函數的結果打印了兩次。
當執行這個程序時,我們對他它重定向到文件,此時緩沖區刷新方案由之前的行緩沖變為全緩沖,所以c函數的執行結果會被存儲在用戶級緩沖區,而write
的執行結果則會直接存入系統緩沖區,此時創建子進程,程序結束時,父進程要調用write
將用戶級緩沖區數據刷新到系統緩沖區上(這個行為會將用戶及緩沖區數據清空),觸發寫時拷貝,子進程結束后也會將數據刷新,這時就有兩份數據打印到了文件中。
不知道大家有沒有注意到,我們上面例子的結果打印順序,也出現了變化,剛剛的這個邏輯同樣能解釋這個問題,到了這里我們算是將問題都解決了
三、緩沖區的作用及存儲
提高用戶效率
在我們調用C函數向顯示器或文件寫入數據時,若沒有緩沖區存在,其底層就會不斷的去調用write
函數,執行效率較低。
配合格式化
我們學習的printf/fprintf
等函數,都是格式化輸出函數(如使用%d
格式化數據)但是在操作系統中并沒有整形、浮點型等概念,在向顯示器或文件打印時統一被作為字符輸出,所以用戶級緩沖區的作用就是將數據格式化處理后,再交有系統接口。
用戶級緩沖區存儲位置
用戶緩沖區實際是被定義再FILE
結構體中的。