《Linux/Unix系統(tǒng)編程手冊(cè)》讀書筆記 目錄
第13章
這章主要將了關(guān)于文件I/O的緩沖。
系統(tǒng)I/O調(diào)用(即內(nèi)核)和C語言標(biāo)準(zhǔn)庫I/O函數(shù)(即stdio函數(shù))在對(duì)磁盤進(jìn)行操作的時(shí)候都會(huì)發(fā)生緩沖。通過緩沖可以在一定程度上將用戶空間與實(shí)際的物理設(shè)備分離,還可以減少內(nèi)核訪問磁盤的次數(shù)。
先來看看關(guān)于內(nèi)核緩沖區(qū)高速緩沖:read和write調(diào)用在對(duì)磁盤文件進(jìn)行操作的時(shí)候不會(huì)直接訪問磁盤,如下圖所示。

例如:write(fd, "abc", 3) write調(diào)用會(huì)將"abc"從用戶空間緩沖區(qū)傳遞內(nèi)核緩沖區(qū)中并隨即返回。在之后的某個(gè)時(shí)刻(緩沖區(qū)滿了或者需要刷新),內(nèi)核會(huì)將其緩沖區(qū)的數(shù)據(jù)寫入到磁盤上,因此write系統(tǒng)調(diào)用與訪問磁盤的操作不是同步執(zhí)行的。同理,對(duì)于read(fd, buffer, 3):內(nèi)核會(huì)從磁盤讀取數(shù)據(jù)并存在內(nèi)核的緩沖區(qū)上,read調(diào)用再從內(nèi)核緩沖區(qū)讀取3個(gè)字節(jié)的數(shù)據(jù)到用戶緩沖區(qū)。當(dāng)緩沖區(qū)的全部數(shù)據(jù)被讀取完,內(nèi)核才會(huì)再從磁盤文件讀取下一段數(shù)據(jù)。
這樣的設(shè)計(jì)可以使得read和write系統(tǒng)調(diào)用不用等待磁盤操作從而加快操作的速度,還減少了內(nèi)核訪問磁盤的次數(shù)。
PS:Linux內(nèi)核對(duì)緩沖區(qū)(內(nèi)核緩沖區(qū))的大小沒有固定的上限,但是可用物理內(nèi)存總量和其他進(jìn)程對(duì)物理內(nèi)存的需求會(huì)影響緩沖區(qū)的大小。
內(nèi)核訪問磁盤的字節(jié)數(shù)是固定的,所以盡量使得每次read(write)傳輸?shù)淖止?jié)數(shù)達(dá)到合適的數(shù)目可以減少系統(tǒng)調(diào)用所消耗的時(shí)間。
下表為書中的一個(gè)關(guān)于復(fù)制100MB大小的文件花費(fèi)的時(shí)間,BUF_SIZE為傳輸?shù)淖止?jié)數(shù), Elapsed為總共的用時(shí),Total CPU為CPU的總共用時(shí),User CPU為用戶CPU的用時(shí),System CPU為系統(tǒng)CPU用時(shí)。測(cè)試的文件系統(tǒng)為塊大小為4096字節(jié)的ext2。

當(dāng)BUF_SIZE為4096字節(jié)的時(shí)候,達(dá)到最優(yōu)性能,再繼續(xù)增大BUF_SIZE不會(huì)對(duì)性能有太大的影響,是因?yàn)橄到y(tǒng)調(diào)用(read和write)花費(fèi)的時(shí)間與在用戶空間和內(nèi)核空間之間傳輸數(shù)據(jù)以及實(shí)際磁盤操作所花費(fèi)的時(shí)間對(duì)比已經(jīng)微不足道。
再來看看第二個(gè)表,是關(guān)于寫入一個(gè)100MB大小的文件所需的時(shí)間。

