Linux之缓冲区与C库IO函数简单模拟

 缓冲区

首先, 我们对缓冲区最基本的理解, 是一块内存, 用户提供的缓冲区就是用户缓冲区, C标准库提供的就是C标准库提供的缓冲区, 操作系统提供的就是操作系统缓冲区, 它们都是一块内存.

为什么要有缓冲区?

先举个生活中的例子, 我们寄快递的时候往往是去驿站寄快递, 而不是直接自己去送, 自己送效率太低, 不如直接把快递交给驿站. 驿站方便了用户, 虽然最终快递还是要被快递员送过去, 但是它给使用者提供了方便, 谁要寄快递就方便了谁.

驿站就相当于缓冲区:

1. 缓冲区的主要作用是提高效率, 提高使用者的效率, 谁使用缓冲区就提高谁的效率. 冯诺依曼体系中, 数据不用从一个设备拷贝到另一个设备, 而是把数据直接写入内存中, 让操作系统定期去刷新即可. 

2. 因为有缓冲区的存在, 我们可以把数据积累一部分统一发送, 提高了发送的效率.

缓冲区刷新方式 

 缓冲区因为能够暂存数据, 所以必定要有对应的刷新方式:

一般策略:

1. 无缓冲(立即刷新)

2. 行缓冲(行刷新)

3. 全缓冲(缓冲区满刷新)

特殊情况 :

1. 强制刷新

2. 进程退出的时候, 一般要进行刷新缓冲区

一般情况, 对于显示器文件, 行缓冲; 磁盘上的文件, 全缓冲.

        现在看一个样例, 调用三个C语言接口往显示器打印, 再调用一个系统调用接口往显示器打印, 最后创建了一个子进程:

  1 #include <stdio.h>2 #include <string.h>3 #include <unistd.h>4 int main()5 {6     fprintf(stdout, "C: hello fprintf\n");7     printf("C: hello printf\n");8     fputs("C: hello fputs\n",stdout);9     const char* str = "system call: hello write\n";10     write(1,str,strlen(str));11 12     fork();                                                                                       13     return 0;                                               14 }

运行后发现结果和我们预想的一样, 打印了四条语句. 

 

当我们向显示器打印的时候, 显示器的刷新方式是行刷新, 而代码中输出的语句都带有\n, fork之前, 数据全部被刷新, 包括系统调用. 

现在把内容重定向到 log.txt 磁盘文件中 , 发现除了系统调用以外, 每一个C语句都被打印了两次:

重定向到 log.txt, 本质是向磁盘文件中写入, 系统对于数据的刷新方式变成了全刷新, 全刷新意味着缓冲区变大, 实际写入的几条简单数据不足以把缓冲区填满, 也就是说fork执行的时候, 数据依然在缓冲区中.

 由于C语言printf等接口底层是封装了系统调用的, 而系统调用却没有打印两次, 说明目前我们谈的缓冲区和操作系统无关, 是C语言提供的缓冲区. C/C++提供的缓冲区, 里面保存的是用户的数据, 仍然属于当前进程在运行时自己的数据, 而进程退出的时候, 一般要刷新缓冲区, 即使数据没有满足刷新条件, 此例中不管是fork创建的子进程还是父进程, 总有一个进程要退出, 也就是会发生一次缓冲区刷新, 而刷新缓冲区是数据清空的操作, 就会发生写时拷贝! 另一个进程退出时数据还会被刷新一次, 数据就被刷新了两次.

再来解释一下为什么write的内容只显示了一次, 因为write是系统调用, 没有使用C缓冲区, 而是直接把数据写入到了操作系统, 如果我们把数据交给了操作系统, 数据就属于操作系统, 不属于当前进程了, 也就不会被写时拷贝.

什么叫做刷新?

