《Linux/Unix系统编程手册》读书笔记8 (文件I/O缓冲)
第13章
这章主要将了关于文件I/O的缓冲。
系统I/O调用(即内核)和C语言标准库I/O函数(即stdio函数)在对磁盘进行操作的时候都会发生缓冲。通过缓冲可以在一定程度上将用户空间与实际的物理设备分离,还可以减少内核访问磁盘的次数。
先来看看关于内核缓冲区高速缓冲:read和write调用在对磁盘文件进行操作的时候不会直接访问磁盘,如下图所示。
例如:write(fd, "abc", 3) write调用会将"abc"从用户空间缓冲区传递内核缓冲区中并随即返回。在之后的某个时刻(缓冲区满了或者需要刷新),内核会将其缓冲区的数据写入到磁盘上,因此write系统调用与访问磁盘的操作不是同步执行的。同理,对于read(fd, buffer, 3):内核会从磁盘读取数据并存在内核的缓冲区上,read调用再从内核缓冲区读取3个字节的数据到用户缓冲区。当缓冲区的全部数据被读取完,内核才会再从磁盘文件读取下一段数据。
这样的设计可以使得read和write系统调用不用等待磁盘操作从而加快操作的速度,还减少了内核访问磁盘的次数。
PS:Linux内核对缓冲区(内核缓冲区)的大小没有固定的上限,但是可用物理内存总量和其他进程对物理内存的需求会影响缓冲区的大小。
内核访问磁盘的字节数是固定的,所以尽量使得每次read(write)传输的字节数达到合适的数目可以减少系统调用所消耗的时间。
下表为书中的一个关于复制100MB大小的文件花费的时间,BUF_SIZE为传输的字节数, Elapsed为总共的用时,Total CPU为CPU的总共用时,User CPU为用户CPU的用时,System CPU为系统CPU用时。测试的文件系统为块大小为4096字节的ext2。
当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 }
测试结果:
一、
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); }
---------------多点使用系统调用和库函数才会熟练,只要熟练就编程才不会觉得很难上手啊。。。。。。
---------------另外那篇关于动态规划的算法导论学习记录感觉写不下去了。。。。。。。。。。。。。。
---------------继续努力!!!!!!