【Linux】缓冲区

目录

一、初识缓冲区

二、用户级缓冲区

三、内核级缓冲区

四、内核级缓冲区 VS 用户级缓冲区

 五、用户级缓冲区在哪里?


一、初识缓冲区

缓冲区是什么?可以简单理解成一部分内存。例如用户缓冲区(char arr[])、C标准库提供的缓冲区、操作系统提供的缓冲区。

为什么要有缓冲区?要通过减少对磁盘的直接访问来提高文件操作的效率。例如给朋友寄快递,不是直接给朋友送过去,而是通过快递站,快递站也不是来一个包裹就送一个包裹,而是积累一部分包裹再统一发送,朋友只用到他对应的快递站取走包裹即可。提高了使用者的效率和发送的效率。

缓冲区能暂存数据,因此也要有一定的刷新方式。(一般策略)

  1. 无缓冲(立即刷新)
  2. 行缓冲(行刷新)
  3. 全缓冲(缓冲区满了,再刷新)
  4. 一般对于显示器文件,行刷新(行缓冲) ;对于磁盘上的文件,全缓冲(缓冲写满,在刷新)

特殊情况:

  1. 强制刷新 
  2. 进程退出的时候,一般要自动刷新缓冲区,即便数据没有满足刷新条件。

强制刷新缓冲区,把当前缓冲区的内容全部刷新。类似于寄快递时对工作人员说:赶快给我发出去,不然就投诉你。这时工作人员就会把驿站当前积累的包裹都发送出去。行刷新,就是把驿站快递柜的一行快递发送。全刷新,就是驿站快递柜满了,把积累的全部包裹发送出去。

看下面一段代码思考结果如何,输出重定向到文件中,结果一样吗?

#include <stdio.h>    
#include <string.h>    
#include <unistd.h>    
    
int main()    
{    
    printf("C: hello printf\n");    
    fprintf(stdout, "C: hello fprintf\n");    
    fputs("C: hello fputs\n",stdout);                                                                            
    const char* str = "system call: hello write\n";    
    write(1,str,strlen(str));    
    
    fork();    
    return 0;    
}  

现象:直接执行程序时向显示器打印,C标准库和系统调用的函数都打印一次,但输出重定向到 log.txt 文件时,C标准库提供的函数打印两次,系统调用的函数只打印一次。

原因:

  1. 当我们直接向显示器打印的时候,显示器文件的刷新方式是行刷新!而且代码输出的所有字符串,都有'\n'。fork之前,数据全部已经被刷新,包括systemcall。
  2. 重定向到 log.txt ,本质是向磁盘文件中写入(不是显示器),系统对于数据的刷新方式已经由行缓冲,变成了全缓冲!
  3. 变成全缓冲意味着缓冲区变大,实际写入的简单数据,不足以把缓冲区写满,fork执行的时候数据依旧在缓冲区中!
  4. 我们目前所谈的"缓冲区",和操作系统是没有关系的,只能和C语言有关。(因为系统调用的结果打印正常符合预期,且C 库中的printf、fprintf、fputs实际上还是封装的wirte,如果是因为write引起的,write函数也会打印两次)

二、用户级缓冲区

我们日常用的最多的其实是C/C++标准库提供的语言级别的缓冲区,也叫用户级缓冲区。而操作系统提供的是内核级缓冲区。
在上面的例子中,我们这边的快递站就是C语言提供的用户级缓冲区,朋友就是操作系统,朋友那边的快递站就是内核级缓冲区。

上面代码原理的补充:

  • C/C++标准库提供的缓冲区,里面一定保存的是用户的数据,属于当前进程在运行时的数据。
  • 如果把用户的数据交给操作系统,那么这个数据不属于用户,属于操作系统。
  • 进程退出刷新缓冲区时,要进行刷新缓冲区,这也属于 "清空" 或 "写入" 操作。
  • C/C++缓冲区的数据属于进程的数据、fork后父子进程共享缓冲区数据,当要写入或者任一进程退出时,要刷新缓冲区,此时会发生 "写时拷贝"。
  • 可以证明write系统调用没有使用C的缓冲区,直接写入到操作系统。这个数据不属于进程。

什么叫做刷新?

一个进程有对应的文件描述符表,例如有一个打开的log.txt文件,它加载到内存中,对应一个struct file,文件描述符为3,对应一个文件缓冲区。磁盘上也有一个对应的log.txt。

用户要把"hello"字符串打印到log.txt文件,该字符串是经过fprintf等函数写入到C语言提供的缓冲区(例如名叫buffer),经过fflush或进程退出时,调用write(3,buffer)写入到文件缓冲区中。

把用户的缓冲区(例如字符数组)拷贝到C语言的缓冲区,再把C语言提供的缓冲区的内容拷贝到文件缓冲区,这个过程叫做刷新。