我们printf将数据输入到C语言缓冲区中, 缓冲区满足刷新方式时就会调用write进行刷新, 刷新其实就是将数据从C缓冲区写入OS! 

 printf只在用户层与C缓冲区交互, 是用户级别的交互, 而不是直接将数据写入操作系统管理的文件缓存区中, 等C语言缓冲区存放了一定的数据后再统一刷新到文件缓冲区, 文件缓冲区再刷新到磁盘中, 提高了printf的效率, 从而间接提高了程序其它代码执行的效率. 另外, 其实printf在拷贝字符串数据的同时也把格式化%d等操作拷贝到目标字符串中, 顺便也把格式化输出的工作也做了.

 操作系统管理的文件缓冲区也有自己的刷新方式, write系统调用只管把C缓冲区的数据写到文件缓冲区即可, 不关心它什么时候刷新到磁盘, 这样能提高系统调用的效率.


C语言缓冲区在哪里?

C语言缓冲区存在FILE结构体里, FILE结构体里不仅封装了fd, 而且包含了该文件fd对应的语言层的缓冲区结构_IO_FILE, 在/usr/include/libio.h中:


 模拟实现C标准库的函数

这里目的是代码说明, 不是复刻

 mystdio.h

#pragma once     #define SIZE 4096    
#define FLUSH_NONE 1                                                                           
#define FLUSH_LINE (1<<1)    
#define FLUSH_ALL (1<<2)    typedef struct _FILE    
{    int fileno;//文件标识符    int flag;//刷新策略    char buffer[SIZE];//缓冲区    int end;//缓冲区大小    
} my_FILE;    extern my_FILE* my_fopen(const char* path, const char* mode);    
extern int my_fclose(my_FILE* stream);    
extern int my_fflush(my_FILE* stream);    
extern unsigned my_fwrite(const void* ptr, unsigned num, my_FILE* stream);//fwrite中间两个参数简化为1个        

mystdio.c 

#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>#define DEFAULT_MODE  0666my_FILE* my_fopen(const char*path, const char* mode)
{int fd = 0;//文件描述符int flag = 0;//确定打开方式if(strcmp(mode,"r") == 0){   flag |= O_RDONLY;}else if(strcmp(mode,"w") == 0){flag |= O_CREAT | O_TRUNC | O_WRONLY;}else if(strcmp(mode,"a") == 0){flag |= O_CREAT | O_APPEND | O_WRONLY;}if(flag & O_CREAT)fd = open(path, flag,DEFAULT_MODE);else fd = open(path, flag); if(fd < 0)                                                                                 {errno = 2;return NULL;}//创建一个my_FILE结构体my_FILE* stream = (my_FILE*)malloc(sizeof(my_FILE));if(!stream){errno = 3;return NULL;}stream->fileno = fd;                                                                       stream->end = 0;stream->flag = FLUSH_LINE;return stream;
}int my_fflush(my_FILE* stream)
{if(stream->end > 0){write(stream->fileno, stream->buffer, stream->end);stream->end = 0;}return 0;
}int my_fclose(my_FILE* stream)
{   my_fflush(stream);//文件关闭前刷新缓冲区int ret = close(stream->fileno); if(ret)errno = 2;return ret;
}unsigned my_fwrite(const void*ptr, unsigned num, my_FILE* stream)    
{    memcpy(stream->buffer+stream->end,ptr,num);    //判断是否需要行刷新    if(stream->end > 0 && stream->flag & FLUSH_LINE)    {    unsigned i = 0;    for(i = 0; i < num; i++)    {    if(*(stream->buffer+stream->end+i) == '\n')    {    stream->end += num;    my_fflush(stream);    return 0;    }                                                                                  }    }    stream->end += num;    return stream->end;    
}    

main.c , 用于测试:

#include "mystdio.h"    
#include <string.h>    
#include <stdio.h>    
#include <unistd.h>    int main()    
{    my_FILE* fp = my_fopen("log.txt","w");    if(fp == NULL)    {    perror("fopen");    return 1;    }    const char* msg = "hello, mystdio\n";                                                      int cnt = 20;    while(cnt--)    {    my_fwrite(msg,strlen(msg),fp);    sleep(1);    }    my_fclose(fp);    return 0;    
}    

