Linux系统:虚拟文件系统与文件缓冲区(语言级内核级)

本节重点

  • 初步理解一切皆文件
  • 理解文件缓冲区的分类
  • 用户级文件缓冲区与内核级文件缓冲区
  • 用户级文件缓冲区的刷新机制
  • 两级缓冲区的分层协作

一、虚拟文件系统

1.1 理解“一切皆文件”

我们都知道操作系统访问不同的外部设备(显示器、磁盘、键盘、鼠标、网卡)时都会通过相应的驱动程序,由于各种外设之间的差异在驱动程序中对每个外设的输入输出(如获取设备状态、属性)的相关方法的实现都不尽相同:

 我们说操作系统是对软硬件资源进行管理的软件,在内核中要对硬件资源进行管理首先需要让操作系统看到硬件资源,也就是将硬件资源“先描述再组织”:

在操作系统内核中通过类似struct device的结构体来对每种外设进行描述,再通过链表的方式将硬件资源管理起来,此时这个数据结构就表示操作系统启动时默认看到的和打开的外设资源:

当用户运行自己的代码与数据时,操作系统就会在内核空间创建进程PCB,其中PCB中的struct files struct指针指向的文件描述符表则记录了该进程打开的文件的数量。而我们知道文件描述符表本质上是元素为struct file的一维数组,struct file中则详细记录了被打开文件的文件缓冲区和元数据,关键的是其中还记录了指向文件操作的各种方法的指针(函数指针),这样对文件的操作会通过函数指针跳转到不同的对应外设的驱动层。

这样即使外设之间存在差异,驱动程序的设计大相径庭用户访问涉及到不同类型外设的文件时也能获得相似的方法,以为在内核通过函数指针已经帮用户完成了差异化的方法调用。

二、文件缓冲区

2.1 什么是缓冲区

缓冲区是内存空间的一部分,用来暂时存储输入或者输出的数据内容,这部分预留的空间就叫做缓冲区。缓冲区根据其对接的是输入设备还是输出设备分为输入缓冲区与输出缓冲区。

2.2 为什么引入缓冲区 

关键1:语言级文件操作都会调用系统调用

在介绍操作系统时我们了解到:操作系统为了不直接暴露内核,为上层用户提供了各类系统调用。在语言层面对文件操作的各类函数接口底层都封装了系统调用。

例如,以C语言为例fopen,fread,fwrite底层都分别封装了open,read,write系统调用。

所以本质上我们使用各类编程语言进行文件操作(如I/O操作)都会调用系统调用。

关键2:系统调用是有代价的 

在之后的学习中我们会了解到,当程序执行系统调用时,CPU会从用户态切换到内核态这个过程涉及到保护用户程序的寄存器状态,切换页表,加载内核代码段等操作。当系统调用完成时,CPU会从内核态返回到用户态,此时CPU需要恢复用户程序的寄存器状态,整个操作会涉及到数百到数千个CPU周期。

关键3:缓冲区的引入可以减少系统调用次数

以向文件中写入数据为例,当我们引入缓冲区的概念后,对文件的输入操作意味着我们可以逐渐将数据块输入到缓冲区中,然后通过适当的缓冲机制调用系统调用将数据块整体写入到文件中,大大减少了系统调用的次数,大大提高了输入效率。

2.3 缓冲区的分类

2.3.1 用户级(C语言为例)

C标准库中的I/O函数(printf、fwrite、fgets)均围绕流的概念设计。每个流(stdout、stderr、stdin、用户自定义的文件流)都由一个FILE结构体来表示,该结构体包含一个缓冲区以及缓冲策略(行缓冲、全缓冲、无缓冲)。

在C标准库中对结构体FILE的描述如下:

//FILE本质上是定义的一个宏在/usr/include/stdio.h中typedef struct _IO_FILE FILE;
//在/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
};
 缓冲机制

在C标准库(stdio.h)总共定义了三个缓冲机制,每个流(FILE结构体)在其生命周期中通常只配置一个缓冲机制。以下是对三个缓冲机制的介绍:

注意事项:

