Linux缓冲区的理解

一、FIFE

  • 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
  • 所以C库当中的FILE结构体内部,必定封装了fd。
#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main()
{
    // C语言提供的接口
    printf("hello printfn");
    fprintf(stdout, "hello fprintfn");
    fputs("hello fputsn", stdout);
    // system call
    const char* msg = "hello writen";
    write(1, msg, strlen(msg));
    fork();
    return 0;
}
Linux缓冲区的理解

可以看到对过程重定向后结果发生了改变,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结构体

Linux缓冲区的理解
Linux缓冲区的理解

所以我们一般所说的缓冲区是语言级别的缓冲区,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提供的缓冲区!

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

三、理解缓冲区

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

#define SIZE 1024
typedef struct _FILE
{
	int flag;//刷新方式
	int fileno;//文件描述符
	char buffer[SIZE];//缓冲区
	int capacity;//buffer总容量
	int size;//buffer当前的使用量
}FILE_;
  • 头文件mystdio.h
#pragma once 
#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>
#define SYNC_NOW 1
#define SYNC_LINE 2
#define SYNC_FULL 4
#define SIZE 1024
typedef struct _FILE
{
    int flags; // 刷新方式
    int fileno; // 文件描述符
    char buffer[SIZE]; //缓冲区
    int capacity; // buffer总容量
    int size; // buffer当前的使用量
}FILE_;
FILE_* fopen_(const char* path_name, const char* mode);
void fclose_(FILE_* fp);
void fwrite_(const void* ptr, int num, FILE_* fp);Linux缓冲区的理解
  • mystdio.c
Linux缓冲区的理解

fsync将数据强制要求OS刷新进行外设同步

#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, "a") == 0)
    {
        flags |= (O_WRONLY | O_APPEND | O_CREAT);
    }
    else if(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)
    {
        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->capacity = SIZE;
    fp->size = 0;
    memset(fp->buffer, 0, SIZE);
    return fp; //打开一个文件,就会返回一个FILE*指针
}
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);
}
void fwrite_(const void* 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; //清空缓冲区
    }
    else if(fp->flags & SYNC_FULL)
    {
        if(fp->size == fp->capacity)
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    else if(fp->flags & SYNC_LINE)
    {
        if(fp->buffer[fp->size - 1] == 'n')
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    else
    {
        //TODO
    }
}Linux缓冲区的理解
  • 主函数main.c
#include"mystdio.h"
int main()
{
    FILE_* fp = fopen_("log.txt", "w");
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* msg = "hello Linuxn";
    int cnt = 0;
    while(1)
    {
        cnt++;
        fwrite_(msg, strlen(msg), fp);
        sleep(1);
        if(cnt == 10)
            break;
        printf("count:%dn", cnt);
    }
    fclose_(fp);
    return 0;
}Linux缓冲区的理解
Linux缓冲区的理解

文章来源:https://www.cnaaa.net,转载请注明出处:https://www.cnaaa.net/archives/7923

(0)
杰斯的头像杰斯
上一篇 2023年4月10日 下午4:36
下一篇 2023年4月11日 下午6:04

相关推荐

  • Linux操作系统中软件安装:用RPM包管理器安装软件步骤

    安装软件的一般步骤如下:1.打开终端,作为root用户或使用sudo命令获取管理员权限。2.使用RPM命令进行软件包的安装。例如,使用“rpm -ivh 软件包名称.rpm”命令来安装软件包,其中“-i”表示安装,“-v”表示显示详细安装信息,“-h”表示以适当的哈希标记显示安装进度。常用命令如下: 示例: 常用参数:

    2023年11月8日
    16700
  • Centos7安装postgresql数据库

    1.更新源 2.安装postgresql 3.初始化数据库 4.启动数据库并设置开机启动 5.登录postgresql并设置密码 postgresql在安装时默认添加用户postgres 输入 psql 进入数据库是这样的 设置密码: 退出按:q 备注其他:列出所有库l 列出所有用户du 列出库下所有表d 6.重启数据库: 7 创建数据库跟用户 因为post…

    2023年4月11日
    31500
  • CentOS 7下安装配置Tomcat

    CentOS 7下安装配置Tomcat 环境:CentOS 7.9 Tomcat下载地址:http://down.cnaaa.net/static/upload/other/20220802/1659432295529455.rar 安装rz工具 创建Tomcat目录 通用rz工具,将安装包上传 解压文件 修改目录名Tomcat8 没有JDK安装JDK 目录…

    2022年8月2日
    46400
  • Linux 实用工具 Screen —— 再也不怕因为网络连接中断杀死任务了!

    不知道小伙伴们是否遇到过这样的场景: 有时候,我们本地通过 SSH 连接到远程服务器并不是很稳定,经常会断开连接。如果此时我们正在做类似更新系统、DD 系统、远程传输 / 下载文件等需要一定时间的操作,就会非常难受,好不容易传了半天的文件,中断了,又得重新来传一次,还得祈祷这次别传一半断开了。 今天我们介绍的这个工具 ——screen,就是为了来解决上面这些…

    2023年11月9日
    16500
  • Linux firewall防火墙 换成 iptables 防火墙

    一、firewalld 增加开放端口 重启防火墙 二、iptables 增加开放端口 如果要修改防火墙配置,如增加防火墙端口3306 增加规则 保存退出后 最后重启系统使设置生效即可。 三、将firewalld防火墙换成iptables 1、直接关闭防火墙 2、设置 iptables service 如果要修改防火墙配置,如增加防火墙端口3306 增加规则 …

    2023年8月9日
    25300

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

在线咨询: QQ交谈

邮件:712342017@qq.com

工作时间:周一至周五,8:30-17:30,节假日休息

关注微信