【Linux】14. 文件缓冲区

news/2024/12/20 14:00:36/

1. 文件缓冲区的引出

在这里插入图片描述
如上现象,在学习完文件缓冲区之后即可解释

2. 认识缓冲区

缓冲区的本质就是内存当中的一部分,那么是谁向内存申请的? 是属于谁的? 为什么要存在缓冲区呢?
在这里插入图片描述
道理是如此,在之前的学习过程中我们也确实知道操作系统中存在缓冲区的概念,但是我们在编写代码时,并未执行将数据拷贝到缓冲区的代码呀,操作系统是怎么将数据拷贝到缓冲区的呢?
– 与其将fwrite函数理解成是写入到文件当中的函数,不如将其理解为fwrite是拷贝函数,将数据从进程拷贝到"缓冲区"或者外设当中

3. 缓冲区的刷新策略

如果此时存在一些数据需要写入到外设(磁盘文件)当中,是一次性写入效率高还是多次少量写入效率高?
答:当然是一次性写入效率高,数据写入到外设需要对外设进行请求,对于CPU来说写入是非常高效的,但是对于外设响应CPU请求非常低效,所以一次性写入就只需要请求一次,而多次分批写势必效率更低

缓冲区一定会结合具体的设备来定制自己的刷新策略,通常有以下3种刷新策略:

  1. 立即刷新 – 无缓冲
  2. 行刷新 – 行缓冲
  3. 缓冲区满 – 全缓冲

通常针对磁盘文件而言,采用的是全缓冲(最高效)
对于显示器而言,采用的是行缓冲,因为显示器是给用户显示的。
若是全缓冲,一次性将数据全部刷新出来,用户难以阅读数据,而无缓冲又太低效了,所以采用的行缓冲(方便用户)

同样的刷新策略也存在特殊情况:

  1. 当用户进行强制刷新时(调用fflush接口)
  2. 进程退出 – 一般情况下都需要进行缓冲区刷新

4. 解释现象

最开始我们引出文件缓冲区是通过fork函数后将运行结果重定向到文件当中发现调用C接口的数据会打印两遍,而调用系统接口的数据只打印一遍,该现象与缓冲区存在什么关系呢?
首先,我们要明确一点该现象一定与缓冲区有关,还有一点就是缓冲区一定不存在于内核当中,不然系统接口也应该打印两遍
我们之前所谈论的所有缓冲区都是用户级语言层面给我们提供的缓冲区
该缓冲区存在于stdout,stderr,stdin当中,这三者都被文件指针所指向(FILE*) 而FILE结构体是经过C语言封装过后,其中包含fd(文件描述符)和一个缓冲区
所有我们想要立即获取数据就要强制刷新(fflush(文件指针)),在关闭文件时也需要传入文件指针(fclose(文件指针))

4.1 C语言库中的源代码

在这里插入图片描述
从源码来看,可以看出FILE结构体当中不仅封装文件描述符,文件的打开方式还封装了缓冲区

基于以上认识,我们就可以解释该现象了
在这里插入图片描述
在代码结束之前 fork创建子进程

  1. 如果未进行重定向,只打印4行信息
    stdout默认采用的是行刷新,
    在进程fork之前就已经将数据进行打印输出到外设(显示器)上,所以在FILE内部(或者称为进程内部)不存在对应的数据
  2. 如果进行了重定向,写入文件不再是显示器而是普通文件,采用的刷新策略就是全缓冲
    而之前的3条C打印函数虽然结尾带上\n,
    但是**并不足以将stdout缓冲区写满,**那么数据也就不会被刷新
    此时再执行fork函数,stdout是属于父进程的,创建子进程,子进程会对父进程的代码和数据进行拷贝
    fork之后紧接着就是退出,谁先退出就一定会进行缓冲区的刷新(也就是修改)
    修改会导致写时拷贝,导致数据会显示两份
  3. write为啥没有显示两份呢?
    因为上面的过程都与write无关 ,write没有FILE结构体而是采用的文件描述符fd,也就不存在C提供的缓冲区啦!