其實(shí)再進(jìn)行write調(diào)用后并沒有這么快執(zhí)行磁盤I/O,因?yàn)閷?shí)際計(jì)算機(jī)的RAM是很大(測(cè)試環(huán)境是4G),所以結(jié)合前表可以知道復(fù)制文件耗時(shí)絕大部分是用在磁盤的讀取。
接著來看stdio庫的緩沖。
stdio庫的一些函數(shù)(fPRintf, fscanf, fgets, fputs, fgets, fputc, fgetc)會(huì)幫我們自動(dòng)采取大塊數(shù)據(jù)緩沖以減少系統(tǒng)調(diào)用。
通過setvbuf設(shè)置stdio庫函數(shù)的緩沖方式
1 #include <stdio.h>2 3 int setvbuf(FILE *stream, char *buf, int mode, size_t size);
成功調(diào)用返回0,失敗返回非0值。
其中stream為文件流(PS:先打開文件流再調(diào)用setvbuf),buf為使用的緩沖區(qū),size為緩沖區(qū)的大小。當(dāng)buf不為NULL,就指向size大小的內(nèi)存塊作為stream的緩沖區(qū);當(dāng)buf為NULL,stdio庫會(huì)為stream自動(dòng)分配一個(gè)緩沖區(qū)。mode為緩沖的類型。
mode的取值:
| _IONBF | 不對(duì)I/O進(jìn)行緩沖,即立即調(diào)用write和read |
| _IOLBF | 采用行緩沖I/O(終端設(shè)備的流的默認(rèn)采用) |
| _IOFBF | 單次讀寫數(shù)據(jù)的大小與緩沖區(qū)相同(磁盤的流默認(rèn)采用) |
除了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緩沖區(qū)
1 #include <stdio.h>2 3 int fflush(FILE *stream);
成功調(diào)用返回0,失敗返回EOF。
如果stream為NULL,fflush會(huì)刷新所有的緩沖區(qū)。
如果將fflush用在輸入流,可以將已緩沖的輸入數(shù)據(jù)全部丟棄。
在C函數(shù)庫的實(shí)現(xiàn)中,如果stdin和stdout指向同一個(gè)終端,那么從stdin讀取輸入時(shí)都會(huì)隱含調(diào)用fflush(stdout)。
----------------------暫時(shí)省略同步I/O。。。。這部分翻譯的很坑,看不懂。。。。
----------------------還有直接I/O。。。。。。。。。。。。。。。。。。。。。。
下圖為I/O緩沖小結(jié):

練習(xí):
13-5. tail [ -n num ] file 命令打印名為file文件的最后路面行(默認(rèn)為10行)。使用I/O系統(tǒng)調(diào)用(lseek()、read()、write()等)來實(shí)現(xiàn)該命令。
1 /* 2 * ===================================================================================== 3 * 4 * Filename: 13.5.c 5 * 6 * Description: 簡單tail實(shí)現(xiàn),可能存在bug,但是沒有找到!!! 7 * 8 * Version: 1.0 9 * Created: 2014年05月02日 18時(shí)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 //獲取最后的行數(shù) 46 num = (flag == TRUE) ? getInt(argv[2], GN_GT_0, "num") : 10; 47 48 //打開文件對(duì)應(yīng)的文件描述符 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字節(jié) 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 //根據(jù)換行符判斷行數(shù) 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; //如果行數(shù)小于要求的,當(dāng)文件的偏移量回到文件的開始位置,即buf從數(shù)組結(jié)尾回到數(shù)組的頭部時(shí),跳出循環(huán)。 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 }測(cè)試結(jié)果:
一、
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);}二、
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);}---------------多點(diǎn)使用系統(tǒng)調(diào)用和庫函數(shù)才會(huì)熟練,只要熟練就編程才不會(huì)覺得很難上手啊。。。。。。
---------------另外那篇關(guān)于動(dòng)態(tài)規(guī)劃的算法導(dǎo)論學(xué)習(xí)記錄感覺寫不下去了。。。。。。。。。。。。。。
---------------繼續(xù)努力!!!!!!
新聞熱點(diǎn)
疑難解答
圖片精選