Unix系統的三種緩衝區行緩衝、全緩衝、無緩衝(以及如何調整緩衝區的類型)

from unix系統編程 p91 練習4-22

下面的程序會產生什麼樣的輸出呢?

#include <stdio.h>

#include <unistd.h>

int main()

{

    printf("This is my output.") ;

    fork() ;

    return 0 ;

}

輸出如下所示:

This is my output.This is my output.

 

(似乎很不正常!該程序中,一個printf語句輸出之後,用fork函數創建子進程,然後父子進程一起返回。

怎麼會輸出兩個printf語句呢??)

 其實,

由於緩衝的原因,printf的輸出可能會寫入stdout所對應的緩衝區中,而不會被寫入實際的輸出設備。由於這個緩衝區是用戶空間的一部分,所有它會被fork複製。父進程和子進程每次終止時,從main的返回都會將緩衝區作爲清理的一部分清空。

 

而下面的程序會產生什麼樣的輸出呢?

#include <stdio.h>

#include <unistd.h>

int main()

{

    printf("This is my output.\n") ;   /*注意,原字符串後多了一個 \n */

    fork() ;

    return 0 ;

}

輸出如下顯示:

This is my output.

 

標準輸出的緩衝通常是行緩衝,這意味着當新行包含新行的時候,緩衝區會被清空,所以會在fork之前被清空,只輸出一行輸入。

 

磁盤文件是完全緩衝的(fully buffered),程序調用fprintf時,實際上沒有把消息寫入磁盤,而是將這些字節寫人如FILE結構的一個緩衝區中,可用fflush調用強制寫出緩衝的任何內容。

與終端有關的文件是行緩衝(line buffered)的。

標準錯誤(strerror)默認情況下是不緩衝的。

附件1:

在Unix系統中,緩衝方式存在三種,分別是:

1,全緩衝

2,行緩衝

3,無緩衝

在學習APUE這本書時,程序8-1中,就很好的體現了全緩衝和行緩衝的區別,代碼如下:

  1. #include<stdio.h>  
  2. #include<unistd.h>  
  3. int glob=6;  
  4. char buf[]="a write ro stdout/n";  
  5. int main()  
  6. {  
  7.         int var;  
  8.         pid_t pid;  
  9.         printf("a write to stdout/n");  
  10.         fflush(NULL);  
  11.         if((pid=fork())<0)  
  12.         {  
  13.                 printf("fork error");  
  14.         }  
  15.         else  
  16.         {  
  17.                 if(pid==0)  
  18.                 {  
  19.                         glob++;  
  20.                         var++;  
  21.                 }  
  22.                 else  
  23.                 {  
  24.                         sleep(2);  
  25.                 }  
  26.         }  
  27.         printf("pid=%d,glob=%d,var=%d/n",getpid(),glob,var);  
  28.         exit(0);  
  29. }  
 

編譯成功後,我這裏生成的二進制文件默認爲a.out

運行:./a.out

可以看到結果如下:

[c-sharp] view plaincopy
  1. a write to stdout  
  2. pid=6587,glob=7,var=134514042  
  3. pid=6586,glob=6,var=134514041  
 

運行./a.out > temp.out

結果如下:

[c-sharp] view plaincopy
  1. a write to stdout  
  2. pid=6591,glob=7,var=134514042  
  3. a write to stdout  
  4. pid=6590,glob=6,var=134514041  

分析原因:

在./a.out輸出中,標註輸出是STDOUT_FILENO,是交互式的終端,所以系統採用的緩衝方式行緩衝,所以在printf函數中,輸出後,立即刷新緩衝區,而在./a.out > temp.out命令中,輸出流定向到了temp.out文件中,所以採用的輸出方式爲全緩衝方式,所以會兩次輸出

a write to stdout

可以將程序中fflush(NULL)加入,則只會輸出一次

a write to stdout

因爲flush即時刷新了緩衝區。


附件2:

使用緩衝區的目的:儘量減少使用read/write的調用
分類:
1. Fully buffered means that I/O takes place only when the buffer is fully, the process explicitly calls fflush, or the process terminates by calling exit. A common size for the standard I/O buffer is 8192 bytes; 
 
2. Line buffered means that I/O takes place when a newline is encountered, when the process calls fflush, or when the process terminates by calling exit. 
 
3. Unbuffered means that I/O take place each time a standard I/O output function is called. 
 
 
Most unix implementations of the standard I/O libarary use the following rules. 
 
1. Standard error is always unbuffered. 能夠快速顯示錯誤
 
2. Standard input and standard output are fully buffered, unless they refer to a terminal device, in which case, they are line buffered. 
 
3. All other streams are fully buffered unless they refer to a terminal device, 
in which case, they are line buffered. 

使用setbuf()和setvbuf()可以更改緩衝的類型:
setbuf(FILE *fp, char *buf)   buf用malloc()開闢,當buf爲NULL時,緩衝區爲不帶緩衝
setvbuf(FILE *fp,char *buf, int buf_mode, int size)
        buf_mde有三種:_IOFBF 0 全緩衝, _IOLBF 1 行緩衝, _IONBF 2 不帶緩衝

在任何時刻,可以使用fflush強制刷新一個數據流,此函數使該流所有未寫的數據都被傳遞至內核。
由於fflush(stdin)的移植性不好,gcc不支持,故可以用以下代碼清空stdin緩衝區:
        while ((c = getchar()) != '\n' && c != EOF);


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章