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 结束后才刷新缓冲区,而行缓冲每写一句刷新一次缓冲区。

相关推荐
牛客企业服务35 分钟前
2025年AI面试推荐榜单,数字化招聘转型优选
人工智能·python·算法·面试·职场和发展·金融·求职招聘
胡斌附体1 小时前
linux测试端口是否可被外部访问
linux·运维·服务器·python·测试·端口测试·临时服务器
愚润求学1 小时前
【Linux】自旋锁和读写锁
linux·运维
大锦终1 小时前
【Linux】常用基本指令
linux·运维·服务器·centos
IT项目管理1 小时前
达梦数据库DMHS介绍及安装部署
linux·数据库
糖葫芦君1 小时前
Policy Gradient【强化学习的数学原理】
算法
知北游天1 小时前
Linux:多线程---深入互斥&&浅谈同步
linux·运维·服务器
Gappsong8741 小时前
【Linux学习】Linux安装并配置Redis
java·linux·运维·网络安全
try2find2 小时前
移动conda虚拟环境的安装目录
linux·运维·conda
码农101号2 小时前
Linux中容器文件操作和数据卷使用以及目录挂载
linux·运维·服务器