【Linux】C文件系统详解(三)——如何理解缓冲区以及自主封装一个文件接口

文章目录

  • 如何理解缓冲区
    • 现象
    • 概念:文件缓冲区
    • 为什么要有缓冲区
    • 缓冲区在哪里
  • 自己封装一个简单的文件接口
    • 自主封装
      • 目标
    • 代码
    • 关于缓冲区
      • 强制刷新内核
  • 关于字符串格式化函数
    • 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));//这里不用加上\0
	
	fork();
	return 0
}

这样可以打印出来

hello fprintf
hello write

因为此时都是向显示器打印,是采用行缓冲,所以直接就刷新出来的(见下图中的解释)
但是如果我们重定向:./myfile > log.txt
结果不一样了:

hello write
hello fprintf
hello fprintf

但是如果不加fork();就不会产生这样的结果.
因为此时是普通文件,采用的刷新策略是全缓冲
所以真正的调用顺序应该是:在fork之前,write就直接打印进文件了,但是fwrite只是写在缓冲区中.在fork之后,fwrite的缓冲区中的文件变成了两份(写时拷贝),由此,会出现打印两次的现象.(下图中有解释)

概念:文件缓冲区

![[文件系统 2023-11-16 16.12.27.excalidraw|800]]

为什么要有缓冲区

可以节省调用者的时间:系统调用也是要花费大量时间的
进程可以继续做自己的事情,最后统一刷新

缓冲区在哪里

在你进行fopen打开文件的时候,会得到一个FILE结构体,缓冲区就在该结构体中
而调用write时,是系统调用,没有缓冲区,会直接刷新出来

自己封装一个简单的文件接口

自主封装

目标

用最简单的方式,呈现出对FILE的理解
特点:实现的是一个demo版本,重在呈现原理

代码

makefile

myfile:main.c myfile.c
	gcc -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 0x4

typedef 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);
    else
        fd = 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);
    // 将用户缓冲区中的数据,通过系统调用接口,冲刷给OS
    write(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.指针置为NULL
    fp = NULL;
    return 0;
}

关于缓冲区

1.历史上我们所谈的缓冲区指的是:用户级缓冲区,语言提供
2.用户层+内核->强制刷新内核

![[文件系统 2023-11-17 10.15.39.excalidraw|900]]

强制刷新内核

fsync(fp->fd);

关于字符串格式化函数

printf和scanf函数

int my_printf(const char* format,...)
{
	//1.先获取对应的变量a
	//2.定义缓冲区,对a转成字符串
	//2.1 fwrite(stdout,str);
	//3.将字串拷贝的stdout->buffer即可
	//4.结合刷新策略显示即可
}

![[Pasted image 20230325111337.png]]
![[Pasted image 20230325111513.png]]
完结.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/163852.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

十三、Linux文件目录指令

pwd 指令 基本语法&#xff1a;pwd &#xff08;功能描述&#xff1a;显示当前工作目录的绝对路径&#xff09; 应用实例&#xff1a;案例&#xff1a;显示当前工作目录的绝对路径 ls 指令 基本语法&#xff1a;ls 【选项】【目录或是文件】 常用选项 -a &#xff1a;显示当…

卡片排列-第15届蓝桥第二次STEMA测评Scratch真题精选

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第159讲。 第15届蓝桥杯第2次STEMA测评已于2023年10月29日落下帷幕&#xff0c;编程题一共有6题&#xff0c;分别如下&…

最强人工智能ChatGPT引领AIGC发展

从公众号转载&#xff0c;关注微信公众号掌握更多技术动态 --------------------------------------------------------------- ——AI不会淘汰所有人&#xff0c;但会淘汰不懂AI的人 一、最强人工智能GPT-4 Turbo 在前不久的OpenAI开发者大会&#xff0c;正值Chatgpt3.5发布一…

UDS 14229-1定义的请求的响应行为

UDS服务响应规则 重要提示服务器一般响应行为包含子功能的请求响应行为物理寻址请求功能寻址请求 没有子功能参数的服务响应行为物理寻址客户端请求功能寻址客户端请求 伪代码示例 重要提示 服务应当支持物理寻址方式请求&#xff0c;部分服务也支持功能寻址方式请求。在功能寻…

Java集合大总结——List的简单使用

List简单介绍 鉴于Java中数组用来存储数据的局限性&#xff0c;我们通常使用java.util.List替代数组List集合类中元素有序、且可重复&#xff0c;集合中的每个元素都有其对应的顺序索引。JDK API中List接口的实现类常用的有&#xff1a;ArrayList、LinkedList和Vector。 List…

