文章目录
- 如何理解缓冲区
- 现象
- 概念:文件缓冲区
- 为什么要有缓冲区
- 缓冲区在哪里
- 自己封装一个简单的文件接口
- 自主封装
- 目标
- 代码
- 关于缓冲区
- 强制刷新内核
- 关于字符串格式化函数
- printf和scanf函数
如何理解缓冲区
以前写过一个进度条, 有一个输出缓冲区->这个缓冲区在哪里,为什么要存在
struct file [缓冲区]中的缓冲区与上面这个缓冲区有关系吗
1.先看现象->提出问题
2.提出文件缓冲区
3.解释问题
现象
int main()
{//C库fprintf(stdout,"hello fprintf\n");//系统调用const char* msg = "hello write\n";write(1,msg,strlen(msg));//这里不用加上\0fork();return 0
}
这样可以打印出来
hello fprintf
hello write
因为此时都是向显示器打印,是采用行缓冲,所以直接就刷新出来的(见下图中的解释)
但是如果我们重定向:./myfile > log.txt
结果不一样了:
hello write
hello fprintf
hello fprintf
但是如果不加fork();
就不会产生这样的结果.
因为此时是普通文件,采用的刷新策略是全缓冲
所以真正的调用顺序应该是:在fork之前,write就直接打印进文件了,但是fwrite只是写在缓冲区中.在fork之后,fwrite的缓冲区中的文件变成了两份(写时拷贝),由此,会出现打印两次的现象.(下图中有解释)
概念:文件缓冲区
为什么要有缓冲区
可以节省调用者的时间:系统调用也是要花费大量时间的
进程可以继续做自己的事情,最后统一刷新
缓冲区在哪里
在你进行fopen打开文件的时候,会得到一个FILE结构体,缓冲区就在该结构体中
而调用write时,是系统调用,没有缓冲区,会直接刷新出来
自己封装一个简单的文件接口
自主封装
目标
用最简单的方式,呈现出对FILE的理解
特点:实现的是一个demo版本,重在呈现原理
代码
makefile
myfile:main.c myfile.cgcc -o $@ $^
.PHONY:clean
clean:rm -f myfile
myfile.h
#pragma once#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <malloc.h>
#include <unistd.h>
#include <assert.h>#define NUM 1024
#define BUFF_NONE 0x1
#define BUFF_LINE 0x2
#define BUFF_ALL 0x4typedef struct MY_FILE
{int fd;char outputbuffer[NUM];int flags; // 刷新方式int current;
} MY_FILE;MY_FILE *my_fopen(const char *path, const char *mode);
size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MY_FILE *stream);
int my_fclose(MY_FILE *fp);
myfile.c
#include "myfile.h"MY_FILE *my_fopen(const char *path, const char *mode)
{int flags = 0;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);mode_t m = 0666;int fd = 0;if (flags & O_CREAT)fd = open(path, flags, m);elsefd = open(path, flags);if (fd < 0){perror("open error");return NULL;}MY_FILE *mf = (MY_FILE *)malloc(sizeof(MY_FILE));if (mf == NULL){close(fd);return NULL;}mf->fd = fd;mf->flags = 0;mf->current = 0;mf->flags |= BUFF_LINE;// mf->outputbuffer[0] = 0;//初始化缓冲区memset(mf->outputbuffer, '\0', sizeof(mf->outputbuffer));return mf;
}int my_fflush(MY_FILE *fp)
{assert(fp);// 将用户缓冲区中的数据,通过系统调用接口,冲刷给OSwrite(fp->fd, fp->outputbuffer, fp->current);fp->current = 0;fsync(fp->fd);return 0;
}size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MY_FILE *stream)
{// 1. 缓冲区如果已经满了,就直接写入assert(stream);if (stream->current == NUM)my_fflush(stream);// 2. 根据缓冲区剩余情况,进行数据拷贝即可size_t user_size = size * nmemb;size_t my_size = NUM - stream->current;size_t writen = 0;if (my_size >= user_size){memcpy(stream->outputbuffer + stream->current, ptr, user_size);//3. 更新计数器字段stream->current += user_size;writen = user_size;}else{memcpy(stream->outputbuffer + stream->current, ptr, my_size);//3. 更新计数器字段stream->current += my_size;writen = my_size;}// 4. 开始计划刷新, 他们高效体现在哪里 -- TODO// 不发生刷新的本质,不进行写入,就是不进行IO,不进行调用系统调用,所以my_fwrite函数调用会非常快,数据会暂时保存在缓冲区中// 可以在缓冲区中积压多份数据,统一进行刷新写入,本质:就是一次IO可以IO更多的数据,提高IO效率if (stream->flags & BUFF_ALL){if (stream->current == NUM)my_fflush(stream);}else if (stream->flags & BUFF_LINE){if (stream->outputbuffer[stream->current - 1] == '\n')my_fflush(stream);}return writen;
}int my_fclose(MY_FILE *fp)
{assert(fp);// 1.关闭文件的时候,C要帮助我们进行冲刷缓冲区if (fp->current > 0){my_fflush(fp);}// 2.关闭文件close(fp->fd);// 3.释放堆空间free(fp);// 4.指针置为NULLfp = NULL;return 0;
}
关于缓冲区
1.历史上我们所谈的缓冲区指的是:用户级缓冲区,语言提供
2.用户层+内核->强制刷新内核
强制刷新内核
fsync(fp->fd);
关于字符串格式化函数
printf和scanf函数
int my_printf(const char* format,...)
{//1.先获取对应的变量a//2.定义缓冲区,对a转成字符串//2.1 fwrite(stdout,str);//3.将字串拷贝的stdout->buffer即可//4.结合刷新策略显示即可
}
完结.