linux文件——文件系统与内存管理——理解打开文件, 写入数据与文件系统的关系

2024-09-02 19:44

本文主要是介绍linux文件——文件系统与内存管理——理解打开文件, 写入数据与文件系统的关系,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        前言:本节课算是一点文件系统的补充内容。 但是说是文件系统的补充内容,其实我们也可以把这篇文章当作linux下的内存管理的文章来看待。 因为博主会从内存管理的角度, 将进程打开文件、写入数据的流程, 以非常底层的角度, 给剖析出来。 ——本篇文章可能讲的可能不会很细致。 但是博主相信友友们如果看完本篇文章后,一定会对于文件打开, 文件写入和磁盘之间的关联有一个很清晰的认识!!!

        ps:注意本篇内容非常抽象, 非常难以理解。没有前面文件系统、文件管理、进程管理的铺垫可能无法学习本节。建议友友们了解对应只是后再来学习本节内容!!! 

目录

页框和页帧

页框

页帧

数据的交换

 操作系统如何管理内存?

先描述

再组织

从用户层到内存

重新理解文件管理

struct inode

adress_space

radix_tree解析——补充知识

从内存到磁盘


页框和页帧

页框

对于物理内存来说, 内存可以分成一块一块的, 并且这些一块一块的内存是连续的, 也就可以被当成一个线性的逻辑结构, 如下图:

这物理内存中一块一块的4kb的块, 就叫做页框。 ——这里为什么是4kb, 不太重要, 而且很简单, 这里先卖关子。

页帧

不只只物理内存的逻辑结构可以是线性的。 我们也知道磁盘的逻辑结构也是线性的。 并且里面的存储内容是以块为单位的!!! 那么我们的程序, 在磁盘中保存的时候, 其实就是一块一块的分开保存的。 如下图:

注意, 这里的块是连续的, 因为一个程序就是一个文件, 而我们说过, 一个文件的块是连续的。 这里的这一个个的块, 就叫做页帧。

        

数据的交换

        我们知道, 在进行IO的时候, 物理内存和磁盘的数据是来回加载过来加载过去的。 而之所以上面的块都是4kb, 就是因为方便交换, 这个4kb就是交换数据的单位。(为什么是4kb, 因为是大佬们规定的。这也就回答了物理内中的页框是4kb一个。)

        在物理内存和磁盘进行数据交换的时候, 会以4kb的大小进行加载。 这个时候, 即便我们都要加载的数据只有1字节有效内容, 也只能将连带1字节在内的4kb一起加载进来!!!

        为什么会这样呢?  ——因为我们的磁盘是机械设备,而机械设备的速度相对于计算机内部的速度(光电速度)是很慢的。 那么就说明磁盘存储数据或者拿数据向内存中加载数据是很慢的, 那么我们一次性向内存中加载4kb数据一次性向内存中加载1kb, 但是加载4次相比肯定要快的多得多。 ——这个过程只需要定位磁头, 定位磁道, 定位扇区, 将数据连续的写到磁盘上即可。

        不知道友友们是否知道局部性原理——也就是我们一般访问代码和数据的时候, 有非常大的概率, 在该正在访问的代码区域的附近, 也会被访问。 ——所以我们在加载内存的时候, 即便只有1字节, 也要把这一字节的上下文全部加载进来。 这就是——基于局部性原理的预加载机制。

        所以, 综上, 一次性加载4kb这样做有什么好处呢?

  • 减少IO的次数——减少访问外设的次数——硬件层面
  • 进行预加载——有较大概率在软件层面上是实现效率提升

 操作系统如何管理内存?

        这里我们需要提前知道的是——我们从内存的学习中了解到了虚拟地址空间, 而操作系统想要管理物理地址。 那么就要能够看到虚拟地址, 同时也要看到物理地址。 ——为什么会这样?

        因为操作系统给我们的虚拟地址利用页表进行映射物理内存, 分配物理空间, 如果操作系统看不到物理内存, 如何给进程分配物理空间呢? 如果看不到虚拟地址, 进程如何实现我们看到的显示器上面的数字呢?

        所以,操作系统是能够看到内存的物理地址的!!!那么如果操作系统想, 是不是就能够管理物理地址了?——那么操作系统是如何管理物理地址的呢?

        具体操作系统如何管理地址, 这对于学习了如何进行文件管理, 如何进行进程管理的我们来说太简单了——也就是先描述,再组织!!!如果友友不知道,那么可能需要先学习一下前面的知识, 因为这六个子是整个计算机世界进行管理的本质

先描述

结构体的名称是page, 并且描述的是物理内存的一个个的页框。

struct page
{//page必要的属性信息。
}//描述的是一个个页框

再组织

        如果这里我们的物理内存一共有4GB, 那么如果按照一个页框4kb来算, 物理内存就有大概一百万个页框, 计算后其实是1,048,576个页框。

        一个struct page对象用来描述一个页框, 那么操作系统要将内存组织起来就要创建一个一百万大小的struct page对象组成的数组。 如下:

