【Linux】第二十三站:缓冲区

news2024/12/24 18:16:27

文章目录

  • 一、一些奇怪的现象
  • 二、用户级缓冲区
  • 三、用户级缓冲区刷新问题
  • 四、一些其他问题
    • 1.缓冲区刷新的时机
    • 2.为什么要有这个缓冲区
    • 3.这个缓冲区在哪里?
    • 4.这个FILE对象属于用户呢?还是操作系统呢?这个缓冲区,是不是用户级的缓冲区呢?
    • 5.为什么前面的图④中C语言系列接口打印了两次?

一、一些奇怪的现象

首先我们需要先注意这两个函数,即fwrite和fread函数

image-20231129192011392

注意这两个函数中

fread中,表示从stream流中读取size个单位的nmemb大小的数据放入ptr处。注意这里的返回值返回的是成功读取的个数,即与size是类似的

fwrite中,表示向stream流中写入size个单位的nmem大小的数据从ptr中。注意这里的返回值返回的是成功写入的个数,与size是类似的

而下面这个函数中

image-20231129192613897

它的意思是向fd文件描述符对应的文件中,写入buf位置的count个字节,这里的返回值返回的是写入成功字节的个数,与count是类似的

当我们的代码为如下的时候

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
    const char* fstr = "hello fwrite\n";
    const char* str = "hello write\n";
    
    //C语言
    printf("hello printf\n");  //stdout-->1
    fprintf(stdout,"hello fprintf\n");//stdout-->1
    fwrite(fstr,strlen(fstr),1,stdout);//stdout-->1
    //操作系统提供的systemcall
    write(1,str,strlen(str)); //1
    return 0;
}

最终运行结果为,我们将这个运行结果称作为①

image-20231129194141307

如果我们不变代码,而是在运行的时候加上重定向,那么最终运行结果为如下,我们将其称之为②

image-20231129194602070

如果我们将代码改为如下,即在最后加上一个fork

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
    const char* fstr = "hello fwrite\n";
    const char* str = "hello write\n";
    
    //C语言
    printf("hello printf\n");  //stdout-->1
    fprintf(stdout,"hello fprintf\n");//stdout-->1
    fwrite(fstr,strlen(fstr),1,stdout);//stdout-->1
   // close(1);
    //操作系统提供的systemcall
    write(1,str,strlen(str)); //1
    fork();
    return 0;
}

那么最终运行结果为如下,我们记作③

image-20231129194946113

同样是这段代码,我们对其做一个重定向,我们记作④

image-20231129195226031

最终如下图所示

image-20231129195812606

对于一二三而言,都是非常容易理解的,唯独二我们也许会好奇为什么系统调用接口会提前打印。

对于第四个,我们会发现,对于C语言的接口而言都打印了两遍,而对于系统调用的接口而言,是不受到这些影响的,依然只打印一次。虽然我们不知道为什么C语言的接口被打印了两遍,但是我们知道这个一定与fork有关

如果我们的代码是这样的

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
    const char* fstr = "hello fwrite\n";
   // const char* str = "hello write\n";
    
    //C语言
    printf("hello printf\n");  //stdout-->1
    fprintf(stdout,"hello fprintf\n");//stdout-->1
    fwrite(fstr,strlen(fstr),1,stdout);//stdout-->1
    close(1);
    //操作系统提供的systemcall
   // write(1,str,strlen(str)); //1
   // fork();
    return 0;
}

那么运行结果为

image-20231129203256910

如果我们的代码是这样的,将所有的\n都给去掉,

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
    const char* fstr = "hello fwrite";
   // const char* str = "hello write\n";
    
    //C语言
    printf("hello printf");  //stdout-->1
    fprintf(stdout,"hello fprintf");//stdout-->1
    fwrite(fstr,strlen(fstr),1,stdout);//stdout-->1
    close(1);
    //操作系统提供的systemcall
   // write(1,str,strlen(str)); //1
   // fork();
    return 0;
}

运行结果为,什么内容也没有了,我们将下图记作⑤

image-20231129203523217

如果我们紧接着将close给去掉了

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
    const char* fstr = " hello fwrite";
   // const char* str = "hello write\n";
    
    //C语言
    printf("hello printf");  //stdout-->1
    fprintf(stdout,"hello fprintf");//stdout-->1
    fwrite(fstr,strlen(fstr),1,stdout);//stdout-->1
   //close(1);
    //操作系统提供的systemcall
   // write(1,str,strlen(str)); //1
   // fork();
    return 0;
}