5. 深刻理解缓冲区

缓冲区到底应该怎么理解呢? 我们通过尝试自己将文件描述符,缓冲区封装成FILE来实现对缓冲区的深刻理解

5.1 功能需求实现

先将文件描述符,缓冲区封装起来,再实现文件操作的基本功能

  1. 写入数据:_fwrite
  2. 刷新数据:_fflush
  3. 关闭文件:_fclose
  4. 打开文件:_fopen
    暂时就先实现这几个简单模块,主要还是针对缓冲区的理解

5.2 基本框架搭建

[hx@hx my_stdio]$ ll
total 4
-rw-rw-r-- 1 hx hx 78 Jun  7 18:33 Makefile
-rw-rw-r-- 1 hx hx  0 Jun  7 18:33 myStdio.c
-rw-rw-r-- 1 hx hx  0 Jun  7 18:33 myStdio.h
[hx@hx my_stdio]$ cat Makefile 
main:main.c myStdio.cgcc -o $@ $^ -std=c99.PHONY:clean
clean:rm -f main

5.3 封装成_FILE

// 在myStdio.h文件当中定义1 #pragma once                                                                                                                                                  2                                                                                                                                                               3 #include <stdio.h>                                                                                                                                            4                                                                                                                                                               5 #define SIZE 1024                                                                                                                                             6                                                                                                                                                               7 typedef struct _FILE                                                                                                                                          8 {                                                                                                                                                             9   int flags; // 刷新方式:(无/行/全缓冲)                                                                                                                       10   int fileno; // 文件描述符                                                                                                                                   11   int capacity; // buffer的总容量                                                                                                                             12   int size; // buffer当前的使用量                                                                                                                             13   char buffer[SIZE]; // SIZE字节的缓冲区                                                                                                                      14 } _FILE;                                                                                                                                                      15                                                                                                                                                               16 // 文件打开需要文件打开路径和权限                                                                                                                             17 _FILE * fopen_(const char* path_name,const char *mode);                                                                                                         18                                                                                                                                                               19 // ptr是要写入文件的数据 num是数据字节数 _FILE* 文件指针                                                                                                         20 void fwrite_(const char* ptr,int num,_FILE* fp);                                                                                                              21                                                                                                                                                               22 //传文件指针关闭文件                                                                                                                                          23 void fclose_(_FILE* fp);                                                                                                                                      24                                                                                                                                                               25 //传文件指针强制刷新缓冲区                                                                                                                                    26 void fflush_(_FILE* fp);   

5.4 fopen_的实现

[hx@hx my_stdio]$ cat 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 {// 目前就简单实现这3种文件操作}// 文件描述符int fd = 0;// 以只读的方式打开文件if(flags & O_RDONLY) fd = open(path_name,flags);// 写入文件 若文件不存在 需要以defaultMode的权限创建else fd = open(path_name,flags,defaultMode);// 文件打开失败if(fd < 0){// 记录下错误信息const char* err = strerror(errno);// 将错误信息写入到标准错误(2/stderr)当中write(2,err,strlen(err));// 这也就是为啥文件打开失败要返回NULL(C语言底层就是这样实现的)return NULL;}// 下面就是文件打开成功// 在堆上申请空间_FILE * fp = (_FILE*)malloc(sizeof(_FILE));// 暴力检查 未申请成功直接报断言错误assert(fp);// 默认设置为行刷新fp->flags = SYNC_LINE;// 文件描述符置为fdfp->fileno = fd;fp->capacity = SIZE;fp->size = 0;// 将缓冲区数据置为0 保证后续往缓冲区写入数据正确memset(fp->buffer,0,SIZE);// 这也就是为啥打开文件要返回FILE*的指针(C语言底层的实现方式)return fp;
}