补充:

  • fprintf等函数不仅将打印内容拷贝到缓冲区中,还做了格式化输出操作。这个格式化输出操作是在拷贝时完成的。我们之前用的所有缓冲区都是C语言提供的用户级缓冲区。
  • 直接把数据从用户的缓冲区拷贝到文件缓冲区,成本很高,刷新操作可以提高效率
  • C语言中几乎所有的I/O接口都有缓冲区。
  • 文件缓冲区中的数据需要写入文件系统时,数据再被写入到文件系统的磁盘块中。这也是为了提高效率(减少磁盘访问)。(该操作由OS执行)

经过上面的分析,就可以更好的理解用户级缓冲区的特点:

  • 用户级缓冲区通常位于用户空间,由进程自己管理。
  • 用户级缓冲区的主要目的是提高文件操作的效率,减少系统调用的次数。
    例如,当写入文件时,数据首先被写入用户级缓冲区,然后写入内核级缓冲区,最后写入文件系统。
  • 用户级缓冲区可以被刷新,使用 'fflush' 函数可以手动刷新缓冲区,确保所有数据都被写入文件。
  • C标准库(如stdio.h)提供了文件操作的函数,如 'fopen'、'fread'、'fwrite' 等。这些函数可以在打开文件时指定缓冲区类型,如 '"w+b"'。

以下是C语言提供的几种用户级缓冲区类型:

  • 无缓冲区(无缓冲):使用 '"wb"' 或 '"rb"' 模式打开文件时,文件没有缓冲区。每次调用写入或读取函数时,数据都会立即写入或从文件中读取。
  • 行缓冲区(行缓冲):使用 '"w+b"' 或 '"r+b"' 模式打开文件时,文件有行缓冲区。每写入或读取一行数据时,数据会被缓冲,直到缓冲区满或遇到换行符。
  • 全缓冲区(全缓冲):使用 '"w+"' 或 '"r+"' 模式打开文件时,文件有全缓冲区。写入或读取的数据会被缓冲,直到缓冲区满或调用 'fflush' 函数。 

三、内核级缓冲区

上面讲到的文件缓冲区就是操作系统的内核级缓冲区
内核缓冲区通常位于内核空间,由操作系统直接管理。
标准输出(stdout)和标准错误(stderr)通常使用内核级缓冲区。
内核级缓冲区通常在进程结束时自动刷新,但在某些情况下,可以通过调用 'fflush' 函数来手动刷新这些缓冲区。
内核级缓冲区的主要目的是减少对物理设备的直接访问,提高系统性能。

用户级缓冲区和内核级缓冲区可以同时使用,以提高程序的性能。例如,当读取文件时,数据首先被读取到用户级缓冲区中,然后从用户级缓冲区读取到程序的内存中。当写入文件时,数据首先被写入程序的内存中,然后写入用户级缓冲区,最后写入内核级缓冲区,最终写入文件系统。

四、内核级缓冲区 VS 用户级缓冲区

  • 作用:内核级缓冲区主要用于减少对物理设备的直接访问,提高系统性能;而用户级缓冲区主要用于提高文件操作的效率,减少系统调用的次数。
  • 位置:内核级缓冲区位于内核空间,由操作系统管理;用户级缓冲区位于用户空间,由程序自己管理。
  • 管理:内核级缓冲区由内核直接管理,对用户不可见;用户级缓冲区由程序员控制,可以通过编程方式进行管理。
  • 性能:内核级缓冲区可以显著减少磁盘I/O操作,提高系统性能;用户级缓冲区可以减少系统调用的次数,提高程序的执行效率。

缓冲区在Linux中的主要作用:

  1. 减少磁盘访问:缓冲区允许数据在内存中暂存,而不是每次操作都直接从磁盘读取或写入。这减少了磁盘I/O操作的次数,从而提高了系统的整体性能。
  2. 优化内存使用:缓冲区可以存储大量数据,减少了对物理内存的需求。数据在内存中缓存,可以在需要时快速访问,而不是每次都从磁盘加载。
  3. 减少系统调用次数:缓冲区允许在多个操作之间缓存数据,减少了与文件系统交互的系统调用次数。这减少了内核和用户空间之间的上下文切换,提高了程序的执行效率。
  4. 提高文件操作的效率:缓冲区可以减少对文件的频繁读写,从而提高了文件操作的效率。例如,当编辑一个大型文件时,文本编辑器可以在缓冲区中修改数据,而不是每次修改都写入文件。
  5. 减少延迟:缓冲区可以减少数据传输的延迟,因为数据可以快速地在内存中传递。这使得程序可以更快速地处理数据,尤其是在处理大量数据时。
  6. 支持并行操作:缓冲区可以支持并行操作,例如,多个进程可以同时访问同一个缓冲区。这使得系统可以更有效地利用多核处理器和多进程架构。

 五、用户级缓冲区在哪里?