运行结果为如下,我们可以看到已经有东西打印出来了

image-20231129203733405

如果我们将代码改为下面的

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
    //const char* fstr = " hello fwrite";
    const char* str = "hello write";
    
    //C语言
  //  printf("hello printf");  //stdout-->1
  //  fprintf(stdout,"hello fprintf");//stdout-->1
  //  fwrite(fstr,strlen(fstr),1,stdout);//stdout-->1
   
    write(1,str,strlen(str)); //1
    close(1);
    //操作系统提供的systemcall
   // fork();
    return 0;
}

那么最终运行结果为如下,我们可以看到打印出来东西了,我们将下面的记作⑥

image-20231129204051641

所以最终情况汇总如下

image-20231129204257545

我们发现上面了很多的奇怪的现象了

二、用户级缓冲区

我们知道像printf/fprintf/fwrite/fputs/…这些C式的接口,他们最终的底层一定是调用write这个系统调用的,但是为什么他们之前出现了上面很多奇怪的现象呢?

而我们知道这些C式的接口一定会将数据写入缓冲区的

所以说这个缓冲区一定不在操作系统内部!!!不是系统级别的缓冲区!

因为一旦,我们将这些数据写入到对应的操作系统中,如下图所示

image-20231129205628436

它在close的时候就一定可以找到对应的文件,比如我们关闭的是1号文件。然后将对应的数据刷新到磁盘中。直接就可以刷新了。它应该是可以看到结果的,可是根据我们的第⑤号运行结果中可以看出来,它是没有看到的,所以一定是不在操作系统内部的。

而第六图中,write可以看到,是因为它直接写到了系统级别的缓冲区,当close的时候,就会刷新系统级别的缓冲区。

所以说,我们所说的缓冲区都是语言层的。

image-20231129211436897

所以说,我们的数据写入的时候,都会先写入到这个C语言层面的缓冲区,随后在通过write写入到系统级别的缓冲区

如下所示才是正确的缓冲区流向

image-20231129211708156

所以这样就解释了前面的五和六两张图的情况

就是因为printf/fprintf这些函数会去使用C提供的一个语言层面的缓冲区,然数据都写到这里来了,而我们最后直接close的时候只能刷新内核级别的缓冲区,所以最终什么结果也没有。而前面的write些往内核里面去写入的,所以最终close的时候会刷新里面的缓冲区,从而使得数据打印出来

所以最终就解释了下面的这个现象

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
    const char* fstr = " hello fwrite";
    const char* str = "hello write";
    
    //C语言
    printf("hello printf");  //stdout-->1
    fprintf(stdout,"hello fprintf");//stdout-->1
    fwrite(fstr,strlen(fstr),1,stdout);//stdout-->1
   
    write(1,str,strlen(str)); //1
    close(1);
    //操作系统提供的systemcall
   // fork();
    return 0;
}

最终运行结果为

image-20231129212353646

就是因为,前面的三个接口都是写入到了C语言层面上的缓冲区了,并没有写入到内核级别的缓冲区,而write是会写入到内核级别的缓冲区的,而close只会刷新内核级别的缓冲区,而关闭以后,原先在C语言级别缓冲区的数据由于1号文件被关闭了,而我们在程序结束的时候确实是会刷新C语言级别的缓冲区,不过在刷新这个缓冲区的时候,wirte要利用1号文件写入到内核中。可是此时1号文件已经被关闭了,所以无法写入。

而我们知道显示器的文件的刷新方案是行刷新,所以在printf执行完,就会立即遇到\n,将数据进行刷新

所以刷新的本质就是将数据通过1+write写入到内核中

而上面的C语言层面的缓冲区就是用户级缓冲区

我们还记得之前的_exit和exit其实是有区别的

exit是c语言的接口,它能看到这个用户级别缓冲区,可以对其进行刷新(fflush(stdout)),然后调用_exit进程退出

而_exit是一个系统调用,看不到这个用户级缓冲区,它就直接关闭了程序了。

目前我们可以认为,只要将数据刷新到了内核,数据就可以到硬件了

所以说,上层所谓的fflush等等,都是通过调用write将数据写入到缓冲区中。

三、用户级缓冲区刷新问题

  1. 无缓冲:直接刷新
  2. 行缓冲:不刷新,直到遇到\n才刷新-------比如显示器
  3. 全缓冲:缓冲区满了,才刷新-----------比如文件写入到普通文件的时候