除了以上默认的刷新方式下列特殊清空也会引发缓冲区的刷新:

  • 缓冲区被写满
  • 显式刷新(如调用flush)

当缓冲区为行缓冲但是始终没有遇到换行符(\n)时,当缓冲区满时会自动提交。 

当涉及磁盘文件操作时默认为全缓冲,当所操作的流涉及一个终端(显示器)时默认为行缓冲,stderr默认不带缓冲区即无缓冲。

这里举一个代码示例:

//code.c
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{const  char* s1="hello printf\n";const  char* s2="hello fwrite\n";printf("%s",s1);fwrite(s2,1,strlen(s2),stdout);fork();return 0;
}

运行结果:

 首先printf与fwrite将字符串写入stdout对应的缓冲区中,当涉及到对终端(显示器)的操作时为行缓冲,所以字符串会依次刷新提交。

此时我们执行以下指令:将程序重定向到一个文本文件(text.txt)中

./code 1> text.txt

 运行结果:

 此时我们发现同一份代码数据被打印了两次,原因是当我们进行重定向操作后就成为了用户对磁盘文件(text.txt)的文件操作,默认缓冲机制变成了全缓冲

当我们创建子进程之前,父进程的两个数据(hello printf / hello fwrite)还在缓冲区中并没有被刷新提交,而我们知道子进程是父进程的副本,当创建子进程时缓冲区中的数据也一并拷贝给了子进程,当程序结束后会自动刷新text.txt文件流的缓冲区,导致数据被打印了两次。

2.3.2 内核级

在Linux系统中,内核级缓冲区是用于在内核空间和用户空间之间传递数据的关键机制。它通常用于提高I/O操作的效率,减少系统调用的次数,并优化数据的传输。

内核级缓冲区的类型:

1> 页缓存

用于缓存文件数据,减少磁盘I/O操作。当文件被读取时,数据会被缓存在页缓存中,后续的读取操作可以直接从缓存中获取数据,而不需要再次访问磁盘。

2> 块设备缓冲区

用于缓存块设备的数据,如硬盘的块数据。它与页缓存类似,但更专注于块设备的I/O操作。

3> 套接字缓冲区

用于网络通信,管理网络数据包的传输。每个网络数据包都会被封装在sk_buff结构中,以便在内核中进行处理。

与用户级缓冲区类似,内核级缓冲区也有刷新机制但是在实现方面会复杂很多。以为在内核层面操作系统要考虑的因素会更多,比如刷新操作可能涉及大量的内存操作,不当的刷新策略可能导致系统资源耗尽或内存泄漏,还有在多核或多线程环境下,内核级缓冲区的刷新机制需要处理并发访问问题。这通常需要引入复杂的同步机制,如自旋锁或读写锁,以确保数据的一致性和完整性等等

 以下是内核级缓冲区的刷新机制,可以来了解一下:

  • 定期刷新:内核会定期将缓冲区中的数据写入存储设备。这种刷新通常由内核的守护进程负责,确保数据在一定时间间隔内被写入磁盘。
  • 显式刷新:应用程序可以通过系统调用(如 fsync 或 fdatasync)显式请求将缓冲区中的数据刷新到存储设备。
  • 缓冲区满时刷新:当内核缓冲区达到一定容量时,内核会自动将数据刷新到存储设备。
  • 文件关闭时刷新:当应用程序关闭文件时,内核会自动将与该文件相关的缓冲区数据刷新到存储设备。
  • 内存压力:当系统内存不足时,内核可能会主动刷新缓冲区以释放内存。这种机制确保系统在高内存压力下仍能正常运行。 

2.3 两级缓冲区的联系

关键词:分层协作

当应户程序通过用户级缓冲区写入数据时,数据首先存储在用户空间的缓冲区中。当缓冲区满或显式调用刷新函数时,数据会被复制到内核级缓冲区。内核级缓冲区进一步管理数据的物理写入操作,确保数据最终被写入磁盘或发送到网络设备。

我们可以通过下图来理解:

这种分层缓冲机制减少了频繁的系统调用,提高了数据处理的效率。同时,内核级缓冲区还可以利用更高级的优化技术,如延迟写入和批量处理,进一步提升系统性能。

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

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