struct page page_arrar[1,048,576];//创建一个结构体数组进行组织

         所以, 我们操作系统对内存的管理, 就转化为了对于数组的管理!!!

         又因为数组天然是有下表的, 所以页框就有了下标!也就有了页号这样的概念!(了解即可, 本篇文章并没有用到页号)

        那么这里就可以得出结论了——我们要访问一个内存 ,只需要先直接找到这个对应的4kb对应的page, 就能在系统中找到对应的物理页框(在操作系统中, 所有申请内存的动作, 其实都是在访问内存page数组)。

我们查看一下linux下面的page结构体, 这里先看一部分, 一个flags, 一个_count

        这两个字段, 我们虽然没有学过内存管理, 但是我们已经学习了虚拟地址空间, 学习了页表。 应该也能够看出来——flag代表映射的物理内存的状态, 是否是只读, 是否是只写等等。而_const就是引用计数, 什么是引用计数呢? 是否还记得我们在学习父子进程的时候, 说过:子进程刚刚被创建出来的时候, 我们的父子进程的页表可能会指向同一块代码和数据。而这里的引用计数其实就是有多少页表会指向这块空间

        操作系统是如何发现缺页中断, 是如何发现要执行写时拷贝的呢?——其实非常简单, 我们知道, 我们的子进程在创建出来的时候, 指向的物理空间应该是只读的, 并且这个时候, 这块物理空间的引用计数应该是2, 那么我们如果看到某一块的物理内存的flags是为只读, 并且这块物理空间内的引用计数大于2, 那么就要执行写时拷贝!!!

        page里面还有一个链表结构——lru(这是个数据结构, 友友们可能学过), 也就是最近最少使用, 我们的操作系统要把我们的内存中不太使用的, 不长使用的刷新出去。因为如果一个一个遍历, 那么就太慢了,所以就用到了一个lru的数据结构。

        其实对于操作系统内存管理的上层还有伙伴系统算法, slab分派器。 这里解释一下slab分派器

        slab分派器就是一种对于垃圾数据结构回收的机制——就比如我们创建进程, 必须要有虚拟地址空间, 也就要有mm_struct对应, 也要有struct page对象。 当我们释放掉这些对象的时候, 我们的操作系统就可能不去释放了, 而是直接将这些对象保存在了一个队列, 或者一个数组等等数据结构里缓存起来等到下次创建对象的时候就直接拿过来用, 就不用再重新向操作系统中申请内存了!!!

从用户层到内存

我们电脑开机的时候, 基本上会将文件系统的前四个信息内容, 加载到内存中, 也就是下面这四个内容:

        那么问题就来了, 我们的磁盘里面可不止有一个分区, 而是由很多个分区。 这些分区可能文件系统不一样, 也就是super block的内容不一样。 那么怎么办呢? 没关系, 其实操作系统就是使用一个链表结构, 将所有分区的super block进行链接起来。 然后在操作系统的层面上就知道了我们分区的位置, 然后还有文件系统的样子等等。如下图:

重新理解文件管理

struct inode

        现在我们来回忆一下文件fd

我们在今天这节课之前理解的文件fd其实就是下面这一张图

        今天, 我们要重新理解一下这张图了。 

        其实, 我们在加载inode属性信息的时候, 不是将磁盘中对应的inode属性直接加载到struct file里面。 而是先创建另一个结构体对象, 这个结构体对象叫做struct inode, 然后将大部分文件的属性保存到这个结构体里面 然后struct file里面包含一个指向这个结构体的指针, 如下图:

        然后我们的上层, 也就是应用层。 我们是如何打开的一个文件?  ——这里以前讲过, 这里相当于重新复习。 这里只是简单的说一下流程。

        首先我们要知道这个文件的路径+文件名。 ——》这就相当于知道了当前目录, 然后我们就可以读取当前目录的数据块, 找到对应目录的inode, 进而找到对应的struct file 对象的inode, 然后查看已经加载到内存中的inode bitmap, 看这个inode是否存在, 如果存在, 再讲磁盘中等等属性信息加载到内存中。

adress_space

另外其实在struct file里面, 存在一个数据结构的指针, 这个数据结构叫做adress_space(地址空间)。 如下图:

adress_space这个数据结构里面包含一颗radix树, 这棵树叫做page_tree

radix_tree这个结构的定义如下:

        这是整颗树的结构体定义。这棵树是一颗多叉树:

        这个叶子结点, 其实就是上面的radix_tree_node类型的对象。

        真正的树节点的结构是下面这张图:

        radix_tree_node这个结构体里面, 我们看一下slots指针数组。 这个指针数组其实就是当前节点的叶子节点的指针。 上图中有RADIX_TREE_MAP_SIZE个数组元素, 说明这是一颗除叶子结点外,每个节点都有RADIX_TREE_MAP_SIZE个孩子节点的多叉树。

        那么可能有的友友们会疑惑了, 这颗树的叶子结点连接的是什么呢?是不是指向空呢?——答案是指向的是我们的page!!!——我们知道, 我们使用fopen, fprintf本质上就是先向内存中写入数据, 再将内存中的数据刷新到磁盘里面。而page就是物理内存page现在被一颗树指向了!!那么是不是也就是说, 我们的用户层, 在使用fopen, fprintf向文件中打印的时候, 一定会先按照一种流程, 这种流程从上到下贯穿整个文件管理, 一直进入地址空间, 然后穿过整颗radix树, 最后找到page, 将数据写到物理内存里面!!!——而这, 就是整个的fopen, fprintf写入数据的过程。 这, 也是内存进行管理管理的过程。 这里的page,这颗树连接的所有的page, 组成的这个page_array[]数组, 其实就是文件页缓冲区, 也是我们所说的内核级缓冲区!!!

        画成图就是这样子:

        这其实就是用户层到内存的整个流程, 也可以说是结论。 