所以fprintf/fwrite这些接口最后写入到的都是C缓冲区,然后根据一定的刷新策略调用write接口,最后写到操作系统中

image-20231130094018559

有了上面的理解,我们就可以直到,像fflush接口中,里面绝对使用了write函数

image-20231130094419721

因为只有write才可以将数据写入到操作系统内核中。

四、一些其他问题

1.缓冲区刷新的时机

在行缓冲的时候,就比如像显示器写入的时候策略就是行缓冲

对于全缓冲,就比如往普通文件写入时候就是全缓冲

还有一种缓冲区刷新的时机是进程退出的时候

image-20231130094859162

虽然我们没有刷新但是,此时是进程退出,会自动刷新C语言缓冲区

image-20231130095344614

但是要注意,如果我们在退出之前,关闭了1号文件,那么就无法完成刷新了。因为write接口无法写入到1号文件了

2.为什么要有这个缓冲区

  1. 解决效率问题-----用户的效率问题(我们只需要将数据移交给缓冲区即可,至于如何放到操作系统里面,不需要我们去自己完成,由缓冲区去完成。类似于我们将包裹交给快递公司。但是最后快递公司一般不会只有一件包裹就去递送,而是有了很多件包裹以后,或者今天要关门了,会去统一的配送,这就类似于缓冲区的刷新)
  2. 配合格式化(我们之前的printf函数中,我们打印的时候需要先将数据给格式化再写入缓冲区中,比如printf(“hello %d”,123)这行代码中,我们写入到缓冲区的时候已经变为了"hello 123")

这个缓冲区,我们一般把他叫做文件流,在C++中也是叫做文件流。

因为我们之前的fprintf/fwrite这些接口会将数据源源不断的写入到缓冲区中。然后这些缓冲区的数据会源源不断的刷新到操作系统中。

而这个过程就是源源不断的往里面写,然后源源不断的删除。

有进有出。所以就有了流的概念,就是文件流。

3.这个缓冲区在哪里?

我们直到,像fflush,fprintf这些接口始终绕不开FILE这个东西

image-20231130101934024

image-20231130102004366

而FILE是一个结构体,而它里面必须要封装fd

FILE里面其实还有对应打开文件的缓冲区字段和维护信息

所以说,其实,这个这个所谓的stdout,就是将hello world给放到stdout所对应文件中的缓冲区中,随后便会通过write写入到操作系统中

image-20231130102431007

所以这个缓冲区就在FILE里面

就比如我们现在打开了10个文件,就有10个缓冲区了。每个文件都有对应的缓冲区

所以每一个文件都会通过它的自己的缓冲区,刷新到其对应的文件上。

4.这个FILE对象属于用户呢?还是操作系统呢?这个缓冲区,是不是用户级的缓冲区呢?

这个FILE对象一定属于用户。语言都属于用户层。

所以这个缓冲区,就是用户级缓冲区

所以我们就知道了,下面这个函数返回的为很么要是FILE*了

image-20231130103324137

fopen是libc.so库中提供的接口。

这个fopen在底层会去调用open系统调用,帮我们建立内核级别的文件对象。然后拿到文件描述符,在语言层给我们malloc(FILE),所以返回的是FILE*

5.为什么前面的图④中C语言系列接口打印了两次?

即在下面代码中,出现了下面的情况

#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
    const char* fstr = "hello fwrite\n";
    const char* str = "hello write\n";
    
    //C语言
    printf("hello printf\n");  //stdout-->1
    fprintf(stdout,"hello fprintf\n");//stdout-->1
    fwrite(fstr,strlen(fstr),1,stdout);//stdout-->1
   
    write(1,str,strlen(str)); //1
    //操作系统提供的systemcall
     fork();
    return 0;
}

对于没有重定向的,我们很清楚打印流程

image-20231130105707411

而我们一旦使用重定向,那么缓冲方案变为了全缓冲,因为全缓冲是在普通文件写入的时候才使用的。

也就是说,遇到\n不在刷新了,而是等缓冲区被写满才刷新

所以我们前面用C接口写的这三行数据也写不满缓冲区,所以会留在缓冲区里面。因为缓冲区并没有被刷新

而最后的write系统调用就会直接写入到操作系统中,故而会先打印出来。

