前言php
在最近看了APUE的標準IO部分以後感受對標準IO的緩存太模糊,沒有搞明白,APUE中關於緩存的部分一筆帶過,沒有深究緩存的實現原理,這樣一本被吹上天的書爲何不講透徹呢?今天早上爬起來趕忙找了幾篇文章看看,直到發現了這篇博客:http://blog.sina.com.cn/s/blog_6592a07a0101gar7.html。講的很不錯。html
1、IO緩存linux
系統調用:只操做系統提供給用戶程序調用的一組接口-------得到內核提供的服務。程序員
以fgetc / fputc 爲例,當用戶程序第一次調用fgetc 讀一個字節時,fgetc 函數可能經過系統調用 進入內核讀1K字節到I/O緩衝區中,而後返回I/O緩衝區中的第一個字節給用戶,把讀寫位置指 向I/O緩衝區中的第二個字符,之後用戶再調fgetc ,就直接從I/O緩衝區中讀取,而不須要進內核 了,當用戶把這1K字節都讀完以後,再次調用fgetc 時,fgetc 函數會再次進入內核讀1K字節 到I/O緩衝區中。在這個場景中用戶程序、C標準庫和內核之間的關係就像在「Memory Hierarchy」中 CPU、Cache和內存之間的關係同樣,C標準庫之因此會從內核預讀一些數據放 在I/O緩衝區中,是但願用戶程序隨後要用到這些數據,C標準庫的I/O緩衝區也在用戶空間,直接 從用戶空間讀取數據比進內核讀數據要快得多。另外一方面,用戶程序調用fputc 一般只是寫到I/O緩 衝區中,這樣fputc 函數能夠很快地返回,若是I/O緩衝區寫滿了,fputc 就經過系統調用把I/O緩衝 區中的數據傳給內核,內核最終把數據寫回磁盤或設備。有時候用戶程序但願把I/O緩衝區中的數據馬上 傳給內核,讓內核寫回設備或磁盤,這稱爲Flush操做,對應的庫函數是fflush,fclose函數在關閉文件 以前也會作Flush操做。編程
雖然write 系統調用位於C標準庫I/O緩衝區的底 層,被稱爲Unbuffered I/O函數,但在write 的底層也能夠分配一個內核I/O緩衝區,因此write 也不必定是直接寫到文件的,也 可能寫到內核I/O緩衝區中,可使用fsync函數同步至磁盤文件,至於究竟寫到了文件中仍是內核緩衝區中對於進程來講是沒有差異 的,若是進程A和進程B打開同一文件,進程A寫到內核I/O緩衝區中的數據從進程B也能讀到,由於內核空間是進程共享的, 而c標準庫的I/O緩衝區則不具備這一特性,由於進程的用戶空間是徹底獨立的.緩存
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { char buf[5]; FILE *myfile = stdin; fgets(buf, 5, myfile); fputs(buf, myfile); return 0; }
buffered I/O中的"buffer"究竟是指什麼呢?這個buffer在什麼地方呢?FILE是什麼呢?它的空間是怎麼分配的呢 要弄清楚這些問題,就要看看FILE是如何定義和運做的了.(特別說明,在平時寫程序時,不用也不要關心FILE是如何定義和運做的,最好不要直接操做它,這裏使用它,只是爲了說明buffered IO)下面的這個是glibc給出的FILE的定義,它是實現相關的,別的平臺定義方式不一樣.函數
struct _IO_FILE { int _flags; #define _IO_file_flags _flags char* _IO_read_ptr; char* _IO_read_end; char* _IO_read_base; char* _IO_write_base; char* _IO_write_ptr; char* _IO_write_end; char* _IO_buf_base; char* _IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; };
上面的定義中有三組重要的字段:url
1. char* _IO_read_ptr; char* _IO_read_end; char* _IO_read_base; 2. char* _IO_write_base; char* _IO_write_ptr; char* _IO_write_end; 3. char* _IO_buf_base; char* _IO_buf_end;
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { char buf[5]; FILE *myfile =stdin; printf("before reading/n"); printf("read buffer base %p/n", myfile->_IO_read_base); printf("read buffer length %d/n", myfile->_IO_read_end - myfile->_IO_read_base); printf("write buffer base %p/n", myfile->_IO_write_base); printf("write buffer length %d/n", myfile->_IO_write_end - myfile->_IO_write_base); printf("buf buffer base %p/n", myfile->_IO_buf_base); printf("buf buffer length %d/n", myfile->_IO_buf_end - myfile->_IO_buf_base); printf("/n"); fgets(buf, 5, myfile); fputs(buf, myfile); printf("/n"); printf("after reading/n"); printf("read buffer base %p/n", myfile->_IO_read_base); printf("read buffer length %d/n", myfile->_IO_read_end - myfile->_IO_read_base); printf("write buffer base %p/n", myfile->_IO_write_base); printf("write buffer length %d/n", myfile->_IO_write_end - myfile->_IO_write_base); printf("buf buffer base %p/n", myfile->_IO_buf_base); printf("buf buffer length %d/n", myfile->_IO_buf_end - myfile->_IO_buf_base); return 0; }
能夠看到,在讀操做以前,myfile的緩衝區是沒有被分配的,在一次讀以後,myfile的緩衝區才被分配.這個緩衝區既不是內核中的緩衝區,也不是用戶分配的緩衝區,而是有用戶進程空間中的由buffered I/O系統負責維護的緩衝區.(固然,用戶能夠能夠維護該緩衝區,這裏不作討論了)spa
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { char buf[5]; char *cur; FILE *myfile; myfile = fopen("bbb.txt", "r"); printf("before reading, myfile->_IO_read_ptr: %d/n", myfile->_IO_read_ptr - myfile->_IO_read_base); fgets(buf, 5, myfile); //僅僅讀4個字符 cur = myfile->_IO_read_base; while (cur <</span> myfile->_IO_read_end) //實際上讀滿了這個緩衝區 { printf("%c",*cur); cur++; } printf("/nafter reading, myfile->_IO_read_ptr: %d/n", myfile->_IO_read_ptr - myfile->_IO_read_base); return 0; }
上面提到的bbb.txt文件的內容是由不少行的"123456789"組成上例中,fgets(buf, 5, myfile); 僅僅讀4個字符,可是,緩衝區已被寫滿,可是_IO_read_ptr卻向前移動了5位,下次再次調用讀操做時,只要要讀的位數不超過myfile->_IO_read_end - myfile->_IO_read_ptr那麼就不須要再次調用系統調用read,只要將數據從myfile的緩衝區拷貝到buf便可(從myfile->_IO_read_ptr開始拷貝)操作系統
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { char buf[2048]={0}; int i; FILE *myfile; myfile = fopen("aaa.txt", "r+"); i= 0; while (i<</span>2048) { fwrite(buf+i, 1, 512, myfile); i +=512; //註釋掉這句則能夠寫入aaa.txt myfile->_IO_write_ptr = myfile->_IO_write_base; printf("%p write buffer base/n", myfile->_IO_write_base); printf("%p buf buffer base /n", myfile->_IO_buf_base); printf("%p read buffer base /n", myfile->_IO_read_base); printf("%p write buffer ptr /n", myfile->_IO_write_ptr); printf("/n"); } return 0; }
上面這個是關於全緩衝寫的例子.全緩衝時,只有當標準I/O自動flush(好比當緩衝區已滿時)或者手工調用fflush時,標準I/O纔會調用一次write系統調用.例子中,fwrite(buf+i, 1, 512, myfile);這一句只是將buf+i接下來的512個字節寫入緩衝區,因爲緩衝區未滿,標準I/O並未調用write.此時,myfile->_IO_write_ptr = myfile->_IO_write_base;會致使標準I/O認爲沒有數據寫入緩衝區,因此永遠不會調用write,這樣aaa.txt文件得不到寫入.註釋掉myfile->_IO_write_ptr = myfile->_IO_write_base;先後,看看效果
#include <stdlib.h> #include <stdio.h> int main(void) { char buf[5]; char buf2[10]; fgets(buf, 5, stdin); //第一次輸入時,超過5個字符 puts(stdin->_IO_read_ptr);//本句說明整行會被一次所有讀入緩衝區, //而非僅僅上面須要的個字符 stdin->_IO_read_ptr = stdin->_IO_read_end; //標準I/O會認爲緩衝區已空,再次調用read //註釋掉,再看看效果 printf("/n"); puts(buf); fgets(buf2, 10, stdin); puts(buf2); return 0; }
上例中, fgets(buf, 5, stdin); 僅僅須要4個字符,可是,輸入行中的其餘數據也被寫入緩衝區,可是_IO_read_ptr向前移動了5位,下次再次調用fgets操做時,就不須要再次調用系統調用read,只要將數據從stdin的緩衝區拷貝到buf2便可(從stdin->_IO_read_ptr開始拷貝)stdin->_IO_read_ptr = stdin->_IO_read_end;會致使標準I/O會認爲緩衝區已空,再次fgets則須要再次調用read.比較一下將該句註釋掉先後的效果
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> char buf[5]={'1','2', '3', '4', '5'}; //最後一個不要是/n,是/n的話,標準I/O會自動flush的 //這是行緩衝跟全緩衝的重要區別 void writeLog(FILE *ftmp) { fprintf(ftmp, "%p write buffer base/n", stdout->_IO_write_base); fprintf(ftmp, "%p buf buffer base /n", stdout->_IO_buf_base); fprintf(ftmp, "%p read buffer base /n", stdout->_IO_read_base); fprintf(ftmp, "%p write buffer ptr /n", stdout->_IO_write_ptr); fprintf(ftmp, "/n"); } int main(void) { int i; FILE *ftmp; ftmp = fopen("ccc.txt", "w"); i= 0; while (i<</span>4) { fwrite(buf, 1, 5, stdout); i++; *stdout->_IO_write_ptr++ = '/n';//能夠單獨把這句打開,看看效果 //getchar();//getchar()會標準I/O將緩衝區輸出 //打開下面的註釋,你就會發現屏幕上什麼輸出也沒有 //stdout->_IO_write_ptr = stdout->_IO_write_base; writeLog(ftmp); //這個只是爲了查看緩衝區指針的變化 } return 0; }
這個例子將將FILE結構中指針的變化寫入的文件ccc.txt,
運行後能夠有興趣的話,能夠看看.#include <</span>stdlib.h> #include <</span>stdio.h> #include <</span>sys/types.h> #include <</span>sys/stat.h> #include <</span>fcntl.h> int main(void) { fputs("stderr", stderr); printf("%d/n", stderr->_IO_buf_end - stderr->_IO_buf_base); return 0; }
對無緩衝的流的每次讀寫操做都會引發系統調用
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { char buf[5]; char buf2[10]; fgets(buf, sizeof(buf), stdin);//輸入要於4個,少於13個字符才能看出效果 puts(buf); //交替註釋下面兩行 //stdin->_IO_read_end = stdin->_IO_read_ptr+1; stdin->_IO_read_end = stdin->_IO_read_ptr + sizeof(buf2)-1; fgets(buf2, sizeof(buf2), stdin); puts(buf2); if (feof(stdin)) printf("input end/n"); return 0; }