【Linux】基础IO——文件描述符:缓冲区的理解

news2024/12/23 14:41:42

上个月学校考试,进行课程复习,一直没有更新博客,现考试结束,继续保持更新,欢迎大家关注!

目录

  • 1 模仿C库自主封装简单的文件接口
  • 2 对缓冲区的理解
    • 2.1 数据刷新到磁盘的过程分析
    • 2.2 如何强制刷新内核

1 模仿C库自主封装简单的文件接口

目的:简单理解C语言是如何封装各个文件操作接口的。
1.接口实现:

#include "mystdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <malloc.h>
#include <assert.h>

MY_FILE *my_fopen(const char *path, const char *mode)
{
    //1.识别标志位
    int flag = 0;
    if(strcmp(mode, "r") == 0) flag |= O_RDONLY;
    else if(strcmp(mode, "w") == 0) flag |= (O_CREAT | O_WRONLY | O_TRUNC);
    else if(strcmp(mode, "a") == 0) flag |= (O_CREAT | O_WRONLY | O_APPEND);
    else{
        //其他模式
    }

    //2.打开文件
    mode_t m = 0666;
    int fd = 0;
    if(flag & O_CREAT) fd = open(path, flag, m);
    else fd = open(path, flag);
    if(fd < 0) return NULL;

    //3.构建MY_FILE结构体对象
    MY_FILE* mf = (MY_FILE*)malloc(sizeof(MY_FILE));
    if(mf == NULL)
    {
        close(fd);
        return NULL;
    }

    //4.初始化MY_FILE对象
    mf->fd = fd;
    mf->flags = 0;
    mf->flags |= BUFF_LINE;
    memset(mf->outputbuffer, '\0', sizeof(mf->outputbuffer));
    mf->current = 0;

    //5.返回打开的文件(MY_FILE)
    return mf;
}

int my_fflush(MY_FILE *fp)
{
    assert(fp);
    write(fp->fd, fp->outputbuffer, fp->current);
    fp->current = 0;
    return 0;
}

size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MY_FILE *stream)
{
    //1.缓冲区满了,就直接刷新
    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.开始计划刷新
    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);
    }
    else
    {}

    return writen;
}

//关闭文件的时候,C会帮助我们冲刷缓冲区
int my_fclose(MY_FILE *fp)
{
    assert(fp);
    //1.冲刷缓冲区
    if(fp->current > 0) my_fflush(fp);
    //2.关闭文件
    close(fp->fd);
    //3.释放堆空间
    free(fp);
    //4.指针NULL
    fp = NULL;

    return 0;
}

2.测试代码:

#include "mystdio.h"
#include <string.h>
#include <unistd.h>

#define MYFILE "log.txt"

int main()
{
    MY_FILE *fp = my_fopen(MYFILE, "w");
    if(fp == NULL) return 1;
    const char *str = "hello my_write";
    int cnt = 500;
    while(cnt)
    {
        char buffer[1024];
        //snprintf(buffer, sizeof(buffer), "%s: %d\n", str, cnt--);
        snprintf(buffer, sizeof(buffer), "%s: %d", str, cnt--);
        size_t size = my_fwrite(buffer, strlen(buffer), 1, fp);
        sleep(1);
        printf("当前成功写入:%lu个字节\n", size);

        if(cnt % 5 == 0) 
        {
            my_fwrite("\n", strlen("\n"), 1, fp);
        }

    }

    my_fclose(fp);
    return 0;
}

注:测试代码分为两种情况:

  1. 写入数据时不添加\n,写5次手动在缓冲区写入一个\n,来完成一次行刷新。
  2. 写入数据时添加\n,写一次刷新一次数据。

观察运行结果,思考:
写入时存在缓冲区,先将数据写入到缓冲区中,然后再刷新到显示器,那为什么不直接将数据刷新到显示器呢?为什么要有缓冲区?
——因为可以先将数据存在缓冲区中,不发生刷新,不进行写入,也就是不进行IO,不进行系统调用,所以函数会调用的很快,数据会暂存在缓冲区中。在缓冲区中可以积压多份数据,最后统一刷新。
其本质就是一次IO可以IO多份数据,从而提高IO的的效率。

