『 Linux 』文件系统

文章目录


磁盘构造

磁盘内部构造由磁头臂,磁头,主轴,盘片,盘面,磁道,柱面,扇区构成;

  • 磁头臂 :控制磁头的移动,可以精确地定位到磁盘上的特定磁道;
  • 磁头 :负责读取和写入数据;在读取数据时,磁头会检测盘片表面的磁性变化;在写入数据时,磁头会改变盘片表面的磁性,以存储数据;
  • 主轴 :带有马达,使盘片高速旋转;这种旋转允许磁头访问盘片上的任意位置;
  • 盘片 :数据存储的介质,通常有多个盘片叠加在一起,每个盘片有两个盘面;
  • 盘面 :盘片的每一面,用于存储数据;每个盘面都配备有对应的磁头;
  • 磁道 :盘面上的同心圆,数据以磁道为单位进行组织;每个磁道可以进一步被分割成多个扇区;
  • 柱面 :由所有盘面上相同磁道号的磁道组成;设想如果磁盘的多个盘片沿主轴方向穿透,形成的圆柱形结构即为柱面;
  • 扇区 :磁道进一步划分的单位,是磁盘存储数据的最小物理单元;每个扇区通常存储512字节或更多的数据;

磁盘抽象化

磁带设备的存储最终数据将被存储到磁带上;

将磁带拉直可以将其看成连续不断的空间;

将磁盘盘面进行抽象化;

由于盘片的高速转动+磁头臂带动磁头的移动可以将其看成是一个螺旋的运动;

螺旋化后拉直为一个以扇区为最小单位的连续不断的空间;

与磁带不同,磁带的访问为顺序访问,磁盘的访问通过盘片的高速旋转+磁头臂带动磁头可以进行随机访问;


磁盘的寻址方式

  • CHS(Cylinder-Head-Sector)寻址

    CHS寻址方式使用柱面号,磁头号和扇区号来定位磁盘上的数据;

    每个地址由三个部分组成:柱面号( C ),磁头号( H )和扇区号( S );

    • 柱面号( C ) :表示磁盘上的柱面,柱面是所有盘片上相同半径的磁道的集合(从0开始编号);
    • 磁头号( H ) :表示盘片上的磁头,磁头用于读取和写入数据(从0开始编号);
    • 扇区号( S ) :表示磁道上的扇区,扇区是数据存储的最小单位(从1开始编号);
  • LBA(Logical Block Addressing)寻址

    LBA寻址方式使用逻辑块地址定位磁盘上的数据;

    每个逻辑块地址是一个唯一的整数,表示磁盘上的一个逻辑块(这里指扇区);

    • ****逻辑块地址(LBA):表示磁盘上的一个逻辑块,逻辑块是数据存储的基本单位;
    • LBA寻址方式将整个磁盘视为一个连续的逻辑块数组,每个逻辑块都有一个唯一的地址;

因物理原因, LBA(Logical Block Addressing) 寻址最终以算法转换为 CHS(Cylinder-Head-Sector) 寻址;

  • LBA寻址转换为CHS寻址公式

    • 柱面号( C )

      bash 复制代码
      C = LBA / ( H * S );
    • 磁头号( H )

      bash 复制代码
      H = ( LBA / S ) % H;
    • 扇区号( S )

      bash 复制代码
      S = ( LBA % S ) + 1; #CHS中扇区号从1开始计算
    • 例:

      • 磁盘有3盘片(6盘面);
      • 每个盘面有20000个扇区;
      • 每个盘面有50个磁道;
      • 每个磁道有400个扇区;
      • 需要访问的扇区编号(LBA)为28888;

      柱面号的计算:28888 / ( 50 * 400 ) = 1;

      相对扇区计算:28888 % ( 50 * 400 ) = 8888;

      磁道编号计算:8888 / 400 = 22;

      磁道上相对扇区编号:8888 % 400 = 88;

      结果:

      • 盘面编号:1;
      • 磁道编号:22;
      • 扇区编号:89(从1开始编号);

CHS寻址方式逆向得到LBA编号;


磁盘控制器

磁盘硬件中存在磁盘控制器(物理硬件);

  • 磁盘控制器的功能:
    • 数据传输
    • 执行命令
    • 错误检测和纠正
    • 缓存管理

