[Linux打怪升级之路]-缓冲区

这篇具有很好参考价值的文章主要介绍了[Linux打怪升级之路]-缓冲区。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux作者小蜗牛向前冲

[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux名言我可以接受失败,但我不能接受放弃

[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux

  如果觉的博主的文章还不错的话,还请[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正

 本期学习目标:认识什么是缓冲区,缓冲区在哪里,模拟实现一个简单的缓冲区。

目录

一、缓冲区

1、见一个现象

2、缓冲区的相关知识

3、解释现象 

二、模拟实现缓冲区 

1、makefile 

2、myStdio.h 

 3、myStdio.c

4、test.c 


一、缓冲区

[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux

我们在重定向博客中曾经发现了一个现象,在做重定向实验时,我们将文件描述符fd = 1关闭掉,并通过open函数打开(创建)add.txt的文件,由于fd = 1 被关闭了,根据文件描述符fd的分配规则:是从小到大 ,遵循寻找最小而且没有被占用的的fd分配这时候fd = 1中file*的指针会指向add.txt文件中,就不在向显示器打印了,而要将open fd的内容写到add.txt中,但是我们通过cat命令查看add.txt中的内容却什么也没有,这是为什么呢?

这就不得不提缓冲区的概念,其实缓存区就是一段内存,但是这段内存是谁申请的?属于谁的?为什么要有缓冲区呢?

下面我们来看一个现象:

1、见一个现象

[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux

首先我们分别调用C接口和系统接口进行打印测试。

[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux

我们将mytest中的文件内容输出重定向到log.txt中,我们也在log.txt中查找到了输出的内容.

下面我们继续进行在代码测试,在代码最后用fork建立一个子进程

[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux

运行程序:

[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux

 我们发现 printf 和 fprintf及fputs(库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?我们只是在多加了应该子进程而已,这说明出现这种现象肯定是和fork函数有关。

2、缓冲区的相关知识

为什么库函数会打印二次,而系统调用的函数只会被打印一次呢?毋庸置疑这肯定和缓冲区有关。

上面我们提到缓存冲区是一段内存,那么既然是一段内存肯定要被管理起来,而管理缓冲区的结构体我们称之为FILE,而且我们可以知道是缓冲区肯定不在内核中。

我们也可以在系统中见一见他

//输入命令
vim /usr/include/libio.h

 打开文件在246行这样就能看到_IO_FILE的结构体,不对啊吖,不是说FILE才是管理缓冲区的吗?[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux

怎么变成了_IO_FILE的结构体,其实在其实是在:

typedef struct _IO_FILE FILE; 在/usr/include/stdio.h

中进行了重命名的,第48行就对_IO_FILE的结构体进行了typedef。

[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux

这里我们需要注意的是FILE结构体中也封装了fd,这就会在合适的时候,就会将在缓冲区中内容刷新到外设中。

缓冲区的刷新几种形式: 

立刻刷新       -----无缓冲

行刷新           ------显示器

缓冲区满刷新     -------磁盘文件

 那我们怎么理解上面的几种刷新方式呢?

立刻刷新是只直接在内存中的信息,刷新到外设,这种场景是非常少见的,因为这样非常消耗资源。

行刷新,就是缓冲区满了一行就刷新,也就是说我们在调用函数时有"\n"时就会进行刷新。

缓冲区满刷新,就是指缓冲区的内存满了,才会把缓冲区里面的内容刷新到外设中。

缓冲区的自动刷新规则:

  • 用户强制刷新
  • 进程退出

3、解释现象 

上面我们了解有关缓冲区的相关知识,那么为什么会出现我们上面的现象呢?

在代码结束前我们进行了子进程的创建:

代码结束之前,进行创建子进程
   1. 如果我们没有进行>,看到了4条消息
stdout 默认使用的是行刷新,在进程fork之前,三条C函数已经将数据进行打印输出到显示器上(外设),你的FILE内部,进程内部不存在对应的数据啦。
    2. 如果我们进行了>, 写入文件不再是显示器,而是普通文件,采用的刷新策略是全缓冲,之前的3条c显示函数,虽然带了\n,但是不足以stdout缓冲区写满!数据并没有被刷新!!!
    执行fork的时候,stdout属于父进程,创建子进程时, 紧接着就是进程退出!谁先退出,一定要进行缓冲区刷新(就是修改)
    由于写时拷贝!!数据最终会显示两份,所以在父子进程退出后,会立刻被缓冲区刷新,从而导致三条C函数分别进行了二次打印。
3. write为什么没有呢?

上面的过程都和wirte无关,wirte没有FILE,而用的是fd,就没有C提供的缓冲区

 这里我们就可以回答:

缓冲区在哪里

在FILE*指向的FILE结构体中(这也就是为什么,我们自己要强制刷新的时候要传文件指针,fflush(文件指针),fclose(文件指针))。

重定向实验的现象:

因为我们虽然将open fd的内容要写入到add.txt中,但是由于add.txt是普通文件,他采取的方式是全缓存,就不足以以让缓冲区刷新到显示器(stdout)中,所以通过cat 命令查看会什么也查不出来。

二、模拟实现缓冲区 

这里我们分模块化实现:

[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux

1、makefile 

这里我们用makefile来完成对程序的自动化编译和构建程序

test:test.c myStdio.c   //依赖关系                                                                                 
    gcc -o $@ $^ -std=c99//依赖方法
.PHONY:clean//声明伪目标clean
clean:
    rm -f test

2、myStdio.h 

myStdio.h中对 缓冲区结构进行定义并且进行相关的函数声明:

#pragma once

#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 1024
#define SYNC_NOW    1//sync能够马上刷新缓冲区(马上刷新)
#define SYNC_LINE   2//行刷新
#define SYNC_FULL   4//全缓冲刷新

typedef struct _FILE{
    int flags; //刷新方式
    int fileno;
    int cap; //buffer的总容量
    int size; //buffer当前的使用量
    char buffer[SIZE];
}FILE_;


FILE_ *fopen_(const char *path_name, const char *mode);
void fwrite_(const void *ptr, int num, FILE_ *fp);
void fclose_(FILE_ * fp);
void fflush_(FILE_ *fp)

 3、myStdio.c

 在myStdio.c中对 缓冲区功能函数进行实现:

这里我们主要实现:

fopen_打开文件。fwrite_x向文件中写入,fflush_刷新缓冲区,fclose_关闭文件

#include "myStdio.h"

FILE_ *fopen_(const char *path_name, const char *mode)
{
    int flags = 0;
    int defaultMode=0666;

    if(strcmp(mode, "r") == 0)
    {
        flags |= O_RDONLY;
    }
    else if(strcmp(mode, "w") == 0)
    {
        flags |= (O_WRONLY | O_CREAT |O_TRUNC);
    }
    else if(strcmp(mode, "a") == 0)
    {
        flags |= (O_WRONLY | O_CREAT |O_APPEND);
    }
    else
    {
        //TODO
    }
    int fd = 0;

    if(flags & O_RDONLY) fd = open(path_name, flags);
    else fd = open(path_name, flags, defaultMode);
    if(fd < 0)
    {
        const char *err = strerror(errno);
        write(2, err, strlen(err));
        return NULL; // 为什么打开文件失败会返回NULL
    }
    FILE_ *fp = (FILE_*)malloc(sizeof(FILE_));
    assert(fp);

    fp->flags = SYNC_LINE; //默认设置成为行刷新
    fp->fileno = fd;
    fp->cap = SIZE;
    fp->size = 0;
    memset(fp->buffer, 0 , SIZE);

    return fp; // 为什么你们打开一个文件,就会返回一个FILE *指针
}

void fwrite_(const void *ptr, int num, FILE_ *fp)
{
    // 1. 写入到缓冲区中
    memcpy(fp->buffer+fp->size, ptr, num); //这里我们不考虑缓冲区溢出的问题
    fp->size += num;

    // 2. 判断是否刷新
    if(fp->flags & SYNC_NOW)
    {
        write(fp->fileno, fp->buffer, fp->size);
        fp->size = 0; //清空缓冲区
    }
    else if(fp->flags & SYNC_FULL)
    {
        if(fp->size == fp->cap)
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    else if(fp->flags & SYNC_LINE)
    {
        if (strcmp(&(fp->buffer[fp->size - 1]), "\n") == 0)
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    else{

    }
}

void fflush_(FILE_ *fp)
{
    if( fp->size > 0) write(fp->fileno, fp->buffer, fp->size);
    fsync(fp->fileno); //将数据,强制要求OS进行外设刷新!
    fp->size = 0;
}

void fclose_(FILE_ * fp)
{
    fflush_(fp);
    close(fp->fileno);
}

4、test.c 

#include "myStdio.h"
#include <stdio.h>

int main()
{
    FILE_ *fp = fopen_("./hello.txt", "w");
    if(fp == NULL)
    {
        return 1;
    }
    int cnt = 10;
    const char *msg = "hello pjb ";
    while(1)
    {
        fwrite_(msg, strlen(msg), fp);
        sleep(1);
        printf("count: %d\n", cnt);
        cnt--;
        if(cnt == 0) break;
    }
    fclose_(fp);

    return 0;
}

 下面写一个简单的bush脚本:

 while :; do cat hello.txt;sleep 1;echo "###############";done

这是一个简单的 Bash 脚本,它的功能是循环读取并打印文件 "hello.txt" 的内容,并每隔 1 秒打印一条分隔线。

解释一下脚本的含义:

  • while :; do 表示开始一个无限循环。
  • cat hello.txt 使用 cat 命令读取并打印 "hello.txt" 文件的内容。
  • sleep 1 表示暂停执行 1 秒,即等待一秒钟。
  • echo "###############" 打印一条分隔线,由多个 "#" 字符组成。
  • done 表示循环结束。

因此,执行这段脚本时,会不断循环读取并打印 "hello.txt" 文件的内容,每次打印之间会有一秒的暂停,并且在每次打印后会输出一条分隔线。

请确保当前目录下存在名为 "hello.txt" 的文件,并且具有可读权限。

测试:

 1、当写入文件的msg字符串不带换行符时。

    const char *msg = "hello pjb ";

 [Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux

 这里我们观察到当程序结束时,才将缓冲区中的内容刷新到hello.txt文件中。

2、当写入文件的msg字符串带换行符

    const char *msg = "hello pjb\n";

[Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux这里名为可以验证到带\n普通文件是逐行进行刷新的。

缓冲区总结 :

看到这些现象我们不由的想缓存区的刷新策略:有全缓存,行缓冲,立即刷新

上面是我们自己进行的封装,但是这和os(操作系统)有什么关系呢?下面来看一幅图

 [Linux打怪升级之路]-缓冲区,Linux的学习日常知识,linux

这幅图大致说明了字符串,要写入到文件中,需要经过层层拷贝在 最终由操作系统(OS)决定刷新到磁盘文件中。

这里我们要注意的是,在有用户刷新到C语言的缓冲区(FILE)中才会遵循全缓冲,行缓冲。对于操作系统来说他会自己调配资源进行刷新。

特别注意:

我们也可以强制OS刷新,调用fflush()就可以了。

ffush()的底层:

void fflush_(FILE_ *fp)
{
    if( fp->size > 0) write(fp->fileno, fp->buffer, fp->size);
    fsync(fp->fileno); //将数据,强制要求OS进行外设刷新!
    fp->size = 0;
}

其实是调用来fsync的接口进行强制刷新。文章来源地址https://www.toymoban.com/news/detail-725071.html

到了这里,关于[Linux打怪升级之路]-缓冲区的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux】深入理解缓冲区

    目录 什么是缓冲区 为什么要有缓冲区 缓冲区刷新策略 缓冲区在哪里  手动设计一个用户层缓冲区 缓冲区本质上一块内存区域,用来保存临时数据。 缓冲区在各种计算任务中都广泛应用,包括输入/输出操作、网络通信、图像处理、音频处理等。 这块内存区域是由 谁提供的

    2024年02月15日
    浏览(62)
  • 【linux】重定向+缓冲区

    自我名言 : 只有努力,才能追逐梦想,只有努力,才不会欺骗自己。 喜欢的点赞,收藏,关注一下把! close(1),为什么没有打印新建文件fd呢? printf(“%dn”,fd); printf会把内容打印到stdout文件中。 但是close(1)关闭标准输出stdout—显示器,int fd=open();新打开的文件fd是1。 st

    2024年02月08日
    浏览(54)
  • Linux之缓冲区的理解

    目录 一、问题引入 二、缓冲区 1、什么是缓冲区 2、刷新策略 3、缓冲区由谁提供 4、重看问题 三、缓冲区的简单实现 我们先来看看下面的代码:我们使用了C语言接口和系统调用接口来进行文件操作。在代码的最后,我们还使用fork函数创建了一个子进程。  代码运行结果如

    2024年02月03日
    浏览(48)
  • 浅谈linux缓冲区的认识!

    今天来为大家分享一波关于缓冲区的知识!那么既然我们要谈缓冲区,那么就得从是什么?为什么?有什么作用这几个方面来谈论一下缓冲区!然后再通过一些代码来更加深刻的理解缓冲区的知识! 从最简单的理解方面来,我们可以将缓冲区理解成一块内存!那么这块内存是

    2024年02月05日
    浏览(54)
  • 【Linux】深入理解文件缓冲区

    问题引入 首先看一段代码: 运行代码,结果如下: 如果此时将输出结果重定向一下: 会发现 printf 、 fwrite 都打印了两次。 究其原因,就要谈到缓冲区和缓冲区刷新策略的概念了。 如何理解缓冲区 假设你在青岛,你要从网上买一件商品,商家所在地是北京。你不会跑去北

    2024年02月11日
    浏览(55)
  • 【Linux】缓冲区+磁盘+动静态库

    缓冲区的本质就是一段用作缓存的 内存 。 节省进程进行数据IO的时间。进程使用fwrite等函数把数据拷贝到缓冲区或者外设中。 3.1、 立即刷新(无缓冲)——ffush() 情况很少,比如调用printf后,手动调用fflush刷新缓冲区。 3.2、 行刷新(行缓冲)——显示器 显示器需要满足人

    2024年02月05日
    浏览(39)
  • 【Linux】基础IO----理解缓冲区

    作者:დ旧言~ 座右铭:松树千年终是朽,槿花一日自为荣。 目标:理解缓冲区 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安! 专栏选自:Linux初阶 望小伙伴们点赞👍收藏✨加关注哟💕💕 缓冲区大家其实不陌生,像我们使用的 VS2019 编译器这里就有缓冲区,那它

    2024年04月13日
    浏览(44)
  • 【Linux】基础IO —— 缓冲区深度剖析

    (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是 Scort 目前状态:大三非科班啃C++中 🌍博客主页:张小姐的猫~江湖背景 快上车🚘,握好方向盘跟我有一起打天下嘞! 送给自己的一句鸡汤🤔: 🔥真正的大师永远怀着一颗学徒的心 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏 🎉🎉

    2024年02月02日
    浏览(53)
  • Linux操作系统——重定向与缓冲区

    上一篇文章(文件详解)我们一直在谈,一个文件要被访问就必须要先被打开,打开之前就必须要先把文件加载到内存,同时呢我们的操作系统为了管理文件也会为我们的文件创建相对应的struct file对象,那么这个struct file对象里面应该有什么? 其实struct file里面最核心的两个

    2024年01月16日
    浏览(47)
  • 用Linux的视角来理解缓冲区概念

    缓冲区(buffer)是存储数据的临时存储区域。当我们用C语言向文件中写入数据时,数据并不会直接的写到文件中,中途还经过了缓冲区,而我们需要对缓冲区的数据进行刷新,那么数据才算写到文件当中。而缓冲区通常是一块内存区域,可以是数组、队列、链表等数据结构。

    2024年01月20日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包