当前位置: 首页 > article >正文

深入理解linux中的文件(下)

目录

一、语言级缓冲区和内核级缓冲区

 二、C语音中的FILE* fp = fopen(“./file.txt”,"w"):

四、理解磁盘结构:

物理结构

逻辑结构

五、未被打开的文件:

六、更加深入理解inode编号怎么找到文件:        

七、对路径结构进行缓存:   

1. 路径名解析加速

2. 更新多叉树的路径

3. 进程如何使用 dentry

(1)打开文件

(2)路径遍历


一、语言级缓冲区和内核级缓冲区

        编程语言运行在操作系统之上,并利用操作系统的功能来完成各种任务。当我们提到语言级缓冲区时,它实际上是在操作系统“看不见”的应用层面上工作的。

        如果没有语言级缓冲区,在C语言中每次使用fwritefprintf函数写数据时,都会直接调用系统底层的write函数,这会导致频繁的数据传输,耗费更多时间。为了避免这种情况,我们在程序里设置了语言级缓冲区。这样,当你使用fwritefprintf时,数据首先存入这个语言级缓冲区。只有当满足一定条件时,才会一次性把大量数据传给操作系统处理。至于什么时候这些数据最终被保存到硬盘上,则由操作系统决定。

语言级缓冲区刷新的方式有三种:

  1. 对于显示输出(比如打印到屏幕上),每当遇到换行符\n就会自动刷新缓冲区。
  2. 对于普通文件(保存在磁盘上的文件),当缓冲区满了的时候会自动刷新。
  3. 使用fflush()函数可以手动强制刷新缓冲区,立即把数据写出去。

 二、C语音中的FILE* fp = fopen(“./file.txt”,"w"):

1. 在当前目录下创建一个file.txt文件,模式是写入。

2. 返回一个FILE结构体指针

二、C语音中的fclose的作用:

  1. 刷新缓冲区:如果文件是以写模式(如 "w""a")打开的,调用 fclose() 会首先将缓冲区中的所有未写入数据强制写入到文件中。这确保了所有通过程序写入的数据都被正确地保存到了文件里。

  2. 释放资源:关闭文件后,fclose() 会释放分配给该文件流的所有系统资源。这包括内存中的缓冲区和其他与文件操作相关的资源。这对于保持程序的效率和避免资源泄漏非常重要,尤其是在程序需要处理多个文件或长期运行的情况下。例如:销毁FILE结构体

四、理解磁盘结构:

 

物理结构

  1. 盘片(Platters):硬盘内部由一个或多个圆形盘片组成,这些盘片是实际存储数据的地方。每个盘片都有两面,称为记录面。

  2. 磁道(Tracks):盘片上的表面被划分为若干个同心圆,每一个这样的圆被称为磁道。所有盘片上相同位置的磁道合在一起形成柱面(Cylinder)。

  3. 扇区(Sectors):每个磁道进一步被分割成多个弧段,每个弧段即为一个扇区。标准的扇区大小通常是512字节或4KB(高级格式化技术)。扇区是磁盘读写的最小单位。

  4. 柱面(Cylinders):所有盘片上相同编号的磁道共同构成一个柱面。柱面的概念主要用于描述磁盘的三维地址空间。

  5. 磁头(Heads):用于读取和写入数据的装置。每个盘片的每一面都有一个对应的磁头。(注意:各个读写磁头不会分开移动,而是由一个移动臂控制所以的磁头,一起前进后退)

由于磁盘的三维的物理结构,把每一个扇区当成一个数组的格子,就可以把他抽象成一个三维数组。而三维数组又可以抽象成一个超大的一维数组。 