在磁盘控制器中存在一系列的寄存器(硬件):命令寄存器,数据寄存器,状态寄存器,地址寄存器等;

  • 命令寄存器

    用于存储来自计算机的操作命令;

  • 数据寄存器

    用于暂存从计算机写入磁盘的数据,或者从磁盘读取的数据;

  • 状态寄存器

    用于指示当前磁盘的状态,以及最近一次操作的结果;

  • 地址寄存器

    用于指定数据在磁盘上的位置,如扇区号或磁道号;

  • ...

同时在磁盘硬件中为用户提供了一系列的 接口/串口 ;

接口/串口 作为驱动程序与磁盘控制器之间的桥梁,操作系统将通过磁盘接口(接口/串口)间接调用磁盘控制器从而操作磁盘;


磁盘数据传输

计算机中存在一种允许外部设备直接与系统内存进行数据传输的硬件组件DMA;

DMA(Direct Memory Access,直接内存访问);

DMA在进行数据传输时无需经过CPU因此可以减少CPU负担;

假设用户请求创建一个名为newfile.txt新文件并写入数据:

  • 用户请求

    用户请求或进程调用open("newfile.txt", O_CREAT | O_WRONLY);

  • 系统调用处理

    CPU将系统调用转发给OS;

    OS内核文件系统模块接收请求;

  • 文件系统操作

    文件系统将维护对应的结构体并为其分配数据块;

  • 数据写入

    用户或应用程序调用write进行数据写入;

    文件系统写入分配的数据块;

  • DMA配置

    OS配置DMA控制器设置源地址和目标地址并启动DMA传输;

  • 驱动程序操作

    驱动程序将写入命令和数据块地址发送给磁盘控制器;

    驱动程序处理终端和状态信息;

  • 磁盘控制器操作

    磁盘控制器接收写入命令并解析;

    磁盘控制器通过DMA从内存读取数据并写入磁盘扇区;

    磁盘控制器报告状态;


文件系统

操作系统通常无法直接管理巨大的磁盘空间,因此需要对磁盘进行分区(类比Windows中对C,D,E,F盘的分区);

分区的方式一般通过操作系统维护其struct结构体即可;

c 复制代码
struct partion{
    int start;
    int end;
    //...
}

而实际每个分区的大小也会比较大,需要继续对分区进行区分管理;

一般情况下每个分区都由以下组成:

  • 启动块(Boot Block)

    一般情况下BootBlock为引导块,只存在于系统盘当中;

    但有些情况也会存在其他分区当中;

    存在该块的分区一般被称为引导分区;

  • 块组(Block group)

    每个分区由若干个块组组成,由Blockgroup 0Blockgroup n;

    块组是文件系统重最基本的管理单元,每个块组包含多个数据块,Inode表和其他元数据;

(该图为演示,与内核空间映像存在差异)

块组中所包含的多个数据块即为文件系统;

每个块组通常包含以下几个部分:

  • ****超级块(Superblock)

    • 存储文件系统的全局元数据;
  • 块组描述符表(Group Descriptor Table)

    • 存储每个块组的描述符,描述块组的元数据位置和状态;
  • 块位图(Block Bitmap)

    • 跟踪块组中数据块的使用情况;
  • Inode位图(Inode Bitmap)

    • 跟踪块组中Inode的使用情况;
  • Inode表(Inode Table)

    • 存储文件和目录的元数据;
    • Inode表中将存若干个Inode;
    • Inode用于存储文件基本属性(如文件类型,文件权限,拥有者,所属组等);
  • 数据块(Data Blocks)

    • 存储实际的文件和目录内容;

假设存在一个文件系统,其应包含以下块组:

cpp 复制代码
struct superblock {
    int fs_size;  // 文件系统的大小
    int block_size;  // 块大小
    int free_blocks;  // 空闲块数量
    int free_inodes;  // 空闲Inode数量
    // 其他文件系统元数据
};

struct group_descriptor {
    int block_bitmap;  // 块位图的起始位置
    int inode_bitmap;  // Inode位图的起始位置
    int inode_table;  // Inode表的起始位置
    int free_blocks;  // 块组中的空闲块数量
    int free_inodes;  // 块组中的空闲Inode数量
    // 其他块组元数据
};

struct block_group {
    struct superblock sb;  // 超级块
    struct group_descriptor gd;  // 块组描述符
    char block_bitmap[128];  // 块位图
    char inode_bitmap[128];  // Inode位图
    struct inode inodes[1024];  // Inode表
    char data_blocks[4096][4096];  // 数据块
};

struct inode {
    int file_type;  // 文件类型
    int permissions;  // 文件权限
    int size;  // 文件大小
    int block_pointers[12];  // 数据块指针
    // 其他文件元数据
};

