【Linux】14. 文件缓冲区

news2024/12/24 22:15:34

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.c
	gcc -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;
  // 文件描述符置为fd
  fp->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)

NAME
       fsync, fdatasync - synchronize a file's in-core state with storage device

SYNOPSIS
       #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.coloradmin.cn/o/622687.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于matlab仿真带有飞机的虚拟场景

一、前言 此示例演示如何通过 MATLAB接口使用空间鼠标。 开始此示例后&#xff0c;带有飞机的虚拟场景将显示在 Simulink 3D 动画查看器中。您可以使用空格鼠标在场景中导航平面。通过按下设备按钮 1&#xff0c;您可以在当前平面位置放置标记。 此示例需要空间鼠标或其他兼容设…

Neuralangelo AI - 视频生成3D模型

NVIDIA Research 宣布了 Neuralangelo&#xff0c;这是一种创新的 AI 模型&#xff0c;它利用神经网络的力量从 2D 视频剪辑中重建详细的 3D 结构。 Neuralangelo 能够生成逼真的建筑物、雕塑和其他现实世界物体的虚拟复制品&#xff0c;展示了 AI 在 3D 重建领域的非凡潜力。…

十个国内可用的智能AI模型

AI语言模型&#xff0c;就是一种利用机器学习和自然语言处理技术进行文本生成的算法。其基于大量已有的语料库进行训练&#xff0c;建立出一个能够理解自然语言规律和特征的语言模型。对于输入的文本、任务和目标&#xff0c;AI语言模型可以快速生成对应的结果。 在实际应用中&…

Flume入门监控端口数据官方案例

Flume安装部署 相关地址 Flume官网地址&#xff1a;http://flume.apache.org/文档查看地址&#xff1a;http://flume.apache.org/FlumeUserGuide.html下载地址&#xff1a;http://archive.apache.org/dist/flume/ 安装 将apache-flume-1.9.0-bin.tar.gz上传到linux的/opt/s…

责任链模式的学习与使用

1、责任链模式的学习 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许你将请求沿着处理链进行传递&#xff0c;直到有一个处理者能够处理该请求。责任链模式将请求的发送者和接收者解耦&#xff0c;使多个对象都有机…

计算机网络通信过程

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

案例|某城商行Zabbix 监控架构分享

编者荐语&#xff1a; 东莞银行董天保将出席7月份Zabbix大会分享案例经验&#xff0c;欢迎参加&#xff01; 以下文章来源于有田菜也香 &#xff0c;作者AcidGo 东莞银行董天保先生将于7月份参加Zabbix大会分享使用经验&#xff0c;欢迎扫码参加&#xff01; 【导读】某银行…

openCV(三)绘制几何图形

openCV内置了几何图形绘制函数&#xff0c;通过简单的操作就可以绘制几何图形。例如&#xff0c;可以绘制直线、矩形、圆形、椭圆、多边形、文字等&#xff0c;分别对应函数cv2.line()、cv2.rectangle()、cv2.circle()、cv2.ellipse()、cv2.polylines()、cv2.putText()。 下面来…

Mysql进阶【3】论述Mysql优化

1.通过explain查看sql的详细信息 Mysql的sql优化企业里边主要是对慢sql进行优化&#xff0c;对语句进行优化&#xff0c;对索引进行优化 通过explain查看sql的详细信息&#xff0c;并且分析sql语句存在的问题&#xff0c;比如有没有使用到索引、使用了索引还是慢是不是索引设…

第一章_从减库存聊起

在多线程高并发场景下&#xff0c;为了保证资源的线程安全问题&#xff0c; jdk 为我们提供了 synchronized 关键字和 ReentrantLock 可重入锁&#xff0c;但是它们只能保证一个 jvm 内的线程安全。在分布式集群、微服务、云原生横行的当下&#xff0c;如何保证不同进程、不同…

2023年京东618预售数据:传统滋补成预售黑马,预售额超27亿

