0


【Linux】缓冲区的理解

大家好我是沐曦希💕

文章目录

一、FIFE

  • 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
  • 所以C库当中的FILE结构体内部,必定封装了fd。
#include<stdio.h>#include<string.h>#include<unistd.h>intmain(){// C语言提供的接口printf("hello printf\n");fprintf(stdout,"hello fprintf\n");fputs("hello fputs\n",stdout);// system callconstchar* msg ="hello write\n";write(1, msg,strlen(msg));fork();return0;}

在这里插入图片描述

可以看到对过程重定向后结果发生了改变,C接口的函数printf,fprintf,fputs都被打印了两次;而系统接口write前后只被打印了一次,为什么呢?肯定和fork有关!

fork会创建子进程。在创建子进程的时候,数据会被处理成两份,父子进程发生写时拷贝,我们进行printf调用数据的时候,数据写到显示器外设上,就不属于父进程了,数据没被写到显示器上,依旧属于父进程,而调用printf并不一定把数据刷到显示器上,没有被显示本质就是数据没有从内存到外设,所以这份没有被显示的数据依旧属于这进程,当我们去fork的时候,进程退出要刷新缓冲区,此时刷新的过程就是把数据从内存刷新到外设,刷新到外设的同时,也会把程序内部的缓冲区的数据直接清走,这就是写入,跟写时拷贝有关系

二、缓冲区

1.理解

缓冲区本质就是一段内存!!在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。

数据如果直接从内存到磁盘,在内存中速度快,但是访问外设效率比较低,那太消耗时间了,属于外设IO,所以缓冲区的意义是节省进程进行数据IO的时间。进程需要将数据拷贝到缓冲区中,这里我们并不需要拷贝,而是调用fwrite,与其理解fwrite是写入到文件的函数,倒不如理解fwrite是拷贝函数!!将数据从进程拷贝到缓冲区或者外设中。

数据直接拷贝到缓冲区,高速设备可以不必等待低速设备,提高了计算机运行速率。

2.刷新策略

缓冲区刷新策略:如果一块数据一次写入到外设(效率最高) VS 如果一块数据多次少批量写入到外设

缓冲区一定会结合具体的设备,定制自己的刷新策略:

  1. 立即刷新——无缓冲,场景少,即在printf后立即fflush
  2. 行刷新——行缓冲——显示器,数据的printf带上\n就会立马显示到显示器上。显示器为什么是行缓冲:显示器是外设,进程运行时在内存里的,把数据定期要刷新到外设,显示器设备比较特殊,是给用户来看的,从左到右,所以显示器为了保证刷新效率,并且用户体验良好,所以显示器采用行缓冲,满足用户的阅读体验并且在一定程度上效率不至于太低
  3. 缓冲区满——全缓冲——磁盘文件,效率最高,只需要一次IO,比如文件读写的时候,直接写到磁盘文件,缓冲区满了采取刷新,减少IO次数,提高效率。

存在特殊情况:1.用户强制刷新,2.进程退出——一般都要进行缓冲区刷新

3.在哪里

上面例子中直接往显示器打印是4条,往文件中打印为七条,这种现象一定和缓冲区有关,所以缓冲区一定不在内核中!! 因为如果在内核中,系统接口write一定会打印两次!

我们之前谈论的所有的缓冲区,都指的是用户级语言层面给我们提供的缓冲区!!这个缓冲区在stdout,stdin,stderr->FILE*->FILE是一个结构体,里面封装了fd,还包括一个缓冲区,所以我们自己要强制刷新fflush(文件指针),fclose(文件指针),这是因为传进去的文件指针包含的缓冲区

从源码出发,我们来看一看FILE结构体
在这里插入图片描述
在这里插入图片描述
所以我们一般所说的缓冲区是语言级别的缓冲区,C语言提供的在FILE结构体里对应的缓冲区。

综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

1.如果我们没有进行 > (重定向),看到4条消息,stdout默认使用的是行刷新,在进程fork之前,三条C函数已经将数据进行打印输出到显示器上(外设),你的FILE内部进程内部不存在对应数据啦!

