目录
1.缓冲区基础
1.1缓冲区的刷新策略
1.1.1三种刷新策略
1.1.2.两种强制刷新策略
2.用户级语言层缓冲区
2.1.默认在显示器输出
2.2.重定向到文件输出
2.3.write调用没有显示两份的原因
3.模拟实现文件缓冲区
3.1 myFileBuffer.h
3.2 myFileBuffer.c
4.系统内核缓冲区
最后
1.缓冲区基础
缓冲区本身就是一段内存,就是用来执行做缓存的一段内存空间。
内存对磁盘进行存储信息,称为外设IO,速度很慢
而缓冲区是,直接在内存中开辟一段空间,将数据拷贝在缓存区中,在缓存区的数据会定期刷新给磁盘,有效的节省了进程进行数据IO的时间。
1.1缓冲区的刷新策略
缓冲区会结合具体的设备执行自己的刷新策略。
1.1.1三种刷新策略
1.立即刷新 无缓冲
2.行刷新 行缓冲 显示器(显示器设备特殊符合人类的阅读习惯,按行缓存提升效率不至于太低
3.缓冲区刷新 全缓冲 磁盘文件 (效率最高,定期一次刷新出去
1.1.2.两种强制刷新策略
1.用户强制刷新 fflush()
2.进程退出 ,一般都要进行缓冲区刷新
2.用户级语言层缓冲区
这个缓冲区在C语言提供的FILE结构体中
测试代码:
1 #include<stdio.h>2 #include<unistd.h>3 #include<string.h>4 5 int main()6 {7 printf("hello printf\n");8 fprintf(stdout,"hello fprintf\n");9 const char* fputsString="hello fputs\n";10 fputs(fputsString,stdout);11 12 //系统接口13 const char* writeString="hello write\n"; 14 write(1,writeString,strlen(writeString));15 16 fork();17 18 return 0;19 } 20
在显示器上输出
重定向到文件输出
两种输出方式结果不同,其中重定向到文件输出的内容,比直接到显示器输出的内容c语言的接口多输出了一遍,是因为文件和显示器的缓冲区刷新策略不同。
在代码结束之前,先创建了子进程
2.1.默认在显示器输出
显示器的刷新策略是行缓冲刷新,在进程fork之前,三条c语言的内容已经被行刷新到显示器上。在fork后其FILE内部已经不存在对应的数据。
2.2.重定向到文件输出
文件的刷新策略是全缓冲,之前的显示函数虽然带了\n,但是不足以把文件的缓冲区写满,此时数据并没有刷新。
在执行fork时,创建子进程,紧接着就是进程退出,进程退出默认刷新缓冲区。此时发生写时拷贝,后面退出的进程会再刷新一份,所以最终数据显示了两份。
2.3.write调用没有显示两份的原因
write是系统调用接口,上面的过程都是采用的用户级语言层面给我们提供的缓冲区,write使用的不是FILE而是fd,更加底层。
3.模拟实现文件缓冲区
通过myFileBuffer.h和myFileBuffer.c两个文件,模拟实现语言级文件缓冲区FILE
3.1 myFileBuffer.h
1 #pragma once2 #include<string.h>3 #include<sys/types.h>4 #include<sys/stat.h>5 #include<fcntl.h>6 #include<unistd.h>7 #include<assert.h>8 #include<errno.h>9 #include<stdlib.h>10 11 #define SIZE 1024 12 13 //刷新方式 14 #define SYNC_NOW 1 15 #define SYNC_LINE 216 #define SYNC_FULL 417 18 typedef struct FILE_{ 19 int flags;//刷新方式 20 int fileno;//文件描述符21 int cap;//buffer总容量 22 int size;//buffer当前的使用量23 char buffer[SIZE];24 }_FILE;25 26 _FILE* fopen_(const char*path_name,const char*mode);27 void fwrite_(const void *ptr,int num,_FILE *fp);28 void fclose_(_FILE* fp);29 void fflush_(_FILE *fp);
3.2 myFileBuffer.c
1 #include"myFileBuffer.h" 2 3 _FILE* fopen_(const char*path_name,const char*mode){ 4 5 int flags=0; 6 int defaultMode=0666; 7 8 //判断不同的打开方式设置flags 这里举例w a r 9 if(strcmp(mode,"r")==0) 10 { 11 flags=O_RDONLY; 12 13 } 14 else if(strcmp(mode,"w")==0) 15 { 16 flags=O_WRONLY|O_CREAT|O_TRUNC; 17 } 18 else if(strcmp(mode,"a")==0) 19 { 20 flags=O_WRONLY|O_CREAT|O_APPEND; 21 } 22 else{ 23 24 } 26 //根据不同的打开方式,打开文件 27 int fd=0; 28 if(flags&O_RDONLY)29 { 30 fd=open(path_name,flags); 31 } 32 else{ 33 fd=open(path_name,flags,defaultMode); 34 }35 36 //printf("我的fd是 %d\n",fd);37 //如果打开失败38 if(fd<0)39 {40 const char* err=strerror(errno);41 write(2,err,strlen(err));42 return NULL;43 }44 45 //创建文件缓冲区并对其初始化46 _FILE*fp=(_FILE*)malloc(sizeof (_FILE));47 assert(fp);48 fp->flags=SYNC_LINE;//默认设置成行刷新49 fp->fileno=fd;50 fp->cap=SIZE;51 fp->size=0;52 memset(fp->buffer,0,SIZE);//初始化文件缓冲区53 54 return fp;55 }56 57 void fwrite_(const void *ptr,int num,_FILE *fp) 58 {59 //写到文件缓冲区中60 memcpy(fp->buffer+fp->size,ptr,num);//注意这里的起始地址61 fp->size+=num;62 63 // printf("fd->fileno是 %d\n",fp->fileno);64 65 //判断是否刷新66 if(fp->flags&SYNC_NOW)67 {68 //立即刷新69 write(fp->fileno,fp->buffer,fp->size);70 fp->size=0;71 }72 else if(fp->flags&SYNC_FULL)73 {74 //等到缓冲区写满再刷新75 if(fp->size==fp->cap)76 {77 write(fp->fileno,fp->buffer,fp->size);78 fp->size=0;79 }80 }81 else if(fp->flags&SYNC_LINE)82 {83 if(fp->buffer[fp->size-1]=='\n')84 { 85 // printf("这里是行缓冲\n");86 87 write(fp->fileno,fp->buffer,fp->size);88 fp->size=0;89 //把_FILE缓冲区的内容拷贝到内核缓冲区中90 } 91 }92 else{93 94 }95 96 }97 void fclose_(_FILE* fp)98 {99 //文件退出前会强制刷新
100 fflush_(fp);
101 close(fp->fileno);
102 }
103 void fflush_(_FILE *fp)
104 {
105 //强制刷新
106 if(fp->size>0)
107 {
108 write(fp->fileno,fp->buffer,fp->size);
109 fp->size=0;
110 }
111 }
4.系统内核缓冲区
os的刷新策略很复杂,是权衡自己整体的内存使用情况来进行相应的刷新,并不是上文所讲述的简单的刷新方法。这与用户无关。
整体过程就是:
把内容由用户代码拷贝到 C语言的缓冲区中,再由C语言缓冲区拷贝到内核缓冲区,再有内核缓冲区刷新到外设。
最后
加油