编译: 

 运行结果:


总结:

不管是什么语言Java, python, C, C++等, 它们的IO函数上层的接口使用起来都不一样,  但是它们的底层都是一样的, 操作系统决定了它们的底层必须一样, 不管上层的接口如何封装如何设计, 底层都是一样的.


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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

教大家几种解决msvcr120.dll文件丢失的问题以及解决办法

当这个msvcr120.dll文件丢失或损坏时&#xff0c;依赖它的程序可能无法启动或正常运行&#xff0c;这可能影响电脑的使用效率。如果用户遇到因缺少msvcr120.dll而导致的程序错误&#xff0c;安装或修复这个msvcr120.dll文件是一种维护措施&#xff0c;可以帮助恢复软件的正常功…

中等职业学校大数据课程建设方案

大数据产业是以数据及数据所蕴含的信息价值为核心生产要素&#xff0c;通过数据技术、数据产品、数据服务等形式&#xff0c;使数据与信息价值在各行业经济活动中得到充分释放的赋能型产业。 大数据产业定义一般分为核心业态、关联业态、衍生业态三大业态。 一、专…

Spring Security之认证过滤器

前言 上回我们探讨了关于Spring Security&#xff0c;着实复杂。这次咱们聊的认证过滤器就先聊聊认证功能。涉及到多方协同的功能&#xff0c;咱分开聊。也给小伙伴喘口气&#xff0c;嘻嘻。此外也是因为只有登录认证了&#xff0c;才有后续的更多功能集成的可能。 认证过滤器…

巧用 20个 Linux 命令贴士与技巧,让你生产力瞬间翻倍?

在本文中&#xff0c;我将向您演示一些专业的Linux命令技巧&#xff0c;这些技巧将使您节省大量时间&#xff0c;在某些情况下还可以避免很多麻烦&#xff0c;而且它也将帮助您提高工作效率。 并不是说这些只是针对初学者的 Linux 技巧。即使有经验的Linux用户也有可能没有发现…

简单了解单例模式

什么是单例模式 对于一个类&#xff0c;只有一个实例化的对象&#xff0c;我们构建单例模式一般有两种&#xff1a;饿汉式和懒汉式 饿汉式 优点是无线程安全问题&#xff0c;类加载就创建对象缺点是占内存 class Singleton01{private static Singleton01 instance new Sing…

shell实现查询进程号并批量kill(脚本)

问题或需求描述 在shell中&#xff0c;如果你想通过命令行查询出一系列匹配某个关键词的进程&#xff0c;并使用xargs命令批量结束这些进程&#xff0c;可以按照以下步骤操作&#xff1a; # 查询并提取进程号 pgrep -f "关键词" | xargs kill# 或者&#xff0c;如果…

图书馆RFID(射频识别)数据模型压缩/解压缩算法实现小工具

点击下载《C# 实现图书馆射频识别数据模型压缩算法&#xff08;源代码pdf参考资料&#xff09;》 1. 前言 最近闲来无聊&#xff0c;看了一下《图书馆射频识别数据模型第1部分&#xff1a;数据元素的设置及应用规则》以及《图书馆射频识别数据模型第2部分&#xff1a;基于ISO…

如何联合Qt,VS,C++,来开发一个电脑版软件(简单有趣,详细)

本教程适合 新手VS+QT小白。目前更新到了可以写一个计算器【拉到文章末尾,可以看到界面】。 前置安装 VS2019 或2022 社区版(这个太简单,就不在这里写了!)建议参考之前写的文章: https://zhuanlan.zhihu.com/p/682531067 注册登陆Qt账户 Try Qt 下载Qt 登陆之后,…

iOS开发 - 转源码 - __weak问题解决