所以截止至此,已经解释了为什么前面的二号图中,顺序会出现问题的现象,因为最后进程退出的时候,会强制刷新。

我们可以用这段代码,来验证一下结果

image-20231130110913183

运行结果为如下

image-20231130111809720

而我们后面将代码加上一个fork以后。

而我们此时,write接口已经写入了。那些C语言系列的接口的数据还在缓冲区中,此时我们fork以后,会创建子进程,共享代码,然后数据以写时拷贝的方式存在。

而我们这个缓冲区就是用户级缓冲区,是我们FILE的一部分,也就是说,就相当于在堆里面的一个数据。而父进程在想要刷新的时候,其实本质就是清空缓冲区,也就是要修改。所以此时会发生写时拷贝,父子进程就对这段缓冲区各自拥有了一份。所以我们最终就发现了这段数据被刷新了两份。

所以我们就看到了C接口被刷新了两次。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1274204.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux实现类似cp的命令

1.利用主函数的三个函数进行传参 1).主函数的三个参数的含义: argc:主函数的参数个数 argv:主函数的参数内容 envp:环境变量; 2).演示代码: #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc,char *argv[],char *envp[]…

Vue3.x 中 hooks 函数封装和使用

一、hooks 是什么 vue3 中的 hooks 就是函数的一种写法&#xff0c;就是将文件的一些单独功能的 js 代码进行抽离出来进行封装使用。 它的主要作用是 Vue3 借鉴了 React 的一种机制&#xff0c;用于在函数组件中共享状态逻辑和副作用&#xff0c;从而实现代码的可复用性。 注…

[笔记]dubbo发送接收

个人笔记 consumer 主要使用ThreadlessExecutor实现全consumer的全双工通讯。consumer创建本次请求的requestId用于将response和request匹配。 然后分以下几步完成一次请求发送并接收结果&#xff1a; 槽&#xff1a;发送消息前将用于接收结果的executor放到一个map中存储 发…

AI落地现状:没有mission、业务零碎、连2B还是2C都在摇摆

最近我私下问某TOP AI 2.0公司的核心产品负责人&#xff0c;你们现在主要是2C还是2B&#xff1f; 他说的第一句话是&#xff0c;“整体没有misson”。 结合近期的各种信息&#xff0c;给大家说下行业最前沿的内幕。 1、据说&#xff0c;某1、2位大佬的AI认知&#xff0c;其实并…

最大公约数的C语言实现xdoj31

时间限制: 1 S 内存限制: 1000 Kb 问题描述: 最大公约数&#xff08;GCD&#xff09;指某几个整数共有因子中最大的一个&#xff0c;最大公约数具有如下性质&#xff0c; gcd(a,0)a gcd(a,1)1 因此当两个数中有一个为0时&#xff0c;gcd是不为0的那个整数&#xff…

Linux的dev/vda1文件满了导致MySQL无法写入

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、dev/vda1文件介绍 二、排查过程 三、总结 前言 今天查看两个月前上线的小项目&#xff0c;发现运行非常慢&#xff0c;而且增…

正则表达式及文本三剑客grep sed awk

正则表达式 1.元字符 . //匹配任意单个字符&#xff0c;可以是个汉字 [yang] //匹配范围内的任意单个字符 [^y] //匹配处理指定范围外的任意单个字符 [:alnum:] //字母和数字 [:alpha:] //代表…

周报:css相关扩展知识

目录 1. 扩展知识&#xff1a;浮动盒子的排列位置 浮动盒子常见排列特点&#xff1a; 浮动盒子扩展特点&#xff1a; 2.扩展知识:行高的取值 line-height常见取值&#xff1a; 行高的取值的方式&#xff1a; 两个方式的区别&#xff1a; 3.扩展知识&#xff1a;body背景…

无公网IP环境如何远程访问本地内网搭建的Emby影音库服务器

文章目录 1.前言2. Emby网站搭建2.1. Emby下载和安装2.2 Emby网页测试 3. 本地网页发布3.1 注册并安装cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar内网穿透本地设置 4.公网访问测试5.结语 1.前言 在现代五花八门的网络应用场景中&#xff0c;观看视频绝对是主力应用场景之一&…

kubeadm快速搭建k8s高可用集群

1.安装及优化 1.1基本环境配置 1.环境介绍 &#xff08;1&#xff09;.高可用集群规划 主机名ip地址说明k8s-master01192.168.2.96master节点k8s-master02192.168.2.97master节点k8s-master03192.168.2.98master节点k8s-node01192.168.2.99node节点k8s-node02192.168.2.100n…

