python 做网站 数据库,建站系统低价建站新闻资讯,西安网页设计师培训班,wordpress 文章分类列表需要云服务器等云产品来学习Linux的同学可以移步/–腾讯云–/官网#xff0c;轻量型云服务器低至112元/年#xff0c;优惠多多。#xff08;联系我有折扣哦#xff09; 文章目录 1. 一个奇怪的现象2. 为什么要有缓冲区3. 缓冲区的刷新策略4. 缓冲区在哪里5. 实现一…需要云服务器等云产品来学习Linux的同学可以移步/–腾讯云–/官网轻量型云服务器低至112元/年优惠多多。联系我有折扣哦 文章目录 1. 一个奇怪的现象2. 为什么要有缓冲区3. 缓冲区的刷新策略4. 缓冲区在哪里5. 实现一个简易的C语言FILE结构体6. 缓冲区理解 1. 一个奇怪的现象
#include stdio.h
#include unistd.h
#include string.h
int main()
{printf(hello printf\n);fprintf(stdout, hello fprintf\n);fputs(hello fputs\n, stdout);const char* msg hello write\n;write(1, msg, strlen(msg));fork();return 0;
}这个运行结果看起来是没有任何问题的。那么现在我们把运行结果进行一次重定向 明显看到打印出来的数据变多了这是什么原因呢
这里我们把fork给去掉试试 所以可以推测出来一定是fork的问题。
仔细观察可以发现出现重复打印的是C语言的接口所以应该是C语言的问题
这里面实际上是一个C语言缓冲区的问题接下来我们来了解一下这个C语言缓冲区的相关内容
2. 为什么要有缓冲区
1. 缓冲区是什么
缓冲区本质上就是一段内存 在内存中提前预留一段空间用来存储即将输入或者输出的内容这一段预留的空间就是缓冲区。
2. 为什么要有缓冲区
我们知道内存相对于外设来说是一个高速设备如果每次IO都要从外设读取或者直接写到外设这种等待的时间太长了相对来说所以就干脆预留一段空间将所有需要写入到外设的数据拷贝到内存的某个空间缓冲区然后由操作系统决定什么时候真正写入到外设。这种方式能够很大的提高效率。这个拷贝数据的过程我们并不需要手动实现而是通过fwrite来实现那么实际上这个fwrite的本质就是一个拷贝功能的函数fread同理 **通过这种策略数据可以直接拷贝到缓冲区高速设备不用在等待低速设备提高计算机的效率。 **
3. 缓冲区的刷新策略
上文中我们说到缓冲区的数据什么时候真正写到磁盘中是由操作系统决定的那么操作系统将缓冲区的内容写到磁盘中遵循什么样的策略呢 缓冲区的刷新策略 1. 立即刷新无缓冲缓冲区中一出现数据就刷新到外设中——这种很少出现 2. 行刷新行缓冲——数据按行刷新每拷贝一行数据就刷新一次显示器采用的就是这种刷新策略因为显示器是给人看了而按行刷新符合人的阅读习惯同时刷新效率也不会太低 3. 缓冲区满刷新全缓冲——待数据把缓冲区填满后再刷新这种刷新方式效率最高一般应用于磁盘文件 两种特殊情况 用户使用 fflush 等函数强制进行缓冲区刷新一般进程推出后缓冲区会被刷新 4. 缓冲区在哪里
把思绪回到文章开头的那个现象中有了现在的知识储备我们再来思考一下这个现象加上fork之后向显示器上打印的数据有四条向文件中写入的数据有七条那么肯定是有三条数据在缓冲区中没有被刷新出来。注意这三条数据是C语言提供的接口产生的系统调用的接口并没有出现这种现象所以这个缓冲区肯定是在语言层的。
实际上C语言封装了一个FILE结构体在FILE结构体中除了我们之前说的fd之外还维护了一个缓冲区
我们来找一找FILE结构体的源码
在/usr/include/stdio.h中 在/usr/include/libio.h中 所以现在我们再来解释一下这个奇怪的现象 一般C库函数写入文件的时候是全缓冲的但是写入到显示器是行缓冲printf fwrite库函数是自带缓冲区的当发生了重定向之后写入显示器的行缓冲就会变成全缓冲所以我们放入缓冲区的数据不会被立刻刷新直到fork之后这个缓冲区也被拷贝了两份进程退出之后所有的缓冲区数据会被刷新写入到文件中包括子进程的缓冲区所以就有了两份数据由于这个缓冲区是语言层的write系统调用并没有这个缓冲区所以write的数据不会被拷贝两份 简单总结来说重定向导致刷新策略发生了改变由行缓冲变成了全缓冲。同时发生了写时拷贝缓冲区的数据变成了两份一样的父子进程各自刷新出现重复写入同一份数据。
5. 实现一个简易的C语言FILE结构体
注意这里实现的只是demo级的FILE当然会有很多bug理解其中意思就行。
/* myStdio.h */
#pragma once#include sys/types.h
#include sys/stat.h
#include unistd.h
#include fcntl.h
#include string.h
#include errno.h
#include stdlib.h#define SIZE 1024 // 缓冲区容量// 缓冲区刷新策略
#define SYNC_NOW 1
#define SYNC_LINE 2
#define SYNC_FULL 4typedef struct _FILE //定义的FILE结构体
{int fileno; //文件描述符int flag; //缓冲区刷新方式char buffer[SIZE]; //缓冲区int cap; //缓冲区容量int size; //缓冲区写入的数据大小
}FILE_;//这里我们自己实现的函数和结构体在后面加了下划线
FILE_ *fopen_(const char* path_name, const char* mode);
void fclose_(FILE_* fp);
void fflush_(FILE_* fp);
void fwrite_(const void *ptr, int num, FILE_* fp);/* myStdio.c */
#include myStdio.h
FILE_ *fopen_(const char* path_name, const char* mode)
{int flags 0; // 打开方式int defaultMode 0666; // 默认的创建文件权限//设置打开的方式if(strcmp(mode, r) 0){flags | O_RDONLY;}else if(strcmp(mode, w) 0){flags | (O_WRONLY | O_CREAT | O_TRUNC);}else if(strcmp(mode, a) 0){flags | (O_WRONLY | O_CREAT | O_APPEND);}else{//do nothing}//按照不同方式打开文件并记录文件的fdint fd 0;if(flags O_RDONLY) // 只读fd open(path_name, flags);else //可能要创建fd open(path_name, flags, defaultMode);if(fd 0){//文件打开失败char* str strerror(errno);write(2, str, strlen(str));return NULL;}//文件打开成功构造FILE*对象并填充内容FILE_* fp (FILE_*)malloc(sizeof(FILE_));if(fp NULL){char* str strerror(errno);write(2, str, strlen(str));return NULL;}fp-fileno fd;fp-cap SIZE;fp-size 0;memset(fp-buffer, 0, SIZE);fp-flag SYNC_LINE;return fp;
}void fflush_(FILE_* fp)
{//如果缓冲区有内容的话执行写入if(fp-size 0){write(fp-fileno, fp-buffer, fp-size);fp-size 0;}
}void fclose_(FILE_* fp)
{//刷新缓冲区fflush_(fp);//关闭文件close(fp-fileno);//释放FILE结构体free(fp);fp NULL;
}
void fwrite_(const void *ptr, int num, FILE_* fp)
{//写到缓冲区(本质上是拷贝吧数据从ptr中拷贝到fp-buffer)memcpy(fp-buffer fp-size, ptr, num);fp-size num;//执行刷新策略if(fp-flag SYNC_NOW) //无缓冲{if(fp-size ! 0){write(fp-fileno, fp-buffer, fp-size);fp-size 0;//清空缓冲区}}else if(fp-flag SYNC_LINE) // 行缓冲{if(fp-buffer[fp-size - 1] \n) //这里不考虑 abcd\nefg{write(fp-fileno, fp-buffer, fp-size);fp-size 0;//清空缓冲区}}else if(fp-flag SYNC_FULL) // 全缓冲{if(fp-size fp-cap){write(fp-fileno, fp-buffer, fp-size);fp-size 0;//清空缓冲区}}else{//do nothing}
}/* mian.c */
#include myStdio.hint main()
{FILE_* fp fopen_(./log.txt, w);if(fp NULL){return 1;}const char* msg hello world\n;fwrite_(msg, strlen(msg), fp);fclose_(fp);return 0;
}6. 缓冲区理解
实际上我们上文中一直在讲的是都是语言层面的缓冲区但是**操作系统也是有缓冲区的** 。
我们在把一串信息写入到外设磁盘中的时候经历了以下过程 通过fputs、fprintf、fwrite等函数把massage写入到**FILE结构体中的缓冲区 **通过系统调用write将FILE结构体中的缓冲区内容写入到OS内核缓冲区中由OS决定什么时候将OS内核的数据真正写入到磁盘中 那么出现一个问题如果在第二步结束之后操作系统宕机了怎么办
可能会导致数据丢失。
那么如果在一个对数据丢失0容忍的系统内出现这个问题咋办
有一个系统调用可以解决这个问题fsync 这个系统调用的功能就是强制将内核缓冲区中的数据立刻同步到外设中而不再采用操作系统的刷新策略 所以我们上文中实现的fflush_函数事实上得加上这句话
void fflush_(FILE_* fp)
{//如果缓冲区有内容的话执行写入if(fp-size 0){write(fp-fileno, fp-buffer, fp-size);fp-size 0;fsync(fp-fileno);}
}本节完…