任何情况下,我们输出输入的时候,都要有一个FILE,FILE是一个结构体,FILE里面包含了fd,也提供了一段缓冲区!因此任何一个文件都要在C标准库里通过FILE创建一个用户级缓冲区。
例如进程打开了5个文件,就有5个FILE对象和struct file,对应5个文件缓冲区,读写时互相不影响。

可以看看C标准库中的FILE结构体:
typedef struct _IO_FILE FILE;  在   /usr/include/stdio.h

在/usr/include/libio.h
struct _IO_FILE {
 int _flags;     /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
 
 //缓冲区相关
 /* The following pointers correspond to the C++ streambuf protocol. */
 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
 char* _IO_read_ptr;     /* Current read pointer */
 char* _IO_read_end;     /* End of get area. */
 char* _IO_read_base;     /* Start of putback+get area. */
 char* _IO_write_base;     /* Start of put area. */
char* _IO_write_ptr;     /* Current put pointer. */
 char* _IO_write_end;     /* End of put area. */
 char* _IO_buf_base;     /* Start of reserve area. */
 char* _IO_buf_end;     /* End of reserve area. */
 /* The following fields are used to support backing up and undo. */
 char *_IO_save_base; /* Pointer to start of non-current get area. */
 char *_IO_backup_base; /* Pointer to first valid character of backup area */
 char *_IO_save_end; /* Pointer to end of non-current get area. */
 
 struct _IO_marker *_markers;

 struct _IO_FILE *_chain;
 
 int _fileno; //封装的文件描述符

#if 0
 int _blksize;
#else
 int _flags2;
#endif

 _IO_off_t _old_offset; /* This used to be _offset but it's too small. */

#define __HAVE_COLUMN /* temporary */
 /* 1+column number of pbase(); 0 is unknown. */
 unsigned short _cur_column;
 signed char _vtable_offset;
 char _shortbuf[1];

 /* char* _save_gptr; char* _save_egptr; */