iOS开发 - 转源码 - __weak问题解决 在使用clang转换OC为C代码时&#xff0c;可能会遇到以下问题 cannot create __weak reference in file using manual reference 原因 __weak弱引用是需要runtime支持的&#xff0c;如果我们还只是使用静态编译&#xff0c;是无法正常转换的…

Jenkins的快速入门

文章目录 一、Jenkins是什么&#xff1f;二、Jenkins安装和持续集成环境配置1.持续集成流程说明2.Gitlab代码托管服务器安装Gitlab简介&#xff1a;Gitlab安装Gitlab的使用切换中文添加组创建用户将用户添加到组创建项目idea中代码上传Gitlab 3.Jenkins持续集成环境服务器安装J…

在MongoDB建模1对N关系的基本方法

“我在 SQL 和规范化数据库方面拥有丰富的经验&#xff0c;但我只是 MongoDB 的初学者。如何建立一对 N 关系模型&#xff1f;” 这是我从参加 MongoDB 分享日活动的用户那里得到的最常见问题之一。 我对这个问题没有简短的答案&#xff0c;因为方法不只有一种&#xff0c;还有…

【Python实战】——神经网络识别手写数字

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

【C++从练气到飞升】05---运算符重载

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书。 目录 ⛳️推荐 一、运算符重载的引用 二、运算符重载 三、赋值运算符重载 1 .赋值运算符重载格式: 2 .赋值运算符只能重载成…

SAP 标准委外业务对已收货后对组件的后续调整简介

标准委外业务对已收货后对组件的后续调整 通常在委外的业务中经常会存在发给供应商的物料发现有少发&#xff0c;或者需要补发的情况&#xff0c;委外的业务都是基于采购订单收货的时候才对供应商库存进行扣减。委外成品在收货后产生543的移动类型原材料进行冲消。 但是我们在物…

【C语言】编译和链接----预处理详解【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C语言】文件操作揭秘&#xff1a;C语言中文件的顺序读写、随机读写、判断文件结束和文件缓冲区详细解析【图文详解】&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 前言 欢迎来到本篇博客&…

继承和多态(2)(多态部分)

提前讲的重要知识点 一个类在没有父类的情况下默认有一个父类为Object类。 而当在有父类情况下&#xff0c;如果你那父类没有父类&#xff0c;则其父类的父类默认为object类&#xff0c;所以即使一个类有父类&#xff0c;其内部还是有object类。 object类都是隐藏起来的&…

【机器学习】基于北方苍鹰算法优化的BP神经网络分类预测(NGO-BP)

目录 1.原理与思路2.设计与实现3.结果预测4.代码获取 1.原理与思路 【智能算法应用】智能算法优化BP神经网络思路【智能算法】北方苍鹰优化算法&#xff08;NGO)原理及实现 2.设计与实现 数据集&#xff1a; 数据集样本总数2000 多输入单输出&#xff1a;样本特征24&#x…

Vue3:网页项目中路由的设计和配置

为了避免我每次建项目配路由的时候都回去翻网课&#xff0c;打算整一博客 路由设计 不同网页的路由设计思路基本相同&#xff0c;分为一级路由和二级路由&#xff0c;基本设计思路如下图 以我之前做过的招新系统管理端为例&#xff0c;可设计出如下路由 路由配置 还是以招新系…

剖析美国政府视角下的ICT供应链安全

2018 年 11 月 15 日&#xff0c;美国国土安全部&#xff08;DHS&#xff09;宣布成立了信息和通信技术 (ICT) 供应链风险管理&#xff08;SCRM&#xff09;工作组&#xff0c;这个工作组是由美国多个政府部门、IT行业企业代表及通信行业企业代表联合成立的。该组织对外宣传的目…

Docker Command

小试牛刀 # 查看docker版本 docker -v docker --version # 查看帮助 docker --help # 永远的Hello World docker run hello-world镜像操作 查看本地已有的镜像 docker images -a :列出本地所有的镜像&#xff08;含中间映像层&#xff09; -q :只显示镜像ID --digests :显示…