radix_tree解析——补充知识

        radix_tree到底是一棵什么样的树呢?

        其实, 这棵树叫做基数树 或者 基数——》本质上就是一棵字典树。 

        这棵树的节点的结构大致如下:

struct node
{int level; //层数void* slot[NUM]; //这个NUM可以是很多数, 可以是26, 可以是3, 可以是5
}

        我们这里假设NUM是3, 也就是说这棵树每一个节点都有3个子节点。 并且, 字典树的层数一般和这个NUM是相同的。 也就是说, 每一个节点有几个孩子节点, 这棵树就有几层。 假设NUM为3, 那么这棵树就有三层, 并且每个节点的子节点都是三个。

上面这就是一颗字典树,最后叶子节点的slot指向的是要保存的数据. 也就是说, 一个叶子节点, 可以保存三个数据块。 那么如何找到某个数据呢?——如果我们要找bca这个数据, 那么就先找第一个节点的b子节点, 再找b子节点的cc子节点。 以此类推, 直到找到最后要查找到的数据, 上图中用红线画的就是整个查找流程。

        文件的内容按照4kb是有偏移量的, 加入今天我有一个10MB的大小的文件。 如下图:

         所以这里块的个数就是10 * 1024 * 1024 / 1024 / 4 = 2560

        然后每一个数据块的下标乘以4kb就能计算出这个数据在原始内容中的偏移量。 

        所以, 如果我们提前构建出一颗字典树, 那么我们如果将我们的文件内容的下标(下表是int类型), 以比特位为单位, 按照某种规则构成特定的几个区域, 在字典树当中找到对应的偏移量和page的映射关系。 就能通过偏移量等等直接查找物理内存了。

        那么未来我们读写文件, 都是通过偏移量, 我们一旦有了偏移量, 根据这棵字典树里面的映射关系, 我们就可以找到对应的page。  ——这个偏移量的具体细节博主也不清楚, 知道就行

偏移量不太重要, 但是下面的这个结论是非常重要的!

        这里的这个文件, 是有自己的文件页缓冲区的。 其实, 在linux中, 我们的每一个进程, 打开的每一个文件, 都要有自己的文件页缓冲区!!!这个文件页缓冲区, 就是字典树能够找到的所有的page结构体对象!!!

从内存到磁盘

        上面我们已经很清楚的讲解了数据如何从上层用户写到内存中。 那么接下里的一步就是如何从内存中写到磁盘里面。

        其实我们从这张图里面可以看到, 进程是不关心文件管理如何向内存中写入数据的。并且, 写到了物理内存之后, 文件管理也不管物理内存了。 那么从物理内存到磁盘, 到底是怎么管理的呢?——其实, 之后的任务就是驱动程序的工作了, 这个也叫做IO子系统。

        而要管理好未来我们的大量的IO请求, 操作系统就要对这些请求进行描述, 再组织起来。

而描述的结构体是一个驱动级别的结构体(知道就行,博主也不理解什么是驱动级别的结构体)

struct request
{//这里面包含了与请求相关的各个page;//这里面也包含了LBA, 也就是包含了要在磁盘的哪个位置写入数据!!!
}

        描述了之后, 系统如果想要将这些IO请求组织起来, 用的是大量的request 队列——为什么使用队列, 这是因为IO也需要排队的。 

        但是, 这里有一个问题——可能我们的这个队列里面存在了这样一个情况, 比如进程1访问1号扇区, 进程2访问6号扇区, 但是进程3访问3号扇区等等。  就是说磁盘在进行访问的时候, 如果直接按照顺序访问队列的进程, 那么磁头就可能访问不到连续的区域。 而磁头的重新定位会花费大量的时间(相对于计算机内部)所以我们再进行定位的时候要尽可能让我们的请求访问同一个盘面的, 同一个磁道的, 连续的扇区。 这样效率才能保证最高。 所以, 我们的操作系统就可以对上面的队列进行IO排序, 然后再IO合并。 以此来保证能够访问连续的扇区。   

经过操作系统的组织之后, 将request对象拿给磁盘驱动, 再由磁盘驱动去执行, 就能够将数据从内存写到我们的磁盘里面了!!!

那么上面就是数据从内存到磁盘到整个过程。

 

————————以上, 就是本节的全部内容, 下面为本节课笔记

这篇关于linux文件——文件系统与内存管理——理解打开文件, 写入数据与文件系统的关系的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1130893

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。