2.如果我们进行了重定向>,写入文件不在是显示器,而是普通文件,采用的刷新策略是全缓冲,采用的刷新策略是全缓冲,之前的3条显示函数虽然带了\n,但是不足以stdout缓冲区写满!数据并没有被刷新!!!执行fork的时候,stdout属于父进程,创建子进程时,紧接着就是进程退出!谁先退出,一定要进行缓冲区的刷新!(刷新本质就是修改)修改就会发生写时拷贝,数据最终会显示两部分!

上面部分都与write无关,write没有FILE,用的是fd,就没有C提供的缓冲区!

简单来说:重定向导致刷新策略发生改变(由行缓冲变成了全缓冲)。同时发生了写时拷贝,父子进程各自刷新。

三、理解缓冲区

我们可以通过写一个小型缓冲区并封装一下来理解缓冲区

#defineSIZE1024typedefstruct_FILE{int flag;//刷新方式int fileno;//文件描述符char buffer[SIZE];//缓冲区int capacity;//buffer总容量int size;//buffer当前的使用量}FILE_;
  • 头文件mystdio.h
#pragmaonce #include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<fcntl.h>#include<stdlib.h>#include<assert.h>#include<string.h>#include<errno.h>#defineSYNC_NOW1#defineSYNC_LINE2#defineSYNC_FULL4#defineSIZE1024typedefstruct_FILE{int flags;// 刷新方式int fileno;// 文件描述符char buffer[SIZE];//缓冲区int capacity;// buffer总容量int size;// buffer当前的使用量}FILE_;
FILE_*fopen_(constchar* path_name,constchar* mode);voidfclose_(FILE_* fp);voidfwrite_(constvoid* ptr,int num, FILE_* fp);
  • mystdio.c

在这里插入图片描述
fsync将数据强制要求OS刷新进行外设同步

#include"mystdio.h"
FILE_*fopen_(constchar* path_name,constchar* mode){int flags =0;int defaultMode =0666;if(strcmp(mode,"r")==0){
        flags |= O_RDONLY;}elseif(strcmp(mode,"a")==0){
        flags |=(O_WRONLY | O_APPEND | O_CREAT);}elseif(strcmp(mode,"w")==0){
        flags |=(O_WRONLY | O_TRUNC | O_CREAT);}else{// }int fd =0;if(flags & O_RDONLY)
        fd =open(path_name, flags);else 
        fd =open(path_name, flags, defaultMode);if(fd <0){constchar* err =strerror(errno);write(2, err,strlen(err));returnNULL;// 打开文件失败返回NULL}
    FILE_* fp =(FILE_*)malloc(sizeof(FILE_));assert(fp);
    fp->flags = SYNC_LINE;// 默认设置成行刷新
    fp->fileno = fd;
    fp->capacity = SIZE;
    fp->size =0;memset(fp->buffer,0, SIZE);return fp;//打开一个文件,就会返回一个FILE*指针}voidfflush_(FILE_* fp){if(fp->size >0)write(fp->fileno, fp->buffer, fp->size);fsync(fp->fileno);// 将数据强制要求OS刷新进行外设的同步
    fp->size =0;}voidfclose_(FILE_* fp){fflush_(fp);close(fp->fileno);}voidfwrite_(constvoid* ptr,int num, FILE_* fp){//把数据写入到缓冲区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;//清空缓冲区}elseif(fp->flags & SYNC_FULL){if(fp->size == fp->capacity){write(fp->fileno, fp->buffer, fp->size);
            fp->size =0;}}elseif(fp->flags & SYNC_LINE){if(fp->buffer[fp->size -1]=='\n'){write(fp->fileno, fp->buffer, fp->size);
            fp->size =0;}}else{//TODO}}
  • 主函数main.c
#include"mystdio.h"intmain(){
    FILE_* fp =fopen_("log.txt","w");if(fp ==NULL){perror("fopen");return1;}constchar* msg ="hello Linux\n";int cnt =0;while(1){
        cnt++;fwrite_(msg,strlen(msg), fp);sleep(1);if(cnt ==10)break;printf("count:%d\n", cnt);}fclose_(fp);return0;}

在这里插入图片描述

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/m0_68931081/article/details/129516676
版权归原作者 沐曦希 所有, 如有侵权,请联系我们删除。

“【Linux】缓冲区的理解”的评论:

还没有评论