2 对缓冲区的理解

以前我们所说的的缓冲区,是指用户级别的缓冲区,由语言提供。
下面要理解一个完整的缓冲区——用户层+内核,进而引入强制刷新内核

2.1 数据刷新到磁盘的过程分析

我们要把数据保存到磁盘文件中,至少要进行3次拷贝,下面进行解析:
当我们每次访问一个文件的时候,其实就是通过进程在访问文件,所以进程拥有对应的tesk_struct,拥有对应的文件描述符表struct file* fd_array[](在files_struct中),拥有被打开的文件struct file,并且被打开的文件拥有自己的文件缓冲区。
并且我们平时使用的C语言,存在C标准库libc.a/so,库当中为我们提供了对应的方法(比如fwrite/fput等),并且提供的这些所有的接口,都拥有FILE*参数,这个FILE结构体对象是在我们使用open打开文件的时候库在内部为我们创建好的,我们可以理解成在库里面或者在自己的进程中(我们理解为放在库中),FILE中存在一个缓冲区,所以我们在使用库提供的接口(fwrite/fput等)时,其实是先将数据存放在这个缓冲区中,所以我们使用的比如fwrite/fput这些接口其实本质是拷贝函数,它们将我们的数据拷贝到了FILE结构体的缓冲区中,然后再结合函数的刷新策略(通过打开文件的类型以及缓冲区是否满了判断),结合OS提供的比如write系统调用接口(write中有文件描述符fd、要写入的文件缓冲区的地址),定期的将数据冲刷到文件缓冲区(前面说的被打开的文件拥有自己的缓冲区)中,所以系统调用接口write的本质也是拷贝函数

那么C库提供的 “拷贝函数” 与系统调用接口 “拷贝函数” 有什么不同呢?

  • C库提供的拷贝函数是从用户——>语言
  • 系统调用拷贝函数是从语言——>内核

当数据到达文件缓冲区,OS就要有自己的刷新策略(所以刷新策略不止全缓冲、行缓冲、无缓冲)!OS可以将数据暂存在这个缓冲区中,以时间为单位刷新到磁盘;也可以等缓冲区满了再冲刷到磁盘;也可以判断当内存使用很紧张的时候,将数据刷新到磁盘中;也可以为了提高刷新速度,积累一定量的数据后再一次刷新。(我们平时写word,ctrl+s保存就是手动将保存在缓冲区中的数据刷新到磁盘,方式断电缓冲区中的数据丢失)
实际上OS的刷新策略要比上层用户的刷新策略复杂得多,因为OS要综合考虑的因素很多。但是总体的思想还是一样的——通过一次IO刷新更多的数据来提升刷新速度。

注:OS的刷新策略我们是不可见的。

总结: 要将数据刷新到硬件上至少要有三次拷贝:

  1. 第一次拷贝:先将数据刷新到语言库的缓冲区上
  2. 第二次拷贝:通过系统调用将数据拷贝到对应的内核当中
  3. 第三次拷贝:将数据从内存拷贝到外设

上面所讲的刷新过程图解:
在这里插入图片描述

2.2 如何强制刷新内核

例如我们在使用word的时候,ctrl+s强制刷新到外设,防止数据丢失。
所以系统也要提供接口供用户自主将数据刷新到外设。

系统提供的接口:

功能:通过文件描述符fd,将缓冲区数据刷新到外设。
接口:int fsync(int fd);

所以在实现fflush的时候,调用fsync来将数据刷新到外设:

int my_fflush(MY_FILE *fp)
{
    assert(fp);
    write(fp->fd, fp->outputbuffer, fp->current);
    fp->current = 0;

    fsync(fp->fd);
    return 0;
}

补充理解:
综上理解,当我们调用printf打印例如1245这样的数字时,其实我们打印的是字符1 2 3 4 5,即打印的是字符串,只是这些字符是连在一起的,在我们看来打印的是一串数字。但是在C程序中,12345是一个整数,但是显示的时候却打印成了字符串,所以肯定出现了一次数据格式转化,数据格式转化是printf做的(所以printf叫格式控制),它将内存的数据转换成了字符串数据。

那么printf是如何进行格式控制的呢?

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