5.5 头文件的引用 刷新方式的定义

[hx@hx my_stdio]$ cat myStdio.h
#pragma once #include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>#define SIZE 1024// 无缓冲 
#define SYNC_NOW  1
// 行缓冲
#define SYNC_LINE 2
// 全缓冲
#define SYNC_FULL 4

5.6 fwrite_的实现

void fwrite_(const void * ptr,int num,_FILE *fp)
{// 将数据写入到缓冲区// 这里的fp->buffer+fp->size 若缓冲区当中存在数据 往后追加// 这里不考虑缓冲区溢出的问题memcpy(fp->buffer+fp->size,ptr,num);// 缓冲区数据增加num个字节fp->size += num;// 判断刷新方式// 无刷新if(fp->flags & SYNC_NOW){// 将缓冲区数据写入文件write(fp->fileno,fp->buffer,fp->size);// 将缓冲区置为0 惰性清空缓冲区fp->size = 0;}// 全刷新else if(fp->flags & SYNC_FULL){//当缓冲区满了才刷新if(fp->size == fp->capacity){write(fp->fileno,fp->buffer,fp->size);fp->size = 0;}}// 行缓冲else if(fp->flags & SYNC_LINE){//当最后1个字符为\n时,刷新数据//这里不考虑 "abcd\nefg" 这种情况if(fp->buffer[fp->size-1] == '\n'){write(fp->fileno,fp->buffer,fp->size);fp->size = 0;}}else {// 不执行任何操作}
}

5.7 fclose_和fflush_的实现

void fflush_(_FILE *fp)
{//若缓冲区内存在数据 将缓冲区的数据写入对应的文件描述符(可能是磁盘文件,也可能是显示器)if(fp->size > 0)write(fp->fileno,fp->buffer,fp->size);
}void fclose_(_FILE *fp)
{// 文件关闭前要进行数据的强制刷新fflush_(fp);// 关闭对应的文件描述符// 文件描述指向的就是文件(关闭文件)close(fp->fileno);
}

5.8 实例测试

1. 情况1

在这里插入图片描述

2. 情况2

在这里插入图片描述

3. 情况3

在这里插入图片描述

6. 理解文件刷新后的整个过程

在这里插入图片描述
用户在往文件当中写入"hello linux\n" ,先调用的C语言接口fwrite, fwrite会1将数据先写入到C语言封装的FILE缓冲区当中,再采取对应的刷新策略 进过write接口(底层接口)根据文件描述符将数据拷贝到内核缓冲区当中,最后由OS定期刷到外设(磁盘)当中。
数据要写入到外设当中要经历3次拷贝,第一次拷贝到C语言的缓冲区当中,第二次拷贝到内核缓冲区当中,第三次拷贝到外设当中
(所以fwrite/write接口的实质其实就是拷贝函数)

怎么证明该过程呢? – 无法证明,但是可以看到接口
用户调用fwrite将数据交到C语言的缓冲区当中,C语言调用操作系统底层的write接口将数据交给内核缓冲区,如果在这个过程当中OS宕机了怎么办?
操作系统宕机也就意味着缓冲在内核缓冲区的数据还未刷新到外设当中,那么就会造成数据丢失,如果用户对数据丢失0容忍怎么办(假设用户是银行机构,数据丢失影响重大),那该怎么办?
操作系统当中存在接口 fsync – 强制刷新

7. 对强制刷新的深刻理解

[hx@hx my_stdio]$ man 2 fsync
FSYNC(2)                               Linux Programmer's Manual                               FSYNC(2)NAMEfsync, fdatasync - synchronize a file's in-core state with storage deviceSYNOPSIS#include <unistd.h>int fsync(int fd);

调用该接口告知操作系统别在按照自己的刷新策略刷新数据,只要拿到数据就立马刷新到外设当中

7.1 实例

在fflush_ 当中强制刷新接口 fsync才是真正的强制刷新 fwrite只能算拷贝

