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  0666

my_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函数上层的接口使用起来都不一样,  但是它们的底层都是一样的, 操作系统决定了它们的底层必须一样, 不管上层的接口如何封装如何设计, 底层都是一样的.


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

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

相关文章

4 个多月的蓝猫吃什么猫粮发腮快?

亲爱的猫友们&#xff0c;你们是不是也在为蓝猫的发腮问题而苦恼呢&#xff1f;&#x1f431; 四个多月的蓝猫正处于生长发育的关键时期&#xff0c;选择合适的猫粮对于它们的健康与美丽至关重要。 &#x1f50d; 在选择猫粮时&#xff0c;我们要关注几个关键点&#xff1a;高…

Elasticsearch从入门到精通-06ES统计分析语法

Elasticsearch从入门到精通-06ES统计分析语法 bucket和metric概念简介 bucket就是一个聚合搜索时的数据分组。如&#xff1a;销售部门有员工张三和李四&#xff0c;开发部门有员工王五和赵六。那么根据部门分组聚合得到结果就是两个bucket。销售部门bucket中有张三和李四&…

window下安装并使用nvm(含卸载node、卸载nvm、全局安装npm)

window下安装并使用nvm&#xff08;含卸载node、卸载nvm、全局安装npm&#xff09; 一、卸载node二、安装nvm三、配置路径和下载源四、使用nvm安装node五、nvm常用命令六、卸载nvm七、全局安装npm、cnpm八、遇到的问题 nvm 全名 node.js version management&#xff0c;顾名思义…

远程桌面安卓版下载 安卓远程控制免费版

远程桌面安卓版下载与安卓远程控制免费版的应用解析 随着移动互联网的快速发展&#xff0c;远程桌面应用逐渐成为了许多用户、特别是技术爱好者和商务人士的必备工具。它们不仅可以在电脑上实现远程控制&#xff0c;还能将这种功能延伸到移动设备上&#xff0c;如安卓手机和平…

Acwing.167 木棒(回溯)

题目 乔治拿来一组等长的木棒&#xff0c;将它们随机地砍断&#xff0c;使得每一节木棍的长度都不超过 50 个长度单位。 然后他又想把这些木棍恢复到为裁截前的状态&#xff0c;但忘记了初始时有多少木棒以及木棒的初始长度。 请你设计一个程序&#xff0c;帮助乔治计算木棒…

年度告警分类统计

1、打开前端Vue项目kongguan_web&#xff0c;完成前端src/components/echart/YearWarningChart.vue页面设计 在YearWarningChart.vue页面添加div设计 <template><div class"home"><div style"margin: 0px auto;height: 100%"><div …

金蝶云星空——单据附件上传

文章目录 概要技术要点代码实现小结 概要 单据附件上传 技术要点 单据附件上传金蝶是有提供标准的上传接口&#xff1a; http://[IP]/K3Cloud/Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.AttachmentUpLoad.common.kdsvc 参数说明 参数类型必填说明FileName字符是…

基于springboot+vue的乡村民宿管理系统

一、系统架构 前端&#xff1a;vue | element-ui 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk1.8 | mysql | maven | nodejs 二、代码及数据库 三、功能介绍 01. 登录页 02. 注册 03. 管理员-首页 04. 管理员-信息管理-公告信息 05. 管理员…

淘宝|天猫|京东|1688主流电商平台的实时数据返回接口|附Python实例

导读&#xff1a;随着淘宝/天猫直通车功能升级&#xff0c;很多功能越来越白盒化&#xff0c;越来越简化&#xff0c;更方便用户的操作&#xff0c;只需一键即可看出淘宝/天猫直通车存在的问题。淘宝/天猫直通车千人千面后有了实时数据工具&#xff0c;下面通过一个案例告诉大家…

【Android】【Bluetooth Stack】蓝牙电话本协议之同步通话记录分析(超详细)

1. 精讲蓝牙协议栈&#xff08;Bluetooth Stack&#xff09;&#xff1a;SPP/A2DP/AVRCP/HFP/PBAP/IAP2/HID/MAP/OPP/PAN/GATTC/GATTS/HOGP等协议理论 2. 欢迎大家关注和订阅&#xff0c;【蓝牙协议栈】专栏会持续更新中.....敬请期待&#xff01; 目录 1. 协议简述 1.1 PBAP…