再比如scanf是如何进行格式控制的呢?

  1. 首先获取数据读取到stdin->buffer中
  2. 对buffer的内容进行格式化,写入到对应的变量当中

写一段伪代码进行解释:
int a, b;
scanf(“%d %d”, &a, &b); //假设输入123 456
其本质就是:

  1. 将数据读取到缓冲区中:read(0, stdin->buffer, num);
  2. 本质输入的是123 456 字符串
  3. 扫描字符串,碰到空格,字符串就被分为2个子串
  4. 分别对两个字串进行格式转换后写入即可:*ap = atoi(str1); *bp = atoi(str2);

总结:
我们在学习比如C、C++等语言的时候,所说的缓冲区概念都是由该语言提供的。

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

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

相关文章

00后是真的卷不过,工作没两年,跳槽到我们公司起薪17K都快接近我了

在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不是。技术很强的同事&#xff0c;可遇不可求&#xff0c;向他学习还来不及呢。 真正让人反感的&#xff0c;是技术平平&…

go+vue自建运维管理平台

文章目录 鲁班运维平台容器管理集群管理namespace管理节点管理工作负载存储管理网络管理配置管理事件中心 kuboard 鲁班运维平台 这个平台和spug很像&#xff0c;感觉就像是spug运维平台的容器版本。 但是如果是容器平台则选择的余地很大&#xff0c;优秀的如kubersphere、kub…

NeRF+SLAM论文阅读笔记

CVPR 2023 Co-SLAM: Joint Coordinate and Sparse Parametric Encodings for Neural Real-Time SLAM input&#xff1a; RGB-D contribution&#xff1a; 1.场景表示&#xff1a;多分辨率哈希网格&#xff08;加速&保留高频特征&#xff09; 2.编码方式&#xff1a;one-b…

STM32 实现简单定时任务调度器,动态创建任务,两种思路实现流水灯

代码实现和硬件没关系&#xff0c;所以并不限于STM32&#xff0c;Arduino 之类的其他地方也能用&#xff0c;只要有一个能获取时间的函数就行&#xff0c;或者说&#xff0c;只要有一个会随着时间自动增加的变量就行&#xff0c;时间单位无所谓&#xff0c;所以确实想的话&…

排它锁和共享锁.md

介绍 排它锁&#xff08;Exclusive&#xff09;&#xff0c;又称为X 锁&#xff0c;写锁。 共享锁&#xff08;Shared&#xff09;&#xff0c;又称为S 锁&#xff0c;读锁。 X和S锁之间有以下的关系&#xff1a; SS可以兼容的&#xff0c;XS、SX、XX之间是互斥的 显式加锁…

Django-带参数的路由编写(一)【不用正则表达式匹配的简单带参数路由】

在某urls.py文件有如下的路由配置语句&#xff1a; urlpatterns [path(app2/show/<int:id>/,views.show_id), ]语句&#xff1a; path(app2/show/<int:id>/,views.show_id),中的<int:id>就是带参数的URL中的参数部分&#xff0c;其语法格式如下&#xff1a…

PPT中如何做出透视圆的效果?

看两个例子 一个是上部这种垂直的圆环。 一个是下部这种圆。 它们都据有一定的透视感,上部用于表示流量,下部用于表示出“某一领域”的意镜。 向下延展的圆环透视效果 先说这个扁平的圆的例子,它有4个圆,画的技巧如下: 就是4个圆环;把4个圆环互相叠加;把上和下在“中…

2023年网络安全保姆级入门学习路线,建议收藏!

作为一个工作多年的网络安全渗透工程师&#xff0c;我知道对于零基础小白来说&#xff0c;网络安全可能是一个非常陌生而且有些恐怖的领域。但是不用担心&#xff0c;只要你愿意花费时间和精力去学习&#xff0c;你也能成为一个优秀的网络安全专家。 网络安全学习路线 首先&a…

顺序队列和链队列

队列也是一种线性结构&#xff0c;不同于栈的是队列为先进先出的数据结构&#xff0c;遵循一边入队一边出队。 顺序队列的底层使用的是数组&#xff0c;因此需预先申请一块足够大的内存空间初始化顺序队列。除此之外&#xff0c;为了满足顺序队列中数据从队尾进&#xff0c;队头…

