引子:
printf("printf123");
write(stdou,"write123",3);
使用write()函數和printf()函數輸出一個字符串到終端的時候,會發現,如果不在printf()中包含換行符 \n就會出現,明明是printf()函數寫在前面,而write()中要輸出的結果先輸出到終端
分析:
1. Linux系統的三種緩存機制:
全緩存:當遇到緩衝區滿/文件關閉/fflush()強制刷新緩存區/程序結束這四種情況以後,才刷新緩存區,將緩存的內容送到內核,如使用fopen 打開的文件,而open打開的文件是沒有緩存的
行緩存:與全緩存相比,行緩存顧名思義,再遇到 \n也會刷新緩存區,比上面的全緩存多了一種刷新緩存區的方式,比如輸出到屏幕的printf函數
無緩存:內容直接送到內核,用戶層沒有緩存區,比如write()函數
所以,把上面的printf(“printf123”)修改爲printf("write123\n"),
輸出結果就是:
printf123
write123
正經問題:
那麼將printf()函數的輸出目的地從終端屏幕改到一個文件“text”中,比如下面的代碼所做的
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <error.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> int main(void){ char *buf="123456\n"; int fd,save_fd; char *lbuf; if((fd=open("text",O_RDWR|O_CREAT,0777))<0){ //打開一個文件 text perror("open"); exit(1); } save_fd=dup(STDOUT_FILENO); //使用save_fd保存stdout的文件描述符 dup2(fd,STDOUT_FILENO); //將text的文件描述符複製給stdout,這樣,標準輸出就指向了text文件 close(fd); //關閉沒用的文件描述符fd printf("1\n"); //使用printf函數向text輸出 1 write(STDOUT_FILENO,buf,strlen(buf)); //使用write函數向text寫入數據 dup2(save_fd,STDOUT_FILENO); //將文件描述符換回來,回覆標準輸入 close(save_fd); printf("2\n"); //向屏幕輸出 2 write(STDOUT_FILENO,buf,strlen(buf)); //向屏幕輸出 1 return 0; } |
屏幕輸出結果:
123456
1
2
文件text中的內容:
123456
對於上面的結果,有兩個問題:
第一個問題:
在改變標準輸出之後,stdou對應的應該是文件text,那麼printf("1\n")也應該文件text中打印一個 "1\n"纔對,但是卻沒有,而write()函數的內容是成功寫入了text,這就說明,標準輸出確實被修改成功了,
第二個問題:
再將標準輸出恢復之後,也就是執行了 dup2(save_fd,STDOUT_FILENO); 這條語句以後,printf("2\n") 並沒有立即向屏幕上打印數據,而是等write()之後纔打印了2 。並且這個2還是在1之後
分析:
printf()函數的緩存屬性發生了改變,也就是之前是行緩存,而現在變成了全緩存,那麼爲什麼不是無緩存呢,因爲如果是無緩存,那麼 "1\n"就因該被刷新到內核緩存區了,而write函數正是講“123456\n”從內核寫入文件的,如果“1\n”在內核緩存區,那麼他就應該被write函數的寫操作影響,或者沖掉,這樣來說,“1\n”就根本還沒有進入內核,這也就是爲什麼分析得出,printf函數的緩存性質發生了改變
爲了驗證上面的猜測:
使用setvbuf()函數設置stdou的緩存區屬性
setvbuf()的原函數
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
stream 文件輸入流
buf 緩存區地址
mode 緩存區性質
_IONBF unbuffered 無緩存
_IOLBF line buffered 行緩存
_IOFBF fully buffered 全緩存
修改代碼,在第一次修改stdout之後,設置stdout的緩存區性質
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <error.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> int main(void){ char *buf="123456\n"; int fd,save_fd; char *lbuf; if((fd=open("text",O_RDWR|O_CREAT,0777))<0){ perror("open"); exit(1); } save_fd=dup(STDOUT_FILENO); dup2(fd,STDOUT_FILENO); close(fd); //設置stdout的緩存區性質 setvbuf(stdout,lbuf=malloc(512*sizeof(char)),_IOLBF,512); printf("1\n"); write(STDOUT_FILENO,buf,strlen(buf)); dup2(save_fd,STDOUT_FILENO); close(save_fd); printf("2\n"); write(STDOUT_FILENO,buf,strlen(buf)); return 0; } |
從setvbuf函數的原函數來看,是使用文件的讀寫指針來設置緩存區屬性的,所以,當改變stdou的時候,纔會對printf()的緩存區造成影響
修改之後的輸出結果
屏幕:
2
123456
text文件中:
1
123456
所以,可以得出結果,第一次修改stdout的時候,確實改變了printf()的緩存性質,從第一次的輸出結果來看,第二次將stdout改回成標準輸出的時候,printf()的緩存性質並沒變回來(原因暫時不明),然後在設置了緩存區性質之後,從第二次的輸出結果可以得出,printf()緩存區又變成了行緩存
總結:
如果要使用dup2()函數改變文件的標準輸出,需要判斷緩存的性質是否是發生了變化,如果發生了變化,就需要重新設置,否則不能得到想要的結果
如果不改變標準輸出,在使用printf的時候,需要注意是否要隨行輸出。