深入理解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 结构进行路径名解析。这通常涉及到从根目录开始逐步向下遍历直到找到目标文件或目录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值