接口测试和功能测试的区别

目录 前言&#xff1a; 一、测试目的不同 二、测试内容不同 三、测试重点不同 四、总结 前言&#xff1a; 接口测试和功能测试是软件测试中的两种不同类型。接口测试侧重于测试不同模块之间的接口&#xff0c;而功能测试则注重测试完整的业务功能。 一、测试目的不同 接…

Vue中如何进行文件打印与PDF导出

Vue中如何进行文件打印与PDF导出 在Vue应用中&#xff0c;有时候需要将页面内容打印出来或者导出为PDF格式&#xff0c;以满足用户的需求。本文将介绍如何在Vue应用中实现文件打印和PDF导出的功能。 文件打印 文件打印是指将页面内容输出到打印机上&#xff0c;将其打印成纸质…

Vue中如何进行地图热点展示与交互(如热力图)

Vue中如何进行地图热点展示与交互&#xff08;如热力图&#xff09; 随着大数据和可视化技术的发展&#xff0c;地图热点展示越来越受到人们的关注。在Vue应用中&#xff0c;我们通常需要实现地图热点的展示和交互&#xff0c;以便更好地呈现数据和分析结果。本文将介绍在Vue中…

MySQL的高级操作(每一次「欢喜」都值得纪念)

文章目录 一、案例扩展二、克隆表1、方法一2、方法二 三、清空表四、创建临时表七、补充七、补充 一、案例扩展 use kgc; create table if not exists info ( id int(4) zerofill primary key auto_increment, #指定主键的第二种方式 name varchar(10) not null default 匿…

eclipse中java代码在控制台输出的中文内容是乱码怎么解决

eclipse中创建了一个maven工程&#xff0c;用System.out在控制台输出内容&#xff0c;但中文内容显示乱码&#xff1a; 解决方法&#xff1a; 右键单击工程&#xff0c;选择Run As->Run Configurations: 点击Common这个tab页&#xff0c;Encoding选择Use system encoding&…

分布式定时任务框架 PowerJob

业务背景 1.1 为什么需要使用定时任务调度 &#xff08;1&#xff09;时间驱动处理场景&#xff1a;整点发送优惠券&#xff0c;每天更新收益&#xff0c;每天刷新标签数据和人群数据。 &#xff08;2&#xff09;批量处理数据&#xff1a;按月批量统计报表数据&#xff0c;批…

vue练习

附加练习-1.帅哥美女走一走 目标: 点击按钮, 改变3个li的顺序, 在头上的就到末尾. 提示: 操作数组里的顺序, v-for就会重新渲染li 代码演示 <template><div><ul><li v-for"item in myArr" :key"item">{{ item }}</li></…

GCC命令与参数详解

GCC 命令与参数详解 无论是 C 还是 C 程序&#xff0c;将其从源代码转变为可执行代码的过程&#xff0c;具体可分为预处理 Preprocessing&#xff0c;编译 Compilation&#xff0c;汇编 Assembly&#xff0c;链接 Linking 这四个阶段。 默认情况下 GCC 指令会直接将源代码历经…

websocket实时通信【纯后端——JAVA】

本文主要介绍一下websoket的实时通信&#xff0c;这里只有java的代码&#xff0c;通过在线网站 http://www.websocket-test.com/测试即可 1. 导包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocke…

亚马逊云科技re:Inforce 2023全球大会,让安全成为业务发展驱动力

S&P《企业之声&#xff1a;2022年信息安全、预算和展望》研究报告指出&#xff0c;有37%的受访者表示云基础设施安全是他们的主要安全支出&#xff0c;65%的受访者表示将计划采用云供应商的高级安全服务。安全是亚马逊云科技的重中之重&#xff0c;re:Inforce全球大会则深化…

开源字节 同城信息小程序

​从小程序发布至今&#xff0c;它已经成为各行各业的推广工具&#xff0c;借助微信服务平台平台流量优点&#xff0c;能帮助店家快速吸粉。 同城小程序项目成本低&#xff0c;使用灵巧便捷&#xff0c;实际操作工作效率高&#xff0c;有利于店家品牌营销。开源字节同城信息小程…