磁盘中最小的存储单位为扇区,而在文件系统中最小的单位为 文件系统块 ;

存储数据的数据块中存在若干个 文件系统块 ;

文件系统块的大小必须 >= 扇区大小,一般最常用为4kb;

  • 在数据的存储中,一般只有最后一个文件系统块会产生空间浪费:

    假设一个文件的的大小为5kb,将会为其分配两个文件系统块用于存储,其中第二个块为最后一个块将浪费3kb的空间;


Inode

在Linux中,文件的内容与文件的属性构成一个完整的文件;

其中文件的内容与文件的属性是分开管理的;

  • 文件的内容管理在数据块(Data Blocks)中
  • 文件的属性管理在Inode表中的Inode中;

Inode可以看作是一个结构体,其包含一个文件的所有属性:

c 复制代码
struct inode {
    uint16_t i_mode;        // 文件类型和权限
    uint16_t i_uid;         // 文件所有者的用户ID
    uint32_t i_size;        // 文件大小(字节)
    uint32_t i_atime;       // 最后访问时间
    uint32_t i_ctime;       // 创建时间
    uint32_t i_mtime;       // 最后修改时间
    uint32_t i_dtime;       // 删除时间
    uint16_t i_gid;         // 文件所属组的组ID
    uint16_t i_links_count; // 链接计数
    uint32_t i_blocks;      // 文件占用的块数
    uint32_t i_flags;       // 文件标志
    uint32_t i_block[15];   // 数据块指针
    // 其他文件系统特定的信息
};

其中每个Inode都在对应的文件系统中存在一个唯一编号(不同文件系统之间的Inode编号可以重复);

在Linux当中可以采用ls -li获取其Inode编号;

bash 复制代码
$ ls -li
total 0
2360117 -rw-rw-r-- 1 _XXX _XXX 0 Jun  1 18:02 newfile1.c
2360116 -rw-rw-r-- 1 _XXX _XXX 0 Jun  1 18:00 newfile.c

其中在每个块组中都存在一个Inode位图(Inode Bitmap)来跟踪该文件系统中的Inode的使用情况(利用位图可以大大节省跟踪时所使用的空间);

当需要访问一个文件时文件系统将根据Inode中的编号查找文件的目录和数据块指针从而访问文件的实际数据;

  • 文件的文件名不存在于Inode之中,而是单独存放在目录的数据结构当中

    每个目录在文件系统当中被视为一个特殊的文件,其自身拥有一个Inode与数据块;

    目录中的数据块保存了文件名与对应Inode编号的映射关系,这些映射关系被称为目录条目;

    • 目录条目的结构演示

      c 复制代码
      struct directory_entry {
          uint32_t inode_number;  // Inode编号
          char file_name[256];    // 文件名
      };

      当通过文件名访问文件时文件系统将会执行以下步骤:

      假设文件路径为/home/user/file.txt;

      • 根目录(/)

        查找根目录的Inode(通常为Inode2);

        在根目录的数据块中查找home目录的目录条并获取其Inode编号;

      • home目录

        查找home目录的Inode;

        home目录的数据块中,查找user目录的目录条目并获取其Inode编号;

      • user目录

        查找user目录的Inode;

        user目录的数据块中,查找file.txt的目录条目并获取其Inode编号;

      • file.txt文件

        查找file.txt的Inode;

        通过Inode中的数据块指针访问其文件的实际数据;


数据块(Data Blocks)

数据块中存在若干个文件系统块;

在文件系统中的块位图(Bolck Bitmap)即为跟踪当前文件系统中数据块内各个文件系统块的使用情况;

在Inode中存在一组数据块指针数组,一般情况这个指针数组的大小为15;

这个指针数组中包含了三种数据块指针:

  • 直接指针(Direct Pointers)
    • 通常有12个直接指针,每个直接指针直接指向一个数据块(下标0-11)。
    • 直接指针用于存储文件的前12个数据块。
  • 单重间接指针(Single Indirect Pointer)
    • 指向一个间接块,间接块中包含指向数据块的指针(下标12)。
    • 单重间接指针用于存储更多的数据块。
  • 双重间接指针(Double Indirect Pointer)
    • 指向一个双重间接块,双重间接块中包含指向间接块的指针,间接块中再包含指向数据块的指针(下标13)。
    • 双重间接指针用于存储更多的数据块。
  • 三重间接指针(Triple Indirect Pointer)
    • 指向一个三重间接块,三重间接块中包含指向双重间接块的指针,双重间接块中再包含指向间接块的指针,间接块中再包含指向数据块的指针(下标14)。
    • 三重间接指针用于存储更多的数据块。