Day02-DDLDMLDQL(定义,操作,查询)(联合查询,子查询,字符集和校对集,MySQL5.7乱码问题)

文章目录 Day02-DDL&DML和DQL学习目标1. SQL语言的组成2. DDL2.1 数据库结构2.2 表结构2.3 约束2.3.1 主键约束(重要)(1)特点(2) 添加主键(3)删除主键(了解) 2.3.2 自增约束(1)特点(2) 添加自增约束(3)删除自增约束(了解) 2.3.3 非空约束(1)添加非空约束(2) 删除非空约束 2…

day01_mysql数据类型和运算符_课后练习 - 参考答案

文章目录 day01_mysql_课后练习第1题第2题第3题第4题第5题 day01_mysql_课后练习 第1题 案例&#xff1a; 1、创建数据库day01_test01_library 2、创建表格books 字段名字段说明数据类型允许为空唯一b_id书编号int(11)否是b_name书名varchar&#xff08;50&#xff09;否否…

OLAP数据库选型指南:Doris与ClickHouse的深入对比与分析

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 在当今数据驱动的时代&#xff0c;数据的存储、处理和分析变得尤为重要。为了满足这一需求&#xff0c;市场上涌现出了许多优秀的…

FPGA Vivado环境下实现D触发器

题目要求&#xff1a;使用Verilog HDL语言设计一个D触发器。请提交程序源代码和Word格式的作业文档&#xff0c;作业文档中应给出程序源代码及RTL分析原理图。 D触发器的工作原理&#xff1a; 初始状态下&#xff0c;触发器处于复位状态&#xff0c;输出为复位信号的稳定状态…

Linux笔试题

1. 程序代码如下&#xff0c;请按执行顺序写出输出结果: int main() { pid_t pid1,pid2;if((pid1fork()) 0) {sleep(3);printf(“info1 from child process_1\n”);exit(0);printf(“info2 from child process_1\n”); } else {if((pid2fork()) 0){sleep(1);printf(“i…

排序算法:快速排序(非递归)

文章目录 一、先建立一个栈二、代码编写 !](https://img-blog.csdnimg.cn/direct/870dd101173d4522862e4459b32237a3.png) 先赞后看&#xff0c;养成习惯&#xff01;&#xff01;&#xff01;^ _ ^<3 ❤️ ❤️ ❤️ 码字不易&#xff0c;大家的支持就是我坚持下去的动力…

力扣刷题-砖墙题554

砖墙题 这题一开始没有想到思路&#xff0c;一开始还想着用枚举法做/笑哭 后来看了题解&#xff0c;原来就是哈希表的题目呀。 说到哈希表&#xff0c;这里有个八股需要记一下&#xff1a; HashMap和HashTable的区别 线程是否安全&#xff1a;HashMap线程不安全 HashTable线…

[综述笔记]Flexible large-scale fMRI analysis: A survey

论文网址&#xff1a;Flexible large-scale fMRI analysis: A survey | IEEE Conference Publication | IEEE Xplore 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff0…

力扣热门算法题 56. 合并区间,57. 插入区间,58. 最后一个单词的长度v

56. 合并区间&#xff0c;57. 插入区间&#xff0c;58. 最后一个单词的长度&#xff0c;每题做详细思路梳理&#xff0c;配套Python&Java双语代码&#xff0c; 2024.03.20 可通过leetcode所有测试用例。 目录 56. 合并区间 解题思路 完整代码 Python Java ​编辑 5…

【自然语言处理】NLP入门(八):1、正则表达式与Python中的实现(8):正则表达式元字符:.、[]、^、$、*、+、?、{m,n}

文章目录 一、前言二、正则表达式与Python中的实现1、字符串构造2、字符串截取3、字符串格式化输出4、字符转义符5、字符串常用函数6、字符串常用方法7、正则表达式1. .&#xff1a;表示除换行符以外的任意字符2. []&#xff1a;指定字符集3. ^ &#xff1a;匹配行首&#xff0…