 _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

向显示器打印123的时候,打印的其实是1字符2字符3字符,我们用键盘输入123时,也是输入1字符2字符3字符,这3个字符被放入到stdin对应FILE结构体中的缓冲区。scanf将缓冲区的数据合并,根据用户定义的格式赋给指定的值。键盘显示器也叫字符设备。

C语言把打开的文件叫做流,输入流输出流,流也相当于C语言提供的缓冲区,一方面C语言从缓冲区的头部拿数据,一方面用户在缓冲区尾部追加数据,该操作像流水一样,所以称作‘流’。C++也是同理,也有自己的缓冲区。原理和C相同,但操作上有很大区别。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Python】图形用户界面设计

1、设计并编写一个窗口程序,该窗口只有一个按钮,当用户单击时可在后台输出hello world. import tkinter as tk def on_button_click():print("hello world") # 创建主窗口 root tk.Tk() root.title("Hello World Button") # 设置窗口大小 root.geometry…

Vue的学习 —— <网络请求库Axios>

目录 前言 正文 一、Axios基本概念 二、安装Axios 三、Axios使用方法 四、向服务器发送请求 前言 在之前的开发案例中&#xff0c;我们通常直接在组件中定义数据。但在实际的项目开发中&#xff0c;我们需要从服务器获取数据。当其他用户希望访问我们自己编写的网页时&a…

区块链数据集(一)Xblock

一、Transaction Datasets Ethereum On-chain Data [Dataset] 2021-10TransactionData/Code AvailableEthereum Introduction: This is the dataset of paper “XBlock-ETH: Extracting and Exploring Blockchain Data From Ethereum”. Data / Code Paper CiteDownloads: …

vue3 自定义组件

在项目中&#xff0c;我们会遇到一些没有现成的组件&#xff0c;那这个时候我们就需要自己去写一个满足我们需求的组件。 比如&#xff0c;我需要一个上下排布&#xff0c;上面显示标题&#xff0c;下面显示内容的组件。封装完成后方便复用。 1、布局组件 我定义一个上下结构的…

meshlab: pymeshlab沿着椭圆赤道投影展开当前网格的几何图形并保存(geometric cylindrical unwrapping)

一、关于环境 请参考&#xff1a;pymeshlab遍历文件夹中模型、缩放并导出指定格式-CSDN博客 二、关于代码 本文所给出代码仅为参考&#xff0c;禁止转载和引用&#xff0c;仅供个人学习。 本文所给出的例子是https://download.csdn.net/download/weixin_42605076/89233917中的…

51单片机入门:I2C通讯协议

I2C通讯协议 I2C简介 串口通信只能在两个设备之间进行&#xff0c;如果是三个设备相互通讯&#xff0c;那么每个设备需要两组串口&#xff0c;实际上是3组相互独立的串口通信。如果是4个设备相互通信就更加麻烦了&#xff0c;最突出的问题就是线路连接比较复杂。 为了解决这个…

现代加密技术(对称和非对称加密)

1.分类 现代加密技术&#xff1a;对称和非对称加密&#xff0c;对称加密即共享密钥&#xff0c;非对称加密是公钥加密算法。 2.基础总结 AES是什么算法&#xff1f; 分组加密算法&#xff0c;对称加密算法AES的分组长度是&#xff1f;固定128位AES密钥长度是多少&#xff1f;支…

Shell编程之数组

一.数组定义方法 1.数组名称&#xff08;数值1 数值2 数值3 数值4 数值5&#xff09;数组名称&#xff08;"字符串1" "字符串2" "字符串4" "字符串5" "字符串6"&#xff09;(或者使用单引号) 如何查看数组的元素&#xff1…

java代码混淆工具ProGuard混淆插件

java代码混淆工具ProGuard混淆插件 介绍 ProGuard是一个纯java编写的混淆工具&#xff0c;有客户端跟jar包两种使用方式。可以将程序打包为jar&#xff0c;然后用工具进行混淆&#xff0c;也可以在maven中导入ProGuard的插件&#xff0c;对代码进行混淆。 大家都知道 java代…

ADS Momentum 仿真设置

1、选择Momenttum Microwave。 2、Layout不需要操作。 3、Partitioning 不需要操作。 4、没有叠层的话需要新建叠层&#xff0c;过孔可以在叠层中右键添加。 5、注意确认端口的Gnd Layer。 6、设置仿真频率。 7、Output Plan。 8、Option。 最后运行仿真&#xff0c;等待结果即…

C++ static_cast学习

static_cast可实现&#xff0c; 1 基本类型之间的转换 2 void指针转换为任意基本类型的指针 3 用于有继承关系的子类与父类之间的指针或引用的转换 用于基本类型转化时&#xff0c;会损失精度类似于C语言的强制转化&#xff1b; 下面先看一下void指针的转换&#xff1b; …

49-Qt控件详解:ltemViewsltemWidgets

1.List View:清单视图 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QListView> //字符串列表模型 #include <QStringListModel> #include <QMessageBox> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEcl…

如何在Sui智能合约中验证是否为多签地址

通过多签合约实现多个用户可访问的安全账户。多签&#xff08;multi-sig&#xff09;钱包和账户通过允许多个用户在预定义条件下访问共享资产&#xff0c;或让单个用户实施额外的安全措施&#xff0c;从而增强密钥管理。例如&#xff0c;多签钱包可以用于管理去中心化自治组织&…

码农慎入 | 入坑软路由,退烧IDC,Homelab折腾记

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 卷圈 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 场地支持 / 声湃轩北京录音间 俗话说&#xff0c;入门软路由&#xff0c;退坑IDC 这一期&#xff0c;我们将深入探讨一个许多科技爱好者…

【介绍下JSON,JSON是什么?】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

Folder Icons for Mac v1.9激活版:自定义文件夹图标

在追求个性和品味的今天&#xff0c;Folder Icons for Mac 让您的Mac桌面焕然一新。支持多种格式的图片和图标文件&#xff0c;满足您不同的审美需求。同时&#xff0c;软件提供丰富的图标库和模板&#xff0c;让您在定制文件夹图标时更加得心应手。Folder Icons for Mac 不仅能…

【React】如何让函数式组件也能使用state——useState(Hooks)

React的函数式组件不同于类式组件&#xff0c;函数式组件没有自己的 this&#xff0c;看似没有操作state的能力 但是React官方提供了一个Hooks叫useState&#xff0c;它解决了函数式组件和类式组件的差异&#xff0c;让函数式组件拥有了类式组件所拥有的 state &#xff0c;同时…

Golang | Leetcode Golang题解之第92题反转链表II

题目&#xff1a; 题解&#xff1a; func reverseBetween(head *ListNode, left, right int) *ListNode {// 设置 dummyNode 是这一类问题的一般做法dummyNode : &ListNode{Val: -1}dummyNode.Next headpre : dummyNodefor i : 0; i < left-1; i {pre pre.Next}cur :…

vue2人力资源项目9权限管理

页面搭建 <template><div class"container"><div class"app-container"><el-button size"mini" type"primary">添加权限</el-button><el-table-column label"名称" /><el-table-co…

DS高阶:跳表

一、skiplist 1.1 skiplist的概念 skiplist本质上也是一种查找结构&#xff0c;用于解决算法中的查找问题&#xff0c;跟平衡搜索树和哈希表的价值是一样的&#xff0c;可以作为key或者key/value的查找模型。skiplist是由William Pugh发明的&#xff0c;最早出现于他在1990年发…