相关文章

在c++中老是碰到string,这是什么意思?

定义一个string类型变量的引用&#xff0c;相当于给现有变量起个别名&#xff0c;与指针还是不一样的。比如string a;string& ba;这两句&#xff0c;b与a实际上是一回事&#xff0c;表示的是同一块内存。 std是系统的一个命名空间(有关命名空间可以参阅namespace_百度百科)…

Day21 奇异值分解(SVD)全面解析

一、奇异值分解概述 奇异值分解是线性代数中一个重要的矩阵分解方法&#xff0c;对于任何矩阵&#xff0c;无论是结构化数据转化成的“样本 * 特征”矩阵&#xff0c;还是天然以矩阵形式存在的图像数据&#xff0c;都能进行等价的奇异值分解&#xff08;SVD&#xff09;。 二…

akshare爬虫限制,pywencai频繁升级个人做量化,稳定数据源和券商的选择

做量化&#xff0c;数据和交易接口是策略和自动化交易的基石&#xff0c;而稳定的数据和快人一步的交易接口是个人做量化的催化剂。 之前写过一篇文章&#xff1a;个人做量化常用的数据&#xff0c;多以爬虫为主&#xff0c;最近akshare爬虫限制&#xff0c;pywencai频繁升级。…

数字签名与证书

1. 数字签名与证书 摘要算法用来实现完整性&#xff0c;能够为数据生成独一无二的“指纹”&#xff0c;常用的算法是 SHA-2&#xff1b;数字签名是私钥对摘要的加密&#xff0c;可以由公钥解密后验证&#xff0c;实现身份认证和不可否认&#xff1b;公钥的分发需要使用数字证书…

Ubuntu22.04安装显卡驱动/卸载显卡驱动

报错 今日输入nvidia-smi报错,在安装了535和550,包括560都没办法解决,但是又怕乱搞导致环境损坏,打算把显卡卸载然后重新安装系统默认推荐版本的显卡驱动 qinqin:~$ nvidia-smi Failed to initialize NVML: Driver/library version mismatch NVML library version: 560.35卸载…

Web 架构之负载均衡全解析

文章目录 一、引言二、思维导图三、负载均衡的定义与作用定义作用1. 提高可用性2. 增强性能3. 实现扩展性 四、负载均衡类型硬件负载均衡代表设备优缺点 软件负载均衡应用层负载均衡代表软件优缺点 网络层负载均衡代表软件优缺点 五、负载均衡算法轮询算法&#xff08;Round Ro…

linux下的Redis的编译安装与配置

配合做开发经常会用到redis&#xff0c;整理下编译安装配置过程&#xff0c;仅供参考&#xff01; --------------------------------------Redis的安装与配置-------------------------------------- 下载 wget https://download.redis.io/releases/redis-6.2.6.tar.gz tar…

A2A大模型协议及Java示例

A2A大模型协议概述 1. 协议作用 A2A协议旨在解决以下问题&#xff1a; 数据交换&#xff1a;不同应用程序之间的数据格式可能不一致&#xff0c;A2A协议通过定义统一的接口和数据格式解决这一问题。模型调用&#xff1a;提供标准化的接口&#xff0c;使得外部应用可以轻松调…

关键点检测--使用YOLOv8对Leeds Sports Pose(LSP)关键点检测

目录 1. Leeds Sports Pose数据集下载2. 数据集处理2.1 获取标签2.2 将图像文件和标签文件处理成YOLO能使用的格式 3. 用YOLOv8进行训练3.1 训练3.2 预测 1. Leeds Sports Pose数据集下载 从kaggle官网下载这个数据集&#xff0c;地址为link&#xff0c;下载好的数据集文件如下…

20250508在WIN10下使用移远的4G模块EC200A-CN直接上网

1、在WIN10/11下安装驱动程序&#xff1a;Quectel_Windows_USB_DriverA_Customer_V1.1.13.zip 2、使用移远的专用串口工具&#xff1a;QCOM_V1.8.2.7z QCOM_V1.8.2_win64.exe 3、配置串口UART42/COM42【移远会自动生成连续三个串口&#xff0c;最小的那一个】 AT命令&#xf…