逻辑结构

  1. 分区(Partition):物理磁盘可以被划分成一个或多个逻辑部分,每个部分称为分区。例如,在Windows系统中的C盘和D盘就是不同的分区。每个分区都可以有自己的文件系统,并像独立的磁盘一样使用。

  2. 分组(Block Group):在Ext4等Linux文件系统中,每个分区可以进一步划分为多个块组。这种划分有助于提高性能并简化管理。

  3. 超级块(Superblock):在linux文件系统中,超级块包含了关于文件系统的元数据,比如块大小、空闲块数量、inode信息等。(重点:Superblock是管理区的,所以不是每一个组中都含有Superblock。如果Superblock损坏了,就找不到几百个G的区了,,所以Superblock也不能只有一个,整个区中会有两三个Superblock作为备份)

  4. 组描述符表(Group Descriptor Table):包含组中的信息:

    1. 数据块位图(Block Bitmap)的位置和状态,用于跟踪哪些数据块已被使用,哪些仍然空闲。

    2. inode位图(Inode Bitmap)的位置和状态,用于跟踪哪些inode已被分配,哪些未被使用。

    3. inode表(Inode Table)的位置,其中包含了该块组内所有文件和目录的inode信息。
    4. 数据块(Data blocks)的数量以及其他相关信息。
  5. 数据块位图 和 inode位图:使用二进制0和1表示空闲和占用的情况。

  6. inode表:inode表中包含很多的inode结构体,通常一个inode结构体大小为128字节。

  7. 索引节点(Inode):在Unix/Linux文件系统中,每个文件都有一个对应的inode,它存储了文件的元数据(如权限、所有权、大小等)以及指向文件数据块的指针。(重点:文件名不存储到inode结构体中

  8. 数据块:每一个数据块是4KB大小,里面存放文件的内容,当然,数据块,也会存放指针。这些指针指向其他的数据块,也就是一个数据块有1KB的指针。

    1. 一级间接块索引表指针,1KB*4KB=4MB大小,就可以存储4MB大小的文件。

    2. 二级索引:4KB * 1KB * 1KB = 4GB。

    3. 三级索引:4KB * 1KB * 1KB = 4TB。

    4. 到了三级索引就够了,一个文件不会有那么大的,太大了我们也会把他拆小。一个文件太大,你内存放不下,也打不开。

五、未被打开的文件:

        未被打开的文件都存储在磁盘上。如果你想访问这些文件,你需要知道它们的具体路径。明明在linux,操作系统只认识inode编号,那么文件路径怎么帮助操作系统找到文件的确切位置?

         在Linux系统中,每个目录不仅包含文件名,还存储了指向这些文件的inode信息。这意味着目录实际上保存了一个文件名与inode编号之间的映射表

       例如,如果你有一个文件路径为 /home/chen/communication/mypipe.cc,以下是系统如何找到这个文件的过程:

  1. 从根目录 / 开始,系统会根据路径进入 home 目录。
  2. 在 home 目录下,系统查找名为 chen 的子目录及其对应的inode编号(比如 chen:13425)。这里的 13425 是一个示例inode编号,实际编号可能不同。
  3. 使用这个inode编号,系统可以定位到 chen 目录。然后在 chen 目录中继续查找 communication 子目录及其inode信息。
  4. 最终,在 communication 目录中找到 mypipe.cc 文件的inode编号,这样就可以读取该文件的内容了。

        需要注意的是,根目录 / 的inode是固定的,这保证了系统始终可以从根目录开始导航至任何文件或目录。这种机制使得即使文件系统非常庞大,也可以高效地定位和管理文件。

通过这种方式,Linux文件系统能够有效地管理和检索文件,同时保持数据的一致性和完整性。

六、更加深入理解inode编号怎么找到文件:        

        当我们获得了某个文件的inode编号(例如13425),接下来需要定位并读取该inode的具体信息来访问文件的内容和属性。

  1. 查找组描述符表:首先,系统会根据inode编号确定该inode属于哪个块组。然后从这个块组的组描述符表中获取相关信息。组描述符表包含了关于该块组的重要信息,比如起始inode编号(例如13400)和结束inode编号(例如13700)。

  2. 计算相对位置:知道了块组的起始inode编号(如13400),我们可以用目标inode编号减去起始inode编号(即13425 - 13400 = 25)。这样我们就得到了目标inode在该块组内的相对位置——这里是第25个inode。

  3. 访问inode表:接着,系统会在该块组的inode表中查找第25号区域中的inode结构体。每个inode结构体通常包含文件的元数据(如权限、所有权、大小等)以及指向文件数据块的指针。

  4. 读取文件内容和属性:一旦找到了正确的inode结构体,我们就可以使用其中的信息来定位文件的数据块,进而读取文件的实际内容及其属性。

通过这种方式,即使文件系统非常庞大,操作系统也能高效地定位和访问任何文件。

七、对路径结构进行缓存:   

        当我们访问文件时,并不是每次都从根目录开始逐层查找,因为这样的效率非常低——实际上,大约99%的文件很少被打开。为了解决这个问题,我们使用多叉树的方式来缓存路径结构。相比直接使用目录结构体,这种多叉树结构更简单,查找速度也更快。

1. 路径名解析加速

        当一个进程尝试访问某个文件时,它提供的是文件的路径名。内核需要将这个路径名转换为具体的 inode。通过使用 dentry 结构体,内核可以快速找到对应路径的 inode,而不需要每次都从磁盘读取信息。多叉树的每个节点都是一个 dentry 结构体,定义如下:

struct dentry {
    char *name;               // 节点名称
    struct inode *inode;      // 指向inode的指针
    struct dentry *parent;    // 父节点指针
    struct list_head children; // 子节点链表头
};

2. 更新多叉树的路径

        当访问一个新的路径时,如果该路径不存在于当前缓存中,则需要创建新的 dentry 节点,并将其添加到正确的父节点的子节点列表中。同时更新父节点的相关信息,例如增加子节点计数等。

3. 进程如何使用 dentry

(1)打开文件

        当一个进程调用 open() 系统调用打开一个文件时,内核会首先通过文件结构体中的 dentry指针,进行查找dentry 缓存中查找该文件的路径。如果找到了,就可以直接获取对应的 inode 和文件描述符;如果没有找到,则需要从磁盘读取相关信息并更新缓存。

(2)路径遍历

        对于任何涉及路径的操作(如 stat(), unlink() 等),内核都会通过 dentry 结构进行路径名解析。这通常涉及到从根目录开始逐步向下遍历直到找到目标文件或目录。


http://www.kler.cn/a/535770.html

相关文章:

  • 【Elasticsearch】nested聚合
  • 20240824 美团 笔试
  • Java 23新特性
  • 生产环境超实用shell脚本一
  • mysql 数据导出到文件
  • 数字人|通过语音和图片来创建高质量的视频
  • Git登录并解决 CAPTCHA
  • 面向 Workload 级别的灵活可配置 Serverless 弹性解决方案
  • 深入浅出DeepSeek LLM 以长远主义拓展开源语言模型
  • AI对话网站一键生成系统源码
  • Android 约束布局ConstraintLayout整体链式打包居中显示
  • 我们究竟畏惧AI什么?
  • 鸿蒙UI(ArkUI-方舟UI框架)- 使用文本
  • Git仓库托管基本使用02——生成公钥
  • 第八天 继续学习ArkTS,掌握基础语法和组件创建
  • 【蓝桥杯—单片机】第十届省赛真题代码题解题笔记 | 省赛 | 真题 | 代码题 | 刷题 | 笔记
  • React 设计模式:实用指南
  • GPU — 8 卡 GPU 服务器与 NVLink/NVSwitch 互联技术
  • 【AI】DeepSeek来了!!!
  • tolua[一]框架搭建,运行example
  • 【Day33 LeetCode】动态规划DP Ⅵ 背包问题
  • SQL Server的安装和简单使用
  • SQL精度丢失:CAST(ce.fund / 100 AS DECIMAL(10, 2)) 得到 99999999.99
  • 【Uniapp-Vue3】z-paging插件组件实现触底和下拉加载数据
  • 【Elasticsearch】random_sampler聚合
  • Leecode刷题C语言之全排列②