uc_12_进程间通信IPC_有名管道_无名管道

1 内存壁垒 进程间天然存在内存壁垒&#xff0c;无法通过交换虚拟地址直接进行数据交换&#xff1a; 每个进程的用户空间都是0~3G-1&#xff08;32位系统&#xff09;&#xff0c;但它们所对应的物理内存却是各自独立的。系统为每个进程的用户空间维护一张专属于该进程的内存映…

JavaWeb服务器详解和后端分层解耦

JavaWeb HTTP协议请求数据格式响应数据格式协议解析 Web服务器请求响应请求参数的接收响应 分层解耦IOC&DI入门IOC详解 HTTP协议 超文本传输协议&#xff0c;规定了浏览器和服务器之间数据传输的规则 特点&#xff1a; 基于TCP协议&#xff1a;面向连接&#xff0c;安全 …

第三方实验室LIMS管理系统源码,asp.net LIMS源码

LIMS实验室信息管理系统源码 LIMS系统的功能根据实验室的规模和任务而有所不同&#xff0c;其系统主要功能包括:系统维护、基础数据编码管理&#xff0c;样品管理、数据管理、报告管理、报表打印、实验材料管理、设备管理等。它可以取代传统的手工管理模式而给检测实验室带来巨…

aspera传输方案怎么样,需要选择aspera替代方案吗

Aspera传输方案是一种高速、可靠的文件传输解决方案&#xff0c;适用于需要大规模传输大文件或数据集的企业和组织。Aspera采用UDP协议及自己开发的FASP协议进行加速传输&#xff0c;能够在高延迟、高丢包网络环境下实现稳定快速的传输。 Aspera传输方案具有以下优点&#xff1…

【【带Micro Blaze的 AXI GPIO 控制LED实验】】

带Micro Blaze的 AXI GPIO 控制LED实验 AXI GPIO IP 核为 AXI 接口提供了一个通用的输入/输出接口。AXI GPIO 是一个软核&#xff08;Soft IP&#xff09;&#xff0c;是由用户通过配置芯片的逻辑资源来实现的一个功能模块。 实验任务 &#xff1a; 本章的实验任务是通过调用…

颠覆性语音识别:单词级时间戳和说话人分离

vbenjs/vue-vben-admin[1] Stars: 19.7k License: MIT Vue Vben Admin 是一个免费开源的中后台模板&#xff0c;使用最新的 vue3、vite4 和 TypeScript 等主流技术进行开发。该项目提供了现成的中后台前端解决方案&#xff0c;并可用于学习参考。 使用先进的前端技术如 Vue3/…

11 款顶级的免费 iPhone 数据恢复软件

iPhone 拥有巨大的存储容量。您可以在 iPhone 设备上存储图像、文档和视频等数据。有时&#xff0c;您的 iPhone 会发生许多意外事件&#xff0c;例如意外删除&#xff0c;从而导致数据丢失。这里有 11 个最好的免费 iPhone 数据恢复软件&#xff0c;您可以免费下载&#xff0c…

基于社区电商的Redis缓存架构-用户分享内容的分页列表缓存延迟构建以及异步通知缓存重建

分页列表缓存的延迟构建 首先&#xff0c;先来讲一下业务场景&#xff0c;用户会在 APP 中去分享内容&#xff0c;那么假如用户分享的是美食菜谱内容&#xff0c;在用户分享之后&#xff0c;先将这个美食菜谱的内容作为 k-v 进行缓存&#xff0c;但是呢&#xff0c;其实对于用…

Microsoft Remote Desktop高效、安全、稳定的远程办公解决方案

在今天的数字化时代&#xff0c;Remote Desktop远程办公已成为许多人的日常生活。无论你是因为工作需要&#xff0c;还是因为在家中需要访问公司服务器&#xff0c;微软远程连接软件都是一个理想的选择。 微软远程连接软件Remote Desktop是一款高效、安全、稳定的远程办公解决…

Clickhouse Join

ClickHouse中的Hash Join, Parallel Hash Join, Grace Hash Join https://www.cnblogs.com/abclife/p/17579883.html https://clickhouse.com/blog/clickhouse-fully-supports-joins-full-sort-partial-merge-part3 总结 本文描述并比较了ClickHouse中基于内存哈希表的3种连接…