第J7周:ResNeXt解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目标 具体实现 &#xff08;一&#xff09;环境 语言环境&#xff1a;Python 3.10 编 译 器: PyCharm 框 架: Tensorflow &#xff08;二&#xff09;具体…

C++之类和对象:初始化列表,static成员,友元,const成员 ……

目录 const成员函数&#xff1a; 前置和后置重载&#xff1a; 取地址及const取地址操作符重载&#xff1a; 初始化列表&#xff1a; explicit关键字&#xff1a; static成员&#xff1a; 友元&#xff1a; 友元函数&#xff1a; 友元类&#xff1a; 内部类&#xff1a…

uni-app 中的条件编译与跨端兼容

uni-app 为了实现一套代码编译到多个平台&#xff08;包括小程序&#xff0c;App&#xff0c;H5 等&#xff09;&#xff0c;引入了条件编译机制。 通过条件编译&#xff0c;我们可以针对不同的平台编写特定的代码&#xff0c;从而实现跨端兼容。 一、条件编译的作用 平台差异…

Linux平台下SSH 协议克隆Github远程仓库并配置密钥

目录 注意&#xff1a;先提前配置好SSH密钥&#xff0c;然后再git clone 1. 检查现有 SSH 密钥 2. 生成新的 SSH 密钥 3. 将 SSH 密钥添加到 ssh-agent 4. 将公钥添加到 GitHub 5. 测试 SSH 连接 6. 配置 Git 使用 SSH 注意&#xff1a;先提前配置好SSH密钥&#xff0c;然…

[C++] 大数减/除法

目录 高精度博客 - 前两讲高精度减法高精度除法高精度系列函数完整版 高精度博客 - 前两讲 讲次名称链接高精加法[C] 高精度加法(作用 模板 例题)高精乘法[C] 高精度乘法 高精度减法 void subBIG(int x[], int y[], int z[]){z[0] max(x[0], y[0]);for(int i 1; i < …

视频添加字幕脚本分享

脚本简介 这是一个给视频添加字幕的脚本&#xff0c;可以方便的在指定的位置给视频添加不同大小、字体、颜色的文本字幕&#xff0c;添加方式可以直接修改脚本中的文本信息&#xff0c;或者可以提前编辑好.srt字幕文件。脚本执行环境&#xff1a;windowsmingwffmpeg。本方法仅…

ubuntu nobel + qt5.15.2 设置qss语法识别正确

问题展示 解决步骤 首选项里面的高亮怎么编辑选择都没用。如果已经有generic-highlighter和css.xml&#xff0c;直接修改css.xml文件最直接&#xff01; 在generic-highlighter目录下找到css.xml文件&#xff0c;位置是&#xff1a;/opt/Qt/Tools/QtCreator/share/qtcreator/…

洛谷P7528 [USACO21OPEN] Portals G

P7528 [USACO21OPEN] Portals G luogu题目传送门 题目描述 Bessie 位于一个由 N N N 个编号为 1 … N 1\dots N 1…N 的结点以及 2 N 2N 2N 个编号为 1 ⋯ 2 N 1\cdots 2N 1⋯2N 的传送门所组成的网络中。每个传送门连接两个不同的结点 u u u 和 v v v&#xff08; u …

C++STL——priority_queue

优先队列 前言优先队列仿函数头文件 前言 本篇主要讲解优先队列及其底层实现。 优先队列 优先队列的本质就是个堆&#xff0c;其与queue一样&#xff0c;都是容器适配器&#xff0c;不过优先队列是默认为vector实现的。priority_queue的接口优先队列默认为大根堆。 仿函数 …

助力你的Neovim!轻松管理开发工具的魔法包管理器来了!

在现代编程环境中&#xff0c;Neovim 已经成为许多开发者的编辑器选择。而针对 Neovim 的各种插件与功能扩展&#xff0c;则是提升开发体验的重要手段。今天我们要介绍的就是一个强大而便捷的开源项目——mason.nvim&#xff0c;一个旨在简化和优化 Neovim 使用体验的便携式包管…