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

相关推荐

  • OpenSSH_9.3p1 升级到 OpenSSH_9.5p1

    文章声明:此文基于实操撰写 生产环境:此文升级是基于9.3p1升级9.5p1ssh -V查看版本是:OpenSSH_9.3p1, OpenSSL 1.1.1t 7 Feb 2023问题关键字:OpenSSH 升级, OpenSSH 更新, OpenSSH 漏洞修复漏洞信息:OpenSSH 命令注入漏洞(CVE-2020-15778)、OpenSSH 安全漏洞…

    2023年12月13日
    2.2K00
  • Centos7.6安装MySQL(超详细)

    1、查看是否已经安装Mysql rpm -qa | grep mysql 我已经安装过了 如果你查看出来有东西,可以使用下面命令将其删除 rpm -e 文件名 如果提示warning: waiting for transaction lock on /var/lib/rpm/.rpm.lock,则需要执行 2、下载官方Mysql包 wget -i -c ht…

    2022年6月9日
    2.1K00
  • Linux中监测磁盘IO性能

    Linux 存储系统 I/O 栈由文件系统层(file system layer)、通用块层( general block layer)和设备层(device layer)构成。 其中,通用块层是 Linux 磁盘 I/O 的核心。向上,它为访问文件系统和应用程序的块设备提供了标准接口;向下,它将各种异构磁盘设备抽象为一个统一的块设备,并响应文件系统和应用程…

    2022年12月1日
    1.7K00
  • Tcpdump 命令实际示例,Linux 下功能最强大的网络抓包工具

    tcpdump是最基本、最强大的命令行网络数据包嗅探工具。我们用它来分析服务器的网络数据包,捕获或过滤在特定网络接口上通过网络传输和接收的TCP/IP数据包。 它几乎适用于所有基于Linux/Unix操作系统的服务器。您还可以将捕获的数据包保存在文件中,以便以后使用tcpdump工具进行分析。它将文件保存为pcap格式,可通过tcpdump命令或基于GUI的…

    2023年8月7日
    1.8K00
  • Linux如何调整Swap空间大小

    查看当前Swap文件位置以及大小 要查看当前正在使用的swap文件或分区的位置,您可以使用swapon命令。 执行以下命令: 这将显示所有活动的swap空间,包括其类型(例如文件或分区)、大小、使用情况和位置。 如果您只对文件路径或分区路径感兴趣,您可以通过/proc/swaps文件查看相同的信息: 这将列出系统上所有的swap空间,包括它们的位置。 设置一…

    2023年11月6日
    1.7K00

发表回复

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

在线咨询: QQ交谈

邮件:712342017@qq.com

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

关注微信