void fflush_(_FILE *fp)
{//若缓冲区内存在数据 将缓冲区的数据写入对应的文件描述符(可能是磁盘文件,也可能是显示器)if(fp->size > 0)write(fp->fileno,fp->buffer,fp->size);// 强制要求操作系统对外设进行刷新fsync(fp->fileno);// 刷新完 将size置为0 表示此时缓冲区内无数据fp->size = 0;
}

在这里插入图片描述


http://www.ppmy.cn/news/275908.html

相关文章

相机基础入门

一张好照片组成的三个要素&#xff1a;曝光、对焦、色彩 课程链接&#xff1a; 摄影基础课-蚂蚁摄影_哔哩哔哩_bilibili 一、曝光 曝光过度&#xff1a;照片太亮 曝光不足&#xff1a;照片太暗 曝光准确&#xff1a;关系刚刚好 曝光三要素&#xff1a;光圈、快门、感光度…

ChatGpt写高考作文——2023北京卷

题目一&#xff1a; “续航”一词&#xff0c;原指连续航行&#xff0c;今天在使用中被赋予了新的含义&#xff0c;如为青春续航、科技为经济发展续航等。 请以“续航”为题目&#xff0c;写一篇议论文。 要求&#xff1a;论点明确&#xff0c;论据充实&#xff0c;论证合理&a…

angular 双向数据绑定原理

Angular的双向数据绑定基于Observable和Zone.js实现。 当一个组件中的属性或者模板中的表达式发生变化时&#xff0c;Angular会创建一个变更检测器&#xff0c;并且在组件的变更检测树中遍历所有的子组件和指令&#xff0c;检测它们的属性是否也发生了变化。如果发生了变化&am…

使用CodeAnt查找并修复IDE中的开源漏洞与许可证合规问题

不断加快的开发步伐正在将软件安全的责任转移到开发人员的桌面上&#xff0c;但是处理在下游构建和测试中检测到的安全问题可能是非常具有破坏性的。直至报告漏洞的时候&#xff0c;开发人员已经转移到他们的下一个任务。为了修复问题&#xff0c;他们必须中断正在做的事情&…

如何用Web服务组件IIS免费搭建站点,并实现外网远程访问?

作为一名程序猿&#xff0c;经常会有搭建网站的需求&#xff0c;或被朋友要求帮忙着搭建网站&#xff0c;但是如果将网站建设在个人电脑或公司的服务器上&#xff0c;面临的问题是&#xff0c;没有公网IP或屏蔽了外网的80端口&#xff0c;在外网环境下就无法直接内网的网站&…

h700通话糊 索尼wi_索尼WI-H700耳机怎么接听电话

索尼WI-H700耳机是一款无线智能耳机&#xff0c;项圈设计可将耳机戴在脖子上&#xff0c;耳机除了能连接手机收听手机的音乐之外&#xff0c;还能通过无线接听手机的电话&#xff0c;那么耳机是怎么接听电话的呢&#xff1f;跟着小编了解一下。 接听电话 当来电呼入时&#xff…

Vue 有哪些经典面试题?

前言 下面总结了vue的一些经典的面试题&#xff0c;希望对正在找工作面试的小伙伴们提供一些帮助&#xff0c;我们废话少说直接进入整体、 简述一下什么是MVVM模型 MVVM&#xff0c;是Model-View-ViewModel的简写&#xff0c;其本质是MVC模型的升级版。其中 Model 代表数据模…

不敲一个代码,10分钟做出数据可视化大屏,还不快来学?

大屏幕实时数据可视化解决方案? 简道云去年举办过一场“最美仪表盘”评选活动&#xff0c;在活动中我们收到了很多精美炫酷的仪表盘&#xff0c;而且这所有的数据可视化仪表盘都是“从业务中来”&#xff0c;“到业务中去”的。 下面举几个例子展示下&#xff1a; 所用工具…