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

相关推荐

  • wordpress上传图片提示“缺少临时文件夹”的解决方法

    在wordpress发布文章上传图片时出现了“缺少临时文件夹”的错误提示,该错误通常是由于服务器php配置的原因,如果之前上传图片正常,而且最近操作过服务器就很有可能是php配置导致的错误,解决方法也很简单,修改一下php配置文件即可。 Linux主机: 1、编辑配置文件php.ini,搜索查找upload_tmp_dir,修改为: upload_tmp_d…

    2023年5月5日
    44000
  • centos 升级内核

    一、关于内核版本的定义: 版本性质:主分支ml(mainline),稳定版(stable),长期维护版lt(longterm) 版本命名格式为 “A.B.C”: 数字 A 是内核版本号:版本号只有在代码和内核的概念有重大改变的时候才会改变,历史上有两次变化: 第一次是1994年的 1.0 版,第二次是1996年的 2.0 版,第三次是2011年的 3.0 版…

    2023年3月16日
    39000
  • linux手误rm后恢复文件

    误删文件还原可以分为两种情况 今天只分析文件被删除后,相关进程还存在的情况 这种一般是有活动的进程存在持续标准输入或输出,到时文件被删除后,进程PID依旧存在。这也是有些服务器删除一些文件但是磁盘不释放的原因 案例演示 创建一个文件 用tail -f打开rumenz.txt文件 目的是让rumenz.txt被删除后,进程依然存在 新开一个终端删除rumenz…

    2023年10月17日
    44000
  • 幻兽帕鲁服务器一键部署保姆教程

    普通一键部署(使用一键部署脚本) Windows 服务器一键部署 Windows的一键部署需要借助 PowerShell 来完成。PowerShell是一种任务自动化和配置管理框架,它提供了一个命令行Shell和脚本语言,用于管理和控制Windows操作系统和相关应用程序。那么要如何找到 PowerShell ?方法如下: 方法 描述 使用开始菜单 点击Wi…

    2024年1月30日
    23900
  • HBase 高可用集群详细图文安装部署

    一、HBase 安装部署 1.1 Zookeeper 正常部署 首先保证 Zookeeper 集群的正常部署,并启动之。 1.2 Hadoop 正常部署  Hadoop 集群的正常部署并启动。 1.3 HBase 安装  HBase 官方下载地址:Apache Download Mirrors 1.4 HBase 的配置文件  1.4.1 hbas…

    2023年12月15日
    22500

发表回复

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

在线咨询: QQ交谈

邮件:712342017@qq.com

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

关注微信