可以将其中的数据块指针数据看成是一种树状结构;

删除一个文件的本质是删除其对应文件系统中的Block Bitmap以及相关映射删除(所以理论上数据的删除可以被修复),当需要再此使用时只需要对文件系统块对应的内容进行覆盖即可;


超级块(SuperBlock)

超级块中包含了当前文件系统中的全局信息和元数据(文件系统大小,块大小,空闲块数量等);

通常超级块位于文件系统的起始位置,每个块组中都可以存在一个超级块以便于在超级块损坏时可以进行对超级块的恢复;

超级块通常存在以下内容:

  • 文件系统元数据
    • 超级块包含文件系统的全局元数据,如文件系统的大小、块大小、空闲块数量、空闲Inode数量等。
    • 这些元数据用于文件系统的管理和操作。
  • 文件系统状态
    • 超级块包含文件系统的状态信息,如文件系统是否已挂载、是否已清除等。
    • 这些状态信息用于文件系统的维护和恢复。
  • 文件系统配置
    • 超级块包含文件系统的配置参数,如块大小、Inode大小、块组大小等。
    • 这些配置参数用于文件系统的初始化和操作。

超级块的结构示例如下:

c 复制代码
struct superblock {
    uint32_t s_inodes_count;        // 文件系统中的Inode总数
    uint32_t s_blocks_count;        // 文件系统中的块总数
    uint32_t s_r_blocks_count;      // 保留块总数
    uint32_t s_free_blocks_count;   // 空闲块总数
    uint32_t s_free_inodes_count;   // 空闲Inode总数
    uint32_t s_first_data_block;    // 第一个数据块的块号
    uint32_t s_log_block_size;      // 块大小(以2的幂次表示)
    uint32_t s_log_frag_size;       // 碎片大小(以2的幂次表示)
    uint32_t s_blocks_per_group;    // 每个块组的块数
    uint32_t s_frags_per_group;     // 每个块组的碎片数
    uint32_t s_inodes_per_group;    // 每个块组的Inode数
	//...
    // 其他文件系统特定的信息
};

块组描述符(Group Descriptor)

块组描述符用于描述每个块组的元数据位置和状态;

其一般包含了所有块组的描述符,其中每个块组都有一个对应的块组描述符;

  • 块组描述符的作用

    • 描述块组元数据的位置

      块组描述符一般包含组内重要的元数据(块位图,Inode位图,Inode表)的起始位置;

      通过快描述符使得文件系统可以快速定位组内元数据;

    • 跟踪块组的状态

      块组描述符包含块组内空闲块和空闲Inode的数量;

      这些信息一般用于文件系统的管理和优化;

块组描述符的结构示例:

c 复制代码
struct group_descriptor {
    uint32_t block_bitmap;  // 块位图的起始位置
    uint32_t inode_bitmap;  // Inode位图的起始位置
    uint32_t inode_table;   // Inode表的起始位置
    uint16_t free_blocks_count; // 块组中的空闲块数量
    uint16_t free_inodes_count; // 块组中的空闲Inode数量
    uint16_t used_dirs_count;   // 块组中的已使用目录数量
    // 其他块组元数据
};
相关推荐
C++忠实粉丝33 分钟前
Linux环境基础开发工具使用(2)
linux·运维·服务器
康熙38bdc1 小时前
Linux 环境变量
linux·运维·服务器
存储服务专家StorageExpert1 小时前
DELL SC compellent存储的四种访问方式
运维·服务器·存储维护·emc存储
凯子坚持 c1 小时前
C语言复习概要(三)
c语言·开发语言
无限大.1 小时前
c语言200例 067
java·c语言·开发语言
无限大.1 小时前
c语言实例
c语言·数据结构·算法
Death2002 小时前
Qt 中的 QListWidget、QTreeWidget 和 QTableWidget:简化的数据展示控件
c语言·开发语言·c++·qt·c#
hakesashou2 小时前
python如何比较字符串
linux·开发语言·python
Ljubim.te2 小时前
Linux基于CentOS学习【进程状态】【进程优先级】【调度与切换】【进程挂起】【进程饥饿】
linux·学习·centos
Death2002 小时前
Qt 3D、QtQuick、QtQuick 3D 和 QML 的关系
c语言·c++·qt·3d·c#