这一期主要分享一下此次京东618预售期间的一个黑马行业——传统滋补。不管是从预售量和预售额来看&#xff0c;传统滋补品类的成绩都是此次大促中的佼佼者。 究其原因&#xff0c;近几年养生滋补也掀起了一股“国潮风”。在小红书、抖音等社交平台上&#xff0c;关于“健康养生…

车载摄像头专用——拓尔微低功耗超高集成PMIC TMI7205B

“2023将是汽车行业的大变革之年&#xff0c;全球迎来L2向L3/L4跨越窗口。”越高级别的自驾对周围环境感知要求越高&#xff0c;车载摄像头“高清化”势不可挡&#xff0c;目前已从传统的100万直接跃升至800万像素摄像头&#xff0c;甚至在供应层面&#xff0c;已有超1500万高像…

Vue.js 中的 $forceUpdate 方法是什么?有什么作用?

Vue.js 中的 $forceUpdate 方法是什么&#xff1f;有什么作用&#xff1f; 在 Vue.js 中&#xff0c;$forceUpdate 方法是一个很常见的方法之一。它可以强制组件重新渲染&#xff0c;从而让组件的视图更新。本文将介绍 $forceUpdate 方法的使用方法和作用&#xff0c;并给出一…

代码随想录算法训练营第二天| 977.有序数组的平方 209.长度最小的子数组 59.螺旋矩阵||

LeetCode977.有序数组的平方 链接&#xff1a;有序数组的平方 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 看到这道题&#xff0c;我第一反应就是把每个数的平方算出来然后排序&#xff…

结构体大小的计算

结构体计算要遵循字节对齐原则。 结构体默认的字节对齐一般满足三个准则&#xff1a; 结构体变量的首地址能够被其最宽基本类型成员的大小所整除&#xff1b;结构体每个成员相对于结构体首地址的偏移量&#xff08;offset&#xff09;都是成员大小的整数倍&#xff0c;如有需…

【并发篇】04 线程池核心参数

这道题其实就是在问java中线程池的实现类ThreadPoolExecutor&#xff0c;这个类参数最多的构造方法有7个参数。 线程池本质上就是管理一组线程&#xff0c;用来执行提交给线程池的任务。提交任务用的是submit(task)。 corePoolSize设置核心线程数。核心线程执行完任务后仍然需…

java培训机构学校教学教务选课管理平台springboot+vue

近年来&#xff0c;随着培训机构机构规模的逐渐增大&#xff0c;人工书写的方式已经不能满足如此庞大的数据。为了更好的适应信息时代的高效性&#xff0c;一个利用计算机来实现培训机构教务管理工作的系统将必然诞生。基于这一点&#xff0c;设计了一个培训机构教务管理系统&a…

视觉相机模型以及投影原理推导——(单目)

相机模型简介 参考文献&#xff1a;视觉SLAM十四讲、视觉惯性SLAM理论与源码分析、该博客、文中的公式直接引用上面的文章&#xff0c;如有侵权请联系本人删除 1、针孔相机模型 投影过程 三维世界中的物体&#xff08;目标点&#xff09;P反射光线&#xff0c;通过相机光心&am…

RL - 强化学习 蒙特卡洛 (Monte-Carlo) 方法计算状态价值

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/131102145 在强化学习中&#xff0c;状态价值 (State Value) 是指在特定状态下&#xff0c;智能体能够从该状态开始执行一系列动作&…

你还在用U盘和聊天工具来处理文档吗?ONLYOFFICE的协作空间来解决你的痛点了!

你还在用U盘和聊天工具来处理文档吗&#xff1f;ONLYOFFICE的协作空间来解决你的痛点了&#xff01; 说起Office办公软件&#xff0c;大家想到的首先就是Word、PPT、Excel&#xff0c;这是微软Office的三件套&#xff0c;从我们当代人念大学写论文时候开始学着用&#xff0c;到…