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

news2024/11/24 12:45:25

 缓冲区

首先, 我们对缓冲区最基本的理解, 是一块内存, 用户提供的缓冲区就是用户缓冲区, 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;
    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);
    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.coloradmin.cn/o/1531693.html

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

相关文章

Spring MVC文件下载配置

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 文件下载 在Spring MVC中通常利用commons-io实现文件下载&#xff0c;示例代码如下&#xff1a; Controller RequestMapping("......") public class DownloadC…

【晶振选型】输出波形的比较 CMOS Clipped sine wave TTL

疑问&#xff1a;哪种波形更好呢&#xff1f;答案是如果数字域方波最好&#xff0c;模拟域必须是正弦波了&#xff0c;所以还是具体问题具体分析。或者设计好冗余电路。 现在简单总结一下 晶振做的比较好的厂家 crystek 有一片被国内晶振厂家拿去分享的PDF; [CLIPPED SINEWA…

解决在命令行中输入py有效,输入python无效,输入python会跳转到microsoft store的问题| Bug

目录 如果你已经尝试过将python添加到系统变量在系统变量里把你自己的路径放到应用商店的路径之前删除windowsapps下的python.exe文件 如果你还未将python添加到系统变量没有python安装包且没有配置系统变量 如果你已经尝试过将python添加到系统变量 打开 运行&#xff0c;输入…

Linux CentOS 7.6安装Redis 6.2.6 详细保姆级教程

1、安装依赖 //检查是否有依赖 gcc -v //没有则安装 yum install -y gcc2、下载redis安装包 //进入home目录 cd /home //通过wget下载redis安装包 wget https://download.redis.io/releases/redis-6.2.6.tar.gz //解压安装包 tar -zxvf redis-6.2.6.tar.gz3、编译 //进入解压…

Prometheus修改默认数据存储时间

Prometheus的默认数据存储时间可以通过修改启动脚本中的相关参数来调整。具体来说&#xff0c;可以通过修改--storage.tsdb.retention.time参数来改变数据保留的时长。该参数决定了何时删除旧数据&#xff0c;默认为15天。如果需要延长数据保留时间&#xff0c;可以将该参数的值…

Day15:二叉树层序遍历 LeedCode 102.二叉树的层序遍历 199二叉树的右视图 637.二叉树的层平均值 101.对称二叉树 226.翻转二叉树

102.二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]]思路…

汇丰:大宗商品的“供给侧大故事”

今年3月以来&#xff0c;黄金、铜、原油、可可等各类大宗商品价格开启“狂飙”模式。 国际黄金从每盎司2050美元涨至接近2200美元&#xff1b;作为全球经济晴雨表&#xff0c;伦铜价格已经突破9000美元/吨&#xff0c;创近1年新高&#xff1b;原油价格也连续上涨&#xff0c;I…

采用U盘安装Win10系统教程

安装流程&#xff1a; 下载 Windows 10https://www.microsoft.com/zh-cn/software-download/windows10 手把手教你如何重装win10系统&#xff0c;自己动手安装系统其实很简单 - 知乎笔者在这里写一个详细点的系统重装教程。手把手教大家如何从零开始重装win10系统。因为是写给新…

CPU设计实战-Wishbone总线接口

为什么需要改用总线接口&#xff1f; 1.但是在实际应用中&#xff0c;程序的体积可能非常大&#xff0c;指令存储器就不能再集成在FPGA内部了&#xff0c;一般使用FPGA芯片外部的Flash作为指令存储器。同理,-般使用FPGA芯片外部的SDRAM作为数据存储器。 2.统一接口标准。 很多…

科研学习|研究方法——实验法

1.实验方法的渊源 今天我们说物理学、生物学是实验的科学&#xff0c;应该不会有人再持异议了&#xff0c;然而连物理学这样的学科在历史上也并非一开始就是实验科学。在2000多年以前的亚里士多德时代&#xff0c;众人都认为物理学是非实验性质的&#xff0c;物理学成为实验科学…

cuda多版本安装

主要参考文章&#xff1a; ubuntu 20.04下多版本cuda&cudnn下载与安装 在ubuntu上安装多个版本的CUDA&#xff0c;并且可以随时切换 1 环境检查 nvidia-smiCUDA Version:12.4表示最高支持cuda 12.4版本 nvcc -V如图所示表示系统目前版本为cuda 12.2 2 多版本cuda下载与…

从零开始的LLaMA-Factory的指令增量微调

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 大模型应用向开发路径及一点个人思考大模型应用开发实用开源项目汇总大模型问答项目…

外卖项目:使用AOP切面,完成公共字段自动填充(断点调试详细讲解)

文章目录 一、问题描述二、实现思路三、实现步骤四、断点实操五、代码演示 一、问题描述 我们已经完成了后台系统的员工管理功能和菜品分类功能的开发&#xff0c;在新增员工或者新增菜品分类时需要设置创建时间、创建人、修改时间、修改人等字段&#xff0c;在编辑员工或者编…

Sentinel持久化(nacos)

导入依赖 <!--SpringCloud ailibaba sentinel-datasource-nacos --><dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId></dependency> yml配置 rule-type: naco配置: resource&am…

javaweb遇到的servlet问题,jar包问题

有时候会遇到这种问题&#xff0c;有的地方会报红 这是因为这个找不到这个包&#xff0c;这个项目缺少jar包 在tomcat9之前还不是Jakarta这个名字&#xff0c;我的运行环境与服务器是jdk17与tomcat10 解决方法&#xff1a; 在项目结构中&#xff0c;找到模块&#xff0c;再…

踏“时间”与“空间”前来探寻复杂度的奥妙(Java篇)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

机器人路径规划:基于广度优先搜索算法的机器人路径规划(提供Python代码)

一、广度优先搜索算法简介 广度优先搜索&#xff08;BFS&#xff09;是一种用于图或树的遍历算法&#xff0c;它从根节点开始&#xff0c;逐层地向下搜索&#xff0c;直到找到目标节点或遍历完整个图。BFS使用队列数据结构来实现&#xff0c;保证了节点的访问顺序是按照层级逐…

拌合楼管理系统(八) c#海康威视摄像头车牌识别

前言: c#调用海康威视SDK实现车牌识别 原本以为海康威视sdk的Demo里面没有车牌识别的实例,后来发现自己肤浅了,官方是有提供的,只是车牌识别是通过安防布警的方式实现的.程序主动监听,触发告警后获取到车牌信息. 一、接口调用的流程&#xff1a; 首先初始化sdk -> 开…

PHP使用PHP_DIO读取串口数据

一、安装PHP_DIO扩展 1. 下载对应版本的dll扩展 根据你的操作系统类型选择对应的扩展名 PECL :: Package :: dio 下载地址&#xff1a; PECL :: Package :: dio 0.2.1 for Windows 以我使用的为例 我本地使用的是phpStudy PHP为7.4.3nts 64位的那就需要下载 注意你的是线程安全…

嵌入式学习42-数据结构-双向链表

知识散记&#xff1a; 1.程序使用时&#xff08;开闭原则&#xff09; …