Linux -- 缓冲区

目录

缓冲区

什么是缓冲区?

C语言的缓冲区

缓冲机制:

如何验证C语言缓冲区的存在?

模拟缓冲区的代码

[头文件 mystdio.h](#头文件 mystdio.h)

[源文件 mystdio.c](#源文件 mystdio.c)

代码区分全缓冲和行缓冲:

全缓冲:

行缓冲:

​编辑


缓冲区

什么是缓冲区?

缓冲区(Buffer)是指一块临时存储数据的内存区域

  1. 临时存储 :缓冲区用于临时存储输入或输出的数据。例如,在读取文件时,可以将一部分数据先读入缓冲区,然后程序可以从缓冲区中快速获取数据,而不需要每次都直接访问磁盘。

  2. 提高效率 :由于磁盘I/O操作比内存操作慢得多,使用缓冲区可以减少对较慢设备的直接访问次数,从而加快数据处理的速度。同样,对于网络通信,缓冲区可以帮助平滑数据流,避免因网络延迟而导致的性能问题。

  3. 批量处理 :缓冲区允许一次性处理多份数据 ,而不是每次只处理一份。这减少了系统调用的开销,提高了整体性能

C语言的缓冲区

我们一直说的缓冲区,其实是语言层面的缓冲区,和操作系统是没有直接关系的,即C语言会自带缓冲区!

当我们进行写入操作时(向显示器写入或者文件写入),系统调用接口为 write,在C语言中用的是 printf/fputs/fwrite(在底层中封装了系统调用),而调用系统调用接口是有时间和空间的成本(宁愿让它一次性拷贝多个数据,也不要让它多次拷贝),所以C语言在语言层面设置了一个缓冲区,一般在C语言写数据时,是先把数据写到C语言的缓冲区中,C语言再定期把数据交给系统调用接口,写入到操作系统中,以减少系统调用的次数

缓冲机制:

全缓冲 :在这种模式下,当缓冲区被填满或者显式地刷新缓冲区时,数据才会被写入到实际的输出设备。文件通常是全缓冲的,这意味着当你使用fprintf()向文件写入数据时,数据会先存放在缓冲区里,直到缓冲区满了或者是你关闭了文件(调用fclose()),或者是你手动刷新缓冲区(调用fflush())。
行缓冲 :对于一些特定的流,比如标准输入(stdin)和标准输出(stdout),默认情况下是行缓冲的。这表示每当遇到换行符('\n')或者缓冲区满的时候,数据就会被自动刷新到终端。如果你使用printf()打印信息,那么除非你打印了一个换行符或者缓冲区已满,否则数据可能不会立即出现在屏幕上。
无缓冲 :也称为不带缓冲。这种模式下,每个输入或输出请求都会立即执行,数据会立刻被发送到目的地或者从源头接收过来标准错误流(stderr)默认是无缓冲的,因为错误信息通常需要尽快显示出来以便用户能够及时响应。

如何验证C语言缓冲区的存在?

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>

int main()
{
    //调用系统调用,向 stdout 文件写入
    const char *s1="hello write\n";
    write(1,s1,strlen(s1));

    //调用C语言接口
    const char *s2="hello fprintf\n";
    fprintf(stdout,"%s",s2);

    //对照
    const char *s3="hello fwrite\n";
    fwrite(s3,strlen(s3),1,stdout);

    fork();//创建子进程

    return 0;
}

同一份代码重定向到不同的位置输出不同的内容!

  • 向显示器打印无论有没有fork都是行刷新,都是3条;
  • 重定向后,变成全缓冲,我们写的 "hello fprintf" "hello fwrite"都先放到了C语言的缓冲区,fork 创建出的子进程和父进程都指向了同一个C语言缓冲区,子进程退出后,就会刷新C语言缓冲区,因为进程具有独立性,所以这时候子进程会进行写时拷贝,把C语言缓冲区的数据拷贝了一份,接着父进程退出,也会刷新C语言缓冲区的数据,所以同一句话就被刷新了2次,而系统调用并没有把**"hello write"写入到C语言的缓冲区中,而是直接写入到操作系统**,和进程没关系,与写时拷贝无关,所以最终打印出了5条

模拟缓冲区的代码

头文件 mystdio.h

cpp 复制代码
#pragma once
#include<stdio.h>

#define SIZE 4096

//选项1,无刷新
#define NONE_FLUSH (1<<1)
//选项2,行刷新
#define LINE_FLUSH (1<<2)
//选项3,全刷新
#define FULL_FLUSH (1<<3)

typedef struct _myFILE
{
    char outbuffer[SIZE];//缓冲区
    int pos;//要写入的位置
    int cap;//缓冲区的顶
    int fileno;//文件描述符
    int flush_mode;//刷新模式
}myFILE;

myFILE *my_fopen(const char *pathname,const char *mode);
int my_fwrite(myFILE *fp,const char *s,int size);
void my_fflush(myFILE *fp);
void my_fclose(myFILE *fp);
void DebugPrint(myFILE *fp);

源文件 mystdio.c

cpp 复制代码
#include"mystdio.h"
#include<string.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>

//打开文件,及选择读写方式
myFILE *my_fopen(const char *pathname,const char *mode)
{
    int flag=0;
    //r模式(读)
    if(strcmp(mode,'r')==0)
    {
        flag|=O_RDONLY;
    }
    //w模式(写)
    else if(strcmp(mode,'w')==0)
    {
        //文件不存在就新建文件,存在的话就不用新建
        //每次打开该文件时清空文件内容
        flag|=(O_CREAT|O_WRONLY|O_TRUNC);
    }
    //a模式(追加)
    else if(strcmp(mode,'a')==0)
    {
        //文件不存在就新建文件,存在的话就不用新建
        //不会清空文件内容
        flag|=(O_CREAT|O_WRONLY|O_APPEND);
    }
    else return NULL;//不存在的模式

    //用系统调用接口,获取文件描述符
    int fd=0;//文件描述符
    if(flag & O_WRONLY)
    {
        umask(0);//设置文件权限
        fd=open(pathname,flag,0666);
    }
    else{
        fd=open(pathname,flag);
    }

    //文件创建失败
    if(fd<0)    return NULL;

    myFILE *fp=(myFILE*)malloc(sizeof(myFILE));
    if(fp==NULL)    return NULL;//开辟空间失败
    fp->cap=SIZE;
    fp->fileno=fd;
    fp->pos=0;
    fp->flush_mode=LINE_FLUSH;
}

//刷新缓冲区
void my_fflush(myFILE *fp)
{
    if(fp->pos==0) return;//没必要调用系统调用接口

    write(fp->fileno,fp->outbuffer,fp->pos);
    fp->pos=0;//清空缓冲区
}

//向文件写入
int my_fwrite(myFILE *fp,const char *s,int size)
{
    memcpy(fp->outbuffer+fp->pos,s,size);
    fp->pos+=size;

    //判断是否需要刷新
    //行刷新且结尾为 \n
    if((fp->flush_mode & LINE_FLUSH) && fp->outbuffer[fp->pos-1]=='\n')
    {
        my_fflush(fp);
    }
    //全刷新
    else if((fp->flush_mode & FULL_FLUSH) && fp->pos==fp->cap)
    {
        my_fflush(fp);
    }
    return size;
}

//关闭文件
void my_fclose(myFILE *fp)
{
    //刷新缓冲区
    my_fflush(fp);
    close(fp->fileno);

    free(fp);//释放空间
}

//把刷新模式用字符表示
const char *toString(int flag)
{
    if(flag & NONE_FLUSH)    return "NONE";
    else if(flag & LINE_FLUSH)   return "LINE";
    else if(flag & FULL_FLUSH)   return "FULL";
    else return "UNKNOW";
}
//打印错误信息
void DebugPrint(myFILE *fp)
{
    printf("outbuffer:%s\n",fp->outbuffer);
    printf("pos:%d\n",fp->pos);
    printf("cap:%d\n",fp->cap);
    printf("fd:%d\n",fp->fileno);
    printf("flush_mode:%s\n",toString(fp->flush_mode));
}

代码区分全缓冲和行缓冲:

全缓冲:
cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include"mystdio.h"

const char *filename="./log.txt";
int main()
{
    myFILE *fp=my_fopen(filename,"w");
    if(fp==NULL) return 1;//打开失败

    int cnt=5;
    char buffer[64];
    while(cnt)
    {
        snprintf(buffer,sizeof(buffer),"helloworld,hellobit,%d ",cnt--);
        my_fwrite(fp,buffer,strlen(buffer));
        DebugPrint(fp);
        sleep(2);//休眠2s,看清现象
    }
    my_fclose(fp);
    return 0;
}
行缓冲:

行缓冲是在打印 helloworld,hellobit 的基础上加上**\n**。

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include"mystdio.h"

const char *filename="./log.txt";
int main()
{
    myFILE *fp=my_fopen(filename,"w");
    if(fp==NULL) return 1;//打开失败

    int cnt=5;
    char buffer[64];
    while(cnt)
    {
        snprintf(buffer,sizeof(buffer),"helloworld,hellobit,%d\n",cnt--);
        my_fwrite(fp,buffer,strlen(buffer));
        DebugPrint(fp);
        sleep(2);//休眠2s,看清现象
    }
    my_fclose(fp);
    return 0;
}

对比之下就可以看出,全缓冲的缓冲区会在 while 结束后才刷新缓冲区,而行缓冲每写一句刷新一次缓冲区。

相关推荐
追风赶月、6 分钟前
【Linux】线程概念与线程控制
linux·运维·服务器
小字节,大梦想8 分钟前
【Linux】重定向,dup
linux
肥猪猪爸1 小时前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
linux_carlos1 小时前
环形缓冲区
数据结构
blessing。。1 小时前
I2C学习
linux·单片机·嵌入式硬件·嵌入式
readmancynn1 小时前
二分基本实现
数据结构·算法
萝卜兽编程1 小时前
优先级队列
c++·算法
Bucai_不才1 小时前
【数据结构】树——链式存储二叉树的基础
数据结构·二叉树
2202_754421541 小时前
生成MPSOC以及ZYNQ的启动文件BOOT.BIN的小软件
java·linux·开发语言
盼海1 小时前
排序算法(四)--快速排序
数据结构·算法·排序算法