五、Linux目录结构

1.基本介绍 1.Linux的文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录"r/"&#xff0c;然后在此目录下再创建其他的目录。 2.深刻理解linux树状文件目录是非常重要的 3.记住一句经典的话&#xff1a;在Linux世界里&#xff0c;一切皆文件…

asp.net智能考试系统VS开发sqlserver数据库web结构c#编程计算机网页项目

一、源码特点 asp.net 智能考试系统 是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 系统运行视频 https://www.bilibili.com/video/BV1gz4y1A7Qp/ 二、功能介绍 本系统使用Microsoft Visual Studio 201…

4.6每日一题(多元函数的隐函数求导)

三元方程确定的二元函数类型的隐函数 方法一&#xff1a;两边对x求偏导&#xff0c;把y看成常数 注&#xff1a;z可以把x和y同时代入求出答案 方法二&#xff1a;带公式

Linux tc 使用

tc模拟延时丢包等网络故障依赖的内核驱动 /lib/modules/5.15.0-52-generic/kernel/net/sched/sch_netem.ko有些系统并不是默认就安装上该驱动的&#xff0c;如果没有安装该驱动&#xff0c;构造网络故障时会报错。 root:curtis# tc qdisc change dev enp4s0 root netem delay…

油猴脚本(JavaScript)-练手-简单的随机音乐播放器

浅浅的写个简单的随机音乐播放脚本(可移动)&#xff0c;注释很详细&#xff0c;直接上源码 效果&#xff1a; // UserScript // name 播放音乐脚本 // namespace 代码对我眨眼睛 // version 1.2 // description 在API上请求音乐链接并随机自动连续播放音乐&…

斯坦福机器学习 Lecture1

https://www.bilibili.com/video/BV1JE411w7Ub?p1&vd_source7a1a0bc74158c6993c7355c5490fc600 笔记如下 机器学习的定义&#xff1a;不需要明确编程就能让计算机去学习做某件事情 另一个定义 TODO:here

【网络】OSI模型 与 TCP/IP模型 对比

一、OSI模型 OSI模型包含7个层次&#xff0c;从下到上分别是&#xff1a; 1. 物理层&#xff08;Physical Layer&#xff09; - 功能&#xff1a;处理与电子设备物理接口相关的细节&#xff08;如电压、引脚布局、同步&#xff0c;等等&#xff09;。 - 协议&#xff1a;以…

clusterProfiler包学习

&#x1f4d6; Introduction | Biomedical Knowledge Mining using GOSemSim and clusterProfiler (yulab-smu.top) 部分使用 #GO classificationlibrary(clusterProfiler) data(geneList, package"DOSE") gene <- names(geneList)[abs(geneList) > 2]# Entre…

cesium雷达扫描(模糊圆效果)

cesium雷达扫描(模糊圆效果) 1、实现思路 使用ellipse方法加载圆型,修改ellipse中‘material’方法重写自己的glsl来实现当前效果 1、示例源码 index.html <!DOCTYPE html> <html lang="en"><head><!<

常见树种(贵州省):002杉类

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、杉木 …

9、传统计算机视觉 —— 边缘检测

本节介绍一种利用传统计算机视觉方法来实现图片边缘检测的方法。 什么是边缘检测? 边缘检测是通过一些算法来识别图像中物体之间,或者物体与背景之间的边界,也就是边缘。 边缘通常是图像中灰度变化显著的地方,标志着不同区域的分界线。 在一张图像中,边缘可以是物体的…

TURN 协议

TURN 地址分配 抓包过程 TURN 连接建立 这里指的是 Client 收到对端从 TURN 分配的 IP 和 端口 &#xff0c;和对端的 TURN 和 IP 绑定的过程 CreatePermission Request 等消息&#xff0c;都会携带有对端的 TURN 和 IP 抓包过程

新手必看!!超详细!STM32-基本定时器

一、基本定时器的作用 定时触发输出直接驱动DAC。 二、基本定时器的框图 以STM32F103系列为例&#xff0c;具体开发板请查看开发手册。 类别定时器总线位数计数方向预分频系数是否可以产生DMA捕获/比较通道互补输出基本定时器TIM6 / TIM7APB116位向上1~65536可以0无通用定时…

五、hdfs常见权限问题

1、常见问题 2、案例 &#xff08;1&#xff09;问题 &#xff08;2&#xff09;hdfs的超级管理员 &#xff08;3&#xff09;原因 没有使用Hadoop用户对hdfs文件系统进行操作。 在Hadoop文件系统中&#xff0c;Hadoop用户相当于Linux系统中的root用户&#xff0c;是最高级别用…