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

相关推荐

  • 使用cloud-init新建虚拟机后重启系统网络IP地址丢失

    cloud-init 是 Linux 平台的⼀个开源⼯具,可以为云平台的 Linux 虚拟机完成⼀系列系统初始化配置。作为⼀个⾮常驻服务,开机启动时执⾏后⽴即退出,不会监听任何端⼝。 环境 问题 解决方案 如果在firstboot部分之后不使用cloud init服务,解决此问题的方法是禁用并停止cloud init服务。在第一个引导步骤之后,禁用cloud…

    2023年2月2日
    89000
  • diff命令比较两个文件的不同

    [语法]: diff [参数] 文件1 文件2-b 将一串空格或TAB 转换成一个空格或TAB-e 生成一个编辑角本,作为ex 或ed 的输入可将文件1 转换成文件2[例子]:diff file1 file2diff -b file1 file2diff -e file1 file2 >edscriptdiff 命令的常用参数参数 说明a 将所有文件当…

    2023年11月29日
    21900
  • Linux实验案例:构建DHCP服务器

    公司要求将闲置的一台Linux主机配置为DHCP服务器,以便为局域网内员工的办公用机提供自动分配IP地址服务,提高网络管理和维护的效率。 需求描述 给局域网内各主机自动分配的IP地址范围为192.168.10.20~192.168.10.30 局域网内各主机使用的默认网关地址为192.168.10.2 局域网内各主机使用的DNS服务器地址分别为202.96.…

    2024年6月24日
    10100
  • Centos在dracut模式下如何修改系统配置文件

    1、进入dracut原因 Centos系统进入dracut的原因有很多比如:系统重要文件丢失系统配置文件配置错误等 2、恢复系统文件 2.1、dracut环境 在dracut上下文中是无法看到Centos系统文件的dracut# 2.2、找到系统盘 Centos默认安装时,系统盘是一个lv设备 如果安装时,手动修改了分区,那么可以根据实际情况找到系统设备比如…

    2023年6月12日
    34700
  • vnstat 统计服务器网卡流量

    vnStat 是一款开源的网络流量统计工具,可以方便的查看当天,当月流量统计。官网地址: 安装 在 Debian/Ubuntu 下非常简单 假设网卡名为 eth0,该配置在 /etc/vnstat.conf 中,安装结束后初始化数据库 添加为开机启动 使用 直接输入 vnstat 图形化输出可以使用 vnst…

    2023年9月25日
    36800

发表回复

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

在线咨询: QQ交谈

邮件:712342017@qq.com

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

关注微信