第13章算法
這章主要將了關於文件I/O的緩衝。編程
系統I/O調用(即內核)和C語言標準庫I/O函數(即stdio函數)在對磁盤進行操做的時候都會發生緩衝。經過緩衝能夠在必定程度上將用戶空間與實際的物理設備分離,還能夠減小內核訪問磁盤的次數。數組
先來看看關於內核緩衝區高速緩衝:read和write調用在對磁盤文件進行操做的時候不會直接訪問磁盤,以下圖所示。函數
例如:write(fd, "abc", 3) write調用會將"abc"從用戶空間緩衝區傳遞內核緩衝區中並隨即返回。在以後的某個時刻(緩衝區滿了或者須要刷新),內核會將其緩衝區的數據寫入到磁盤上,所以write系統調用與訪問磁盤的操做不是同步執行的。同理,對於read(fd, buffer, 3):內核會從磁盤讀取數據並存在內核的緩衝區上,read調用再從內核緩衝區讀取3個字節的數據到用戶緩衝區。當緩衝區的所有數據被讀取完,內核纔會再從磁盤文件讀取下一段數據。post
這樣的設計能夠使得read和write系統調用不用等待磁盤操做從而加快操做的速度,還減小了內核訪問磁盤的次數。性能
PS:Linux內核對緩衝區(內核緩衝區)的大小沒有固定的上限,可是可用物理內存總量和其餘進程對物理內存的需求會影響緩衝區的大小。學習
內核訪問磁盤的字節數是固定的,因此儘可能使得每次read(write)傳輸的字節數達到合適的數目能夠減小系統調用所消耗的時間。測試
下表爲書中的一個關於複製100MB大小的文件花費的時間,BUF_SIZE爲傳輸的字節數, Elapsed爲總共的用時,Total CPU爲CPU的總共用時,User CPU爲用戶CPU的用時,System CPU爲系統CPU用時。測試的文件系統爲塊大小爲4096字節的ext2。spa
當BUF_SIZE爲4096字節的時候,達到最優性能,再繼續增大BUF_SIZE不會對性能有太大的影響,是由於系統調用(read和write)花費的時間與在用戶空間和內核空間之間傳輸數據以及實際磁盤操做所花費的時間對比已經微不足道。
再來看看第二個表,是關於寫入一個100MB大小的文件所需的時間。
其實再進行write調用後並無這麼快執行磁盤I/O,由於實際計算機的RAM是很大(測試環境是4G),因此結合前表能夠知道複製文件耗時絕大部分是用在磁盤的讀取。
接着來看stdio庫的緩衝。
stdio庫的一些函數(fprintf, fscanf, fgets, fputs, fgets, fputc, fgetc)會幫咱們自動採起大塊數據緩衝以減小系統調用。
經過setvbuf設置stdio庫函數的緩衝方式
1 #include <stdio.h> 2 3 int setvbuf(FILE *stream, char *buf, int mode, size_t size);
成功調用返回0,失敗返回非0值。
其中stream爲文件流(PS:先打開文件流再調用setvbuf),buf爲使用的緩衝區,size爲緩衝區的大小。當buf不爲NULL,就指向size大小的內存塊做爲stream的緩衝區;當buf爲NULL,stdio庫會爲stream自動分配一個緩衝區。mode爲緩衝的類型。
mode的取值:
_IONBF | 不對I/O進行緩衝,即當即調用write和read |
_IOLBF | 採用行緩衝I/O(終端設備的流的默認採用) |
_IOFBF | 單次讀寫數據的大小與緩衝區相同(磁盤的流默認採用) |
除了setvbuf還有setbuf和setbuffer
1 #define _BSD_SOURCE //獲取setbuffer的聲明 2 #include <stdio.h> 3 4 void setbuf(FILE *stream, char *buf); 5 6 void setbuffer(FILE *stream, char *buf, size_t size); 7
還有經過fflush刷新stdio緩衝區
1 #include <stdio.h> 2 3 int fflush(FILE *stream);
成功調用返回0,失敗返回EOF。
若是stream爲NULL,fflush會刷新全部的緩衝區。
若是將fflush用在輸入流,能夠將已緩衝的輸入數據所有丟棄。
在C函數庫的實現中,若是stdin和stdout指向同一個終端,那麼從stdin讀取輸入時都會隱含調用fflush(stdout)。
----------------------暫時省略同步I/O。。。。這部分翻譯的很坑,看不懂。。。。
----------------------還有直接I/O。。。。。。。。。。。。。。。。。。。。。。
下圖爲I/O緩衝小結:
練習:
13-5. tail [ -n num ] file 命令打印名爲file文件的最後路面行(默認爲10行)。使用I/O系統調用(lseek()、read()、write()等)來實現該命令。
1 /* 2 * ===================================================================================== 3 * 4 * Filename: 13.5.c 5 * 6 * Description: 簡單tail實現,可能存在bug,可是沒有找到!!! 7 * 8 * Version: 1.0 9 * Created: 2014年05月02日 18時58分15秒 10 * Revision: none 11 * Compiler: gcc 12 * 13 * Author: alan (), alan19920626@gmail.com 14 * Organization: 15 * 16 * ===================================================================================== 17 */ 18 19 #include <sys/stat.h> 20 #include <fcntl.h> 21 #include <ctype.h> 22 #include "tlpi_hdr.h" 23 24 #define BUF_SIZE 4096 25 26 int main(int argc, char *argv[]){ 27 off_t seek, off, offset = 0; 28 int whence, fd, num, numRead, type = 0, i, off_cnt = 1, n_cnt = 0; 29 Boolean flag = FALSE; 30 struct stat statbuf; 31 char *file; 32 char buf[BUF_SIZE+1]; 33 34 if(strcmp(argv[1], "--help") == 0) 35 usageErr("%s [ -n num ] file", argv[0]); 36 37 //獲取文件名 38 file = argv[1]; 39 40 if(argc == 4 && strcmp(argv[1], "-n") == 0){ 41 flag = TRUE; 42 file = argv[3]; 43 } 44 45 //獲取最後的行數 46 num = (flag == TRUE) ? getInt(argv[2], GN_GT_0, "num") : 10; 47 48 //打開文件對應的文件描述符 49 fd = open(file, O_RDONLY); 50 if(fd == -1) 51 errExit("open"); 52 53 if(fstat(fd, &statbuf) == -1) 54 errExit("fstat"); 55 56 //判斷文件的大小是否超過4096字節 57 if(statbuf.st_size <= BUF_SIZE){ 58 off = 0; 59 whence = SEEK_CUR; 60 type = 1; 61 } 62 else{ 63 off = -1 * BUF_SIZE; 64 whence = SEEK_END; 65 } 66 67 //根據換行符判斷行數 68 while((seek = lseek(fd, off_cnt * off, whence)) != -1){ 69 numRead = read(fd, buf, BUF_SIZE); 70 if(numRead == -1) 71 errExit("read"); 72 if(numRead > 0){ 73 for(i = numRead-1; i >=0; --i){ 74 if(buf[i] == '\n') 75 n_cnt++; 76 if(n_cnt == num+1) 77 break; 78 } 79 80 if(n_cnt == num+1){ 81 offset += (numRead-1 - i); 82 break; 83 } 84 else 85 offset += numRead; 86 if(type) 87 break; //若是行數小於要求的,當文件的偏移量回到文件的開始位置,即buf從數組結尾回到數組的頭部時,跳出循環。 88 } 89 off_cnt++; 90 memset(buf, 0, BUF_SIZE+1); 91 } 92 if(seek == -1) 93 errExit("lseek"); 94 95 if(lseek(fd, (0 - offset), SEEK_END) == -1) 96 errExit("lseek"); 97 98 while((numRead = read(fd, buf, BUF_SIZE)) > 0){ 99 buf[numRead] = '\0'; 100 printf("%s", buf); 101 memset(buf, 0, BUF_SIZE+1); 102 } 103 if(numRead == -1) 104 errExit("read"); 105 106 exit(EXIT_SUCCESS); 107 }
測試結果:
1、
lancelot@debian:~/Code/tlpi$ ./13.5 13.5.c while((numRead = read(fd, buf, BUF_SIZE)) > 0){ buf[numRead] = '\0'; printf("%s", buf); memset(buf, 0, BUF_SIZE+1); } if(numRead == -1) errExit("read"); exit(EXIT_SUCCESS); } lancelot@debian:~/Code/tlpi$ tail 13.5.c while((numRead = read(fd, buf, BUF_SIZE)) > 0){ buf[numRead] = '\0'; printf("%s", buf); memset(buf, 0, BUF_SIZE+1); } if(numRead == -1) errExit("read"); exit(EXIT_SUCCESS); }
2、
lancelot@debian:~/Code/tlpi$ tail -n 15 13.5.c errExit("lseek"); if(lseek(fd, (0 - offset), SEEK_END) == -1) errExit("lseek"); while((numRead = read(fd, buf, BUF_SIZE)) > 0){ buf[numRead] = '\0'; printf("%s", buf); memset(buf, 0, BUF_SIZE+1); } if(numRead == -1) errExit("read"); exit(EXIT_SUCCESS); } lancelot@debian:~/Code/tlpi$ ./13.5 -n 15 13.5.c errExit("lseek"); if(lseek(fd, (0 - offset), SEEK_END) == -1) errExit("lseek"); while((numRead = read(fd, buf, BUF_SIZE)) > 0){ buf[numRead] = '\0'; printf("%s", buf); memset(buf, 0, BUF_SIZE+1); } if(numRead == -1) errExit("read"); exit(EXIT_SUCCESS); }
---------------多點使用系統調用和庫函數纔會熟練,只要熟練就編程纔不會以爲很難上手啊。。。。。。
---------------另外那篇關於動態規劃的算法導論學習記錄感受寫不下去了。。。。。。。。。。。。。。
---------------繼續努力!!!!!!