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

news/2024/11/6 21:44:44/

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

目录

  • 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.指针NULLfp = 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.ppmy.cn/news/404156.html

相关文章

【RH850/U2A】:Task激活过程

Task激活过程 Autostart非AutostartTASK(Default_Init_Task)EcuM_StartupTwo(void)SchM_Init(void)BswM_Init(xx)Rte_Start(void)Task激活过程,分自动运行(AutoStart)和非自动运行。 Autostart 在DavinciCfg中的配置如下: Autostart意味作在执行StartOS()后就开始运行了。…

任务4

1朴素贝叶斯 朴素贝叶斯的原理 利用朴素贝叶斯模型进行文本分类 朴素贝叶斯1 2SVM模型 SVM的原理 利用SVM模型进行文本分类 3LDA主题模型 pLSA、共轭先验分布 LDA 使用LDA生成主题特征&#xff0c;在之前特征的基础上加入主题特征进行文本分类 LDA数学八卦 lda2 合并特征 1&a…

04任务列表

任务列表 引导学生重新审视笔记的价值 讲解笔记的正确定位 讲解优秀笔记的标准 笔记工具的特征及选择 - [ ] 青菜 注意&#xff1a; -、[ 、] 后&#xff0c;都一定要有空格&#xff01;无快捷键&#xff0c;通过鼠标操作。 菜单栏&#xff1a;选中文字—>段落—>任务…

现有任务2

现有任务 -javaagent:E:\soft\eclipse-jee-2019-03-R-win32-x86_64\eclipse\lombok-1.18.10.jar 组件市场&#xff1a; 产品化 工作流 项目团队春节值班表

任务-01

本次任务主要考察了学生对RelativeLayout布局 LinearLayout布局的掌握程度,和对button控件和CheckBox控件的运用,以及自主学习页面跳转功能. 其中,我遇到的问题是: 1.用CheckBox实现对输入框密码的显示控制 2.用户名和密码的图标与输入框的布局排列 3.页面的跳转 解决方案: 1…

任务01-03

<!DOCTYPE html> <html> <head><title>轰隆隆</title><link rel"stylesheet" type"text/css" href"Ex1.css"><script type"text/javascript" src"Ex1.js"></script> </…

任务二到任务四

项目二 任务二 1.查看&#xff0c;创建&#xff0c;删除&#xff0c;更改&#xff0c;合并文件或目录。进入目录翻阅文件&#xff0c;打印目录。 项目二 任务三 一。配置网络 任务四 一。修改root密码 二。md5.

第四周任务1

* (程序头部注释开始) * 程序的版权和版本声明部分 * Copyright (c) 2011, 烟台大学计算机学院学生 * All rights reserved. * 文件名称&#xff1a; 三角形类 * 作 者&#xff1a; 于晓峰 * 完成日期&#xff1a; 2012年 3月 12日 * 版 本 号&#xff1a; v1.2 #include <…