跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Shell / Bash

Linux Ext 系列文件系统原理:从磁盘结构到软硬链接

Linux Ext 系列文件系统是 Linux 主流的文件系统,从磁盘物理结构出发,解析了块、分区、inode 等核心概念,阐述了 Ext2 文件系统的架构(超级块、GDT、位图、inode 表),并详细说明了目录挂载机制及软硬链接的底层实现逻辑。文章涵盖了磁盘物理与逻辑结构、CHS 与 LBA 寻址、Ext2 块组管理细节以及路径解析过程,帮助读者深入理解 Linux 文件存储的底层原理。

深海蔚蓝发布于 2026/2/28更新于 2026/7/2545 浏览
Linux Ext 系列文件系统原理:从磁盘结构到软硬链接

Linux Ext 系列文件系统原理:从磁盘结构到软硬链接

前言

Ext 系列文件系统(Ext2/Ext3/Ext4)是 Linux 最经典的文件系统。本文从磁盘硬件出发,拆解 Ext 文件系统的设计逻辑——从物理扇区到逻辑块,从 inode 到块组,再到目录、挂载和软硬链接,解析 Linux 文件存储的底层原理。

一、开篇:为什么需要文件系统?

磁盘是计算机的'仓库',但它本身只是一堆'冰冷的硬件'——由盘片、磁头、扇区组成,只能按物理地址存储数据。如果直接让用户按物理地址操作磁盘,不仅效率极低,还会导致数据混乱。

**文件系统(File System)**是操作系统用于管理磁盘等存储设备的一套规则、数据结构和接口集合。它就像磁盘的'管理员 + 翻译官',一边对接底层存储硬件,一边为上层用户/程序提供'文件名、路径'等友好的操作方式。核心目标是让'存储数据'变得有序、高效、安全。

文件系统的核心作用,就是给磁盘'制定规则':

  1. 把磁盘空间划分为'可管理的单元'(如块、分区);
  2. 分离存储'文件属性'(如大小、权限、修改时间)和'文件内容';
  3. 建立'文件名→文件数据'的映射(通过目录和 inode);
  4. 管理空闲资源(哪些空间可用,哪些已占用)。

简单说:没有文件系统的磁盘,只是一堆能存储 0 和 1 的'裸硬件';有了文件系统,磁盘才变成了能分类存放'文件'的'智能仓库'。

Ext 系列文件系统中,Ext2 是基础,Ext3/Ext4 在其之上增加了日志等特性,核心设计完全一致。我们以 Ext2 为原型,讲解文件系统的底层逻辑。

二、磁盘基础:从物理结构到逻辑地址

磁盘(Disk)是计算机中用于长期存储数据的硬件设备,它是块设备,也是操作系统和用户数据的'最终归宿'。要理解文件系统,首先得懂磁盘的物理结构和寻址方式。

2.1 磁盘物理结构:盘片、磁道、扇区与柱面

机械磁盘的物理结构类似'多碟 CD 机',核心组件包括:

  • 盘片:圆形金属盘,两面都能存储数据;
  • 磁头:对应每个盘面,负责读写数据;
  • 磁道:盘片上的同心圆,每个同心圆是一个磁道;
  • 扇区:磁道被分成的扇形区域,是磁盘最小物理存储单位(默认 512 字节);
  • 柱面:所有盘片上'半径相同的磁道'组成的圆柱。

我们在磁盘中通过一个个的扇区来存储数据。一个磁盘中有多个盘面,因此就有多个磁头,这些磁头是共进退的,也就是说它们会同时执行不同盘面的相同位置的扇区。因此所有盘面中的同心圆,也就是相对位置相同的磁道就组成了一个柱面。

例如:一张磁盘有 2 个盘片(4 个盘面),每个磁道有 63 个扇区,共 1024 个磁道,其容量计算为:容量 = 磁头数(4)× 柱面数(1024)× 每磁道扇区数(63)× 每扇区字节数(512)= 64MB。

2.2 磁盘逻辑结构:从物理结构抽象为线性结构

在 OS 中,我们可以把每一个盘片看作是'磁带',将盘在一起的'磁带'拉成一条直线,形成线性结构。这种地址叫做 LBA(Logical Block Address),即线性地址。

但实际上,磁盘的逻辑结构是由一个个柱面为单位展开的。整个磁盘所有盘面的同一个磁道,即柱面展开。柱面上的每个磁道,扇区个数是一样的,因此我们一个柱面展开后就是一个二维数组,那么整个磁盘就是多张二维的扇区数组表。

那么在磁盘上进行寻址(也就是物理结构上)需要先找到哪一个柱面 (Cylinder),再确定柱面内哪一个磁道 (Head),在确定扇区(Sector),所以就有了 CHS 寻址。

在我们看来,整个磁盘其实全部都是一维数组。每一个扇区都有一个下标,这个下标我们叫做 LBA 地址。这样,我们在 OS 中就可以通过一个数字下标去找到对应的扇区。这就是磁盘的逻辑结构。

因此 OS 只需要使用 LBA 就可以进行寻址了,对于 LBA 地址转成 CHS 地址,CHS 如何转换成为 LBA 地址由磁盘自己做,不需要我们关心。

2.3 寻址方式:从 CHS 到 LBA

要读取一个扇区,需要定位'哪个磁头(盘面)、哪个柱面(磁道)、哪个扇区'——这就是CHS 寻址(Cylinder/Head/Sector)。

但 CHS 有个致命局限:早期系统用 8 位存磁头、10 位存柱面、6 位存扇区,最大支持容量仅 8GB,无法满足大磁盘需求。于是(Logical Block Address)应运而生:把磁盘所有扇区看作一个'一维数组',每个扇区对应一个唯一的'逻辑地址'(数组下标)。磁盘硬件会自动将 LBA 地址转换为 CHS 地址,上层(操作系统)只需用 LBA 即可。

LBA 寻址

CHS 与 LBA 的转换公式:

  • CHS 转 LBA:LBA = 柱面号 × 磁头数 × 每磁道扇区数 + 磁头号 × 每磁道扇区数 + 扇区号 - 1
  • LBA 转 CHS:柱面号 = LBA // (磁头数×每磁道扇区数),磁头号 = (LBA % (磁头数×每磁道扇区数)) // 每磁道扇区数,扇区号 = (LBA % 每磁道扇区数) + 1

对用户而言,磁盘从此变成了'按 LBA 访问的一维数组',文件系统只需基于 LBA 管理数据。

三、Ext 文件系统核心:块、分区与 inode

Ext 文件系统在磁盘基础上,定义了三个关键概念:块、分区、inode,它们是文件存储的'基石'。

3.1 块(Block):文件存取的最小单位

硬盘是典型的'块'设备,操作系统读取硬盘数据的时候,不会一个个扇区地读取,而是一次性连续读取多个扇区。Ext 文件系统将连续的多个扇区组合成'块',作为文件存取的最小单位。

  • 块大小:格式化时指定(常见 4KB,即 8 个扇区),一旦确定不可修改;
  • 块地址:基于 LBA 计算,比如块大小 4KB(8 扇区),则 块号 = LBA // 8,块内扇区 = LBA % 8。

查看块信息:用 stat 命令,比如 stat test.c 会显示 Blocks: 8(表示文件占用 8 个块,共 32KB)、IO Block: 4096(块大小 4KB)。

'块'是文件系统的基本单位。

3.2 分区(Partition):磁盘的'逻辑切片'

磁盘是可以被分成多个分区(partition)的。分区从实质上说就是对硬盘的一种格式化。但是 Linux 的设备都是以文件形式存在。

柱面是分区的最小单位,我们可以利用参考柱面号码的方式来进⾏分区,其本质就是设置每个区的起始柱面和结束柱面号码。

  • 分区本质:划定磁盘的'起始柱面'和'结束柱面',每个分区的块从 0 开始编号;
  • 分区目的:隔离数据(如系统分区和数据分区)、支持不同文件系统;
  • 查看分区:用 fdisk -l 命令,比如 /dev/vda1 是第一个分区,Start 和 End 列显示其扇区范围。

只要知道每个分区的起始和结束柱面号,就可以知道每一个柱面有多少个扇区,那么该分区的大小以及该分区的 LBA 起止范围也就清楚了。

3.3 inode:文件的'身份证'

我们知道 文件 = 属性(元数据) + 内容。Ext 文件系统将'文件属性'单独存储在inode(索引节点) 中,每个文件对应一个唯一的 inode(在同一个分区中),包含:

  • 基础属性:文件类型(普通文件 / 目录 / 链接)、权限(rwx)、所有者(UID/GID)、大小、修改时间(Access/Modify/Change);
  • 内容映射:inode 中的 i_block 数组(Ext2 中 15 个元素),存储'文件内容所在的块号'(直接块、间接块,支持大文件存储);
  • 关键特点:inode 号唯一(以分区为单位),文件名不存储在 inode 中(文件名存在目录的内容里)。

查看 inode:用 ls -li 命令,第一列就是 inode 号。

文件数据都储存在'块'中,那么很显然,我们还必须找到一个地方储存文件的元信息(属性信息),比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做 inode,中文译名为'索引节点'。

因此 Linux 中文件的存储是属性和内容分离存储的。下面是 Linux 源码中的 inode,它是一个结构体:

/* * Structure of an inode on the disk */
struct ext2_inode {
 __le16 i_mode;    /* File mode */
 __le16 i_uid;     /* Low 16 bits of Owner Uid */
 __le32 i_size;    /* Size in bytes */
 __le32 i_atime;   /* Access time */
 __le32 i_ctime;   /* Creation time */
 __le32 i_mtime;   /* Modification time */
 __le32 i_dtime;   /* Deletion Time */
 __le16 i_gid;     /* Low 16 bits of Group Id */
 __le16 i_links_count; /* Links count */
 __le32 i_blocks;  /* Blocks count */
 __le32 i_flags;   /* File flags */
 union {
  struct {
   __le32 l_i_reserved1;
  } linux1;
  struct {
   __le32 h_i_translator;
  } hurd1;
  struct {
   __le32 m_i_reserved1;
  } masix1;
 } osd1; /* OS dependent 1 */
 __le32 i_block[EXT2_N_BLOCKS]; /* Pointers to blocks */
 __le32 i_generation; /* File version (for NFS) */
 __le32 i_file_acl; /* File ACL */
 __le32 i_dir_acl; /* Directory ACL */
 __le32 i_faddr;  /* Fragment address */
 union {
  struct {
   __u8 l_i_frag;      /* Fragment number */
   __u8 l_i_fsize;     /* Fragment size */
   __u16 i_pad1;
   __le16 l_i_uid_high;/* these 2 fields */
   __le16 l_i_gid_high;/* were reserved2[0] */
   __u32 l_i_reserved2;
  } linux2;
  struct {
   __u8 h_i_frag;      /* Fragment number */
   __u8 h_i_fsize;     /* Fragment size */
   __le16 h_i_mode_high;
   __le16 h_i_uid_high;
   __le16 h_i_gid_high;
   __le32 h_i_author;
  } hurd2;
  struct {
   __u8 m_i_frag;      /* Fragment number */
   __u8 m_i_fsize;     /* Fragment size */
   __u16 m_pad1;
   __u32 m_i_reserved2[2];
  } masix2;
 } osd2; /* OS dependent 2 */
};

我们要知道,文件名属性并未纳入到 inode 数据结构内部。并且对于任何文件,它们的内容大小是不同的,但属性大小一定是相同的,因为它们的属性都存放在 inode 结构体中,这里也是一个 inode 中不存放文件名的原因:文件名的大小是不固定的,如果在 inode 中存放文件名,那么 inode 的大小也将不固定。

到目前为止,我们对文件系统的核心有了一定的了解,但是:

  • 我们已经知道硬盘是典型的'块'设备,操作系统读取硬盘数据的时候,读取的基本单位是'块'。'块'又是硬盘的每个分区下的结构,难道'块'是随意的在分区上排布的吗?那要怎么找到'块'呢?
  • 还有就是上面提到的存储文件属性的 inode,又是如何放置的呢?

文件系统就是为了组织管理这些的,下面让我们来看一看 Ext2 文件系统是如何做的。

四、Ext2 文件系统架构:块组与管理结构

文件系统的载体是分区,也就是说每一个分区都有对应的文件系统去进行管理,因此我们只需要了解一个文件系统对一个分区的管理,就可以推广到该文件系统对其他分区的管理。

4.1 宏观认识

所有的准备工作都已经做完,是时候认识下文件系统了。想要在硬盘上储文件,必须先把硬盘格式化为某种格式的文件系统,才能存储文件。文件系统的目的就是组织和管理硬盘中的文件。在 Linux 系统中,最常见的是 Ext2 系列的文件系统。其早期版本为 Ext2,后来又发展出 Ext3 和 Ext4。Ext3 和 Ext4 虽然对 Ext2 进行了增强,但是其核心设计并没有发生变化,我们仍是以较老的 Ext2 作为演示对象。

Ext2 文件系统将整个分区划分成若干个同样大小的块组 (Block Group),每个块组结构相同,类似'小区分栋管理'——既方便维护,又能减少磁头移动(同一块组的块物理位置相近)。

只要能管理一个分区就能管理所有分区,也就能管理所有磁盘文件。

上图中启动块(Boot Block/Sector)的大小是确定的,为 1KB,由 PC 标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。启动块之后才是 ext2 文件系统的开始。

因此我们研究 Ext2 文件系统,主要就在于研究块组,下面让我们来看一下一个块组是由哪几部分构成的:

4.2 超级块(Super Block):文件系统的'说明书'

超级块是文件系统的'大脑',存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:

  • 基础参数:块总数、inode 总数、空闲块/inode 数、块大小、inode 大小;
  • 状态信息:最近挂载时间、最近写入时间、文件系统状态(正常 / 错误);

Super Block 的信息被破坏,可以说整个文件系统结构就被破坏了。超级块在每个块组的开头都有一份拷贝(第一个块组必须有,后面的块组可以没有)。为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常工作,就必须保证文件系统的 super block 信息在这种情况下也能正常访问。所以一个文件系统的 super block 会在多个 block group 中进行备份,这些 super block 区域的数据保持一致。

下面是 Linux 内核源码中存储超级块的结构体:

/* * Structure of the super block */
struct ext2_super_block {
 __le32 s_inodes_count;    /* Inodes count */
 __le32 s_blocks_count;    /* Blocks count */
 __le32 s_r_blocks_count;  /* Reserved blocks count */
 __le32 s_free_blocks_count; /* Free blocks count */
 __le32 s_free_inodes_count; /* Free inodes count */
 __le32 s_first_data_block; /* First Data Block */
 __le32 s_log_block_size;  /* Block size */
 __le32 s_log_frag_size;   /* Fragment size */
 __le32 s_blocks_per_group; /* # Blocks per group */
 __le32 s_frags_per_group;  /* # Fragments per group */
 __le32 s_inodes_per_group; /* # Inodes per group */
 __le32 s_mtime;           /* Mount time */
 __le32 s_wtime;           /* Write time */
 __le16 s_mnt_count;       /* Mount count */
 __le16 s_max_mnt_count;   /* Maximal mount count */
 __le16 s_magic;           /* Magic signature */
 __le16 s_state;           /* File system state */
 __le16 s_errors;          /* Behaviour when detecting errors */
 __le16 s_minor_rev_level; /* minor revision level */
 __le32 s_lastcheck;       /* time of last check */
 __le32 s_checkinterval;   /* max. time between checks */
 __le32 s_creator_os;      /* OS */
 __le32 s_rev_level;       /* Revision level */
 __le16 s_def_resuid;      /* Default uid for reserved blocks */
 __le16 s_def_resgid;      /* Default gid for reserved blocks */
 __le32 s_first_ino;       /* First non-reserved inode */
 __le16 s_inode_size;      /* size of inode structure */
 __le16 s_block_group_nr;  /* block group # of this superblock */
 __le32 s_feature_compat;  /* compatible feature set */
 __le32 s_feature_incompat;/* incompatible feature set */
 __le32 s_feature_ro_compat; /* readonly-compatible feature set */
 __u8 s_uuid[16];          /* 128-bit uuid for volume */
 char s_volume_name[16];   /* volume name */
 char s_last_mounted[64];  /* directory where last mounted */
 __le32 s_algorithm_usage_bitmap; /* For compression */
 __u8 s_prealloc_blocks;   /* Nr of blocks to try to preallocate*/
 __u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */
 __u16 s_padding1;         /* Padding to the end of the block */
 __u8 s_journal_uuid[16];  /* uuid of journal superblock */
 __u32 s_journal_inum;     /* inode number of journal file */
 __u32 s_journal_dev;      /* device number of journal file */
 __u32 s_last_orphan;      /* start of list of inodes to delete */
 __u32 s_hash_seed[4];     /* HTREE hash seed */
 __u8 s_def_hash_version;  /* Default hash version to use */
 __u8 s_reserved_char_pad;
 __u16 s_reserved_word_pad;
 __le32 s_default_mount_opts;
 __le32 s_first_meta_bg;   /* First metablock block group */
 __u32 s_reserved[190];    /* Padding to the end of the block */
};

查看超级块信息:用 dumpe2fs /dev/vda1 | grep -i superblock 命令,可看到超级块的位置和内容。

4.3 GDT(Group Descriptor Table):块组的'目录'

GDT 也叫块组描述符表,用来描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组对应一个描述符,记录该块组的'资源位置':

  • 块位图位置(bg_block_bitmap):该块组的块位图存在哪个块;
  • inode 位图位置(bg_inode_bitmap):该块组的 inode 位图存在哪个块;
  • inode 表位置(bg_inode_table):该块组的 inode 表从哪个块开始;
  • 空闲资源数:bg_free_blocks_count(空闲块数)、bg_free_inodes_count(空闲 inode 数)。
// 磁盘级 blockgroup 的数据结构
/* * Structure of a blocks group descriptor */
struct ext2_group_desc {
 __le32 bg_block_bitmap;   /* Blocks bitmap block */
 __le32 bg_inode_bitmap;   /* Inodes bitmap */
 __le32 bg_inode_table;    /* Inodes table block*/
 __le16 bg_free_blocks_count; /* Free blocks count */
 __le16 bg_free_inodes_count; /* Free inodes count */
 __le16 bg_used_dirs_count; /* Directories count */
 __le16 bg_pad;
 __le32 bg_reserved[3];
};

4.4 块位图(Block Bitmap)与 inode 位图(Inode Bitmap):空闲资源的'登记簿'

位图是'高效管理空闲资源'的工具,用 1 个 bit 表示 1 个资源的状态(0 = 空闲,1 = 占用):

  • 块位图:1 个 bit 对应 1 个块,标记该块组中哪些块已占用、哪些空闲;
  • inode 位图:1 个 bit 对应 1 个 inode,标记该块组中哪些 inode 已占用、哪些空闲。

相信大家在日常中会发现我们下载东西时会比较慢,但是删除时却非常的快,这是因为我们下载内容时需要通过位图来找到空闲的数据块和 inode 号,再对其进行写入,而删除时我们直接将对应的位图标记为空即可,不需要再将对应的数据块以及 inode 清空。

这也是为什么删除文件后,用数据恢复工具能找回文件(只要数据块没被新内容覆盖)—— 本质是'逻辑上释放空间,物理上保留数据'。

4.5 inode 表(Inode Table):文件属性的'仓库'

inode 表是连续的块,存储该块组所有 inode 的具体内容(每个 inode 默认 128 字节或 256 字节)。比如块组的 inode 表从块 100 开始,那么块 100 ~ 块 103 可能存储了 64 个 inode(128 字节 / 个)。

每个 inode 有唯一编号(分区内连续),比如第一个块组的 inode 从 1 开始,第二个块组从'每块组 inode 数 + 1'开始。也就是说在一个分区内每一个 inode 号只有一个。

如上图所示,每一行就代表一个文件的文件属性。

在一个文件的 inode 中存在着 12 个直接指针块以及一级到三级的间接块索引表指针,用来指向该文件所对应的内容存放的数据块。

这里我们要知道,由于每一个块组的大小是相同的,也就是说每一个块组中的数据块的个数是一样的,那么当一个文件的内容过大时,其内容也可能存放到其他块组的数据块中,具体是哪些数据块都存放在索引表中。

4.6 数据块(Data Blocks):文件内容的'存储柜'

数据块是块组中最大的区域,存储文件的实际内容,根据文件类型不同,存储方式也不同:

  • 普通文件:直接存储文件内容(小文件用直接块,大文件用间接块);
  • 目录:存储'文件名→inode 号'的映射表(比如目录 test 的数据块中,有 test.c → 1052007 的记录);
  • 链接文件:硬链接无独立数据块(复用目标文件的 inode),软链接的数据块存储'目标文件路径'。

数据块的块号在一个分区中也是唯一的,也就是说 inode 号和数据块都是跨组编号的,不能跨分区,所以在同一个分区内部 inode 号和块号都是唯一的。

分区之后的格式化操作,就是对分区进行分组,在每个分组中写入 SB、GDT、Block Bitmap、Inode Bitmap 等管理信息,这些管理信息统称:文件系统。

只要知道文件的 inode 号,就能在指定分区中确定是哪一分组,进而在哪一分组确定是哪一个 inode,拿到对应 inode 后文件属性和内容就全部都有了。

下面我们来看一下创建一个文件的步骤:

  1. 存储属性:内核先找到一个空闲的 i 节点(例如 263466)。内核把文件信息记录到其中。
  2. 存储数据:该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到 300,下一块复制到 500,以此类推。
  3. 记录分配情况:文件内容按顺序 300,500,800 存放。内核在 inode 上的磁盘分布区记录了上述块列表。
  4. 添加文件名到目录:新的文件名 abc。linux 如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件。文件名和 inode 之间的对应关系将文件名和文件的内容及属性连接起来。

五、目录、路径解析与挂载:如何找到文件?

我们平时用 /home/hcy/test.c 这样的路径访问文件,但 Linux 底层是如何通过路径找到磁盘上的扇区?这需要理解'目录的本质''路径解析'和'挂载'三个关键点。

5.1 目录的本质:文件名与 inode 的映射表

我们上面说过,inode 中只存放文件的属性,并不存放文件名,但是我们在访问文件时都是使用的文件名,基本上不会使用 inode 号,这是为什么呢?

这需要我们对目录有一个清晰的认识!那么在 Linux 中是如何看待目录的呢?我们知道在 Linux 下一切皆文件,目录也是一种文件,那么它也有对应属性和内容,也就是说目录也有对应的 inode 和数据块,目录的属性不必多说,关键在于目录文件的内容:

  • 目录文件的内容保存的是文件名和 inode 号的映射关系!

也就是说,文件的文件名都保存在它所在目录文件的内容中!

比如 ls -li /home/hcy 显示:

这意味着 /home/hcy 的数据块中,有两条记录:test.c → 525789、demo1 → 525787(demo1 是目录,其 inode 指向自己的 inode 表和数据块)。

所以当我们访问文件时,必须打开当前目录,根据文件名来获得对应的 inode 号,然后进行文件访问,因此我们想要访问文件就必须要知道当前目录,本质是必须能打开当前工作目录文件,查看目录文件的内容。

5.2 路径解析和路径缓存:从根目录开始的'寻宝游戏'

我们已经知道访问文件需要打开当前工作目录文件,查看当前工作目录文件的内容,但是当前工作目录不也是一个文件吗?我们访问当前工作目录不也是只知道当前工作目录的文件名吗?要访问它也得知道当前工作目录的 inode 啊。所以我们也要打开当前目录的上级目录,同理,上级目录也有它自己的上级目录,这个过程类似于递归,出口是'/'根目录。

实际上,任何文件都有其路径,当访问目标文件,比如:/home/hcy/test.c 都要从根目录开始,依次打开每一个目录,根据目录名,依次访问每个目录下指定的目录,直到访问到 test.c。这个过程叫做 Linux 路径解析。

这也就是访问文件必须有路径的原因,我们可以用相对路径和绝对路径两种方式来进行访问,其实它们的本质是一样的:能使用相对路径访问是因为我们要访问的文件的 PCB 中存放着当前工作目录 cwd,当我们使用相对路径时 OS 会自动将其拼接成绝对路径。

路径 /home/hcy/test.c 的解析过程,就像从'小区大门'找'某栋楼某户':

  1. 根目录(/)的 inode 号是固定的(通常是 2),系统开机后已知;
  2. 打开根目录的数据块,找到 home → inode-A 的记录;
  3. 打开 home 的 inode(inode-A),找到其数据块位置,从中找到 whb → inode-B 的记录;
  4. 打开 hcy 的 inode(inode-B),找到其数据块位置,从中找到 test.c → inode-C 的记录;
  5. 打开 test.c 的 inode(inode-C),从 i_block 数组找到存储内容的数据块,读取文件。

这就是'路径解析'的核心 —— 从根目录开始,逐层解析每个目录的'文件名→inode'映射,直到找到目标文件的 inode。

根目录固定文件名,inode 号,无需查找,系统开机之后就必须知道

Linux 的根目录及系统缺省目录构成了目录树的基础骨架,用户新建的目录则对其进行扩展,两者共同形成了完整的目录树;而路径是基于这个目录树,描述文件 / 目录位置的访问标识 —— 因此系统和用户共同构建了 Linux 路径结构的基础。

思考一下:在 Linux 的磁盘中真的存在目录树吗?也就是目录的树状结构。答案是不存在的,在磁盘中只有文件的属性和内容。那么在我们访问文件时,是不是就意味着每一次访问都需要从根目录开始进行路径解析呢?从原则上来说是这样的,但是这种方式效率太过低下,因此 OS 会缓存历史路径结构。

所以在 Linux 中目录的概念是由 OS 产生的,OS 自己在内存中进行路径维护。

Linux 中,在内核中维护树状路径结构的内核结构体叫做:struct dentry

struct dentry {
 atomic_t d_count;
 unsigned int d_flags; /* protected by d_lock */
 spinlock_t d_lock; /* per dentry lock */
 struct inode *d_inode; /* Where the name belongs to - NULL is negative */
 struct hlist_node d_hash; /* lookup hash list */
 struct dentry *d_parent; /* parent directory */
 struct qstr d_name;
 struct list_head d_lru; /* LRU list */
 union {
  struct list_head d_child; /* child of parent list */
  struct rcu_head d_rcu;
 } d_u;
 struct list_head d_subdirs; /* our children */
 struct list_head d_alias; /* inode alias list */
 unsigned long d_time; /* used by d_revalidate */
 struct dentry_operations *d_op;
 struct super_block *d_sb; /* The root of the dentry tree */
 void *d_fsdata; /* fs-specific data */
#ifdef CONFIG_PROFILING
 struct dcookie_struct *d_cookie; /* cookie, if any */
#endif
 int d_mounted;
 unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
}

对于 dentry,我们需要注意以下几点:

  • 每个文件其实都要有对应的 dentry 结构,包括普通文件。这样所有被打开的文件,就可以在内存中形成整个树形结构
  • 整个树形节点也会隶属于 LRU(Least Recently Used,最近最少使用) 结构中,进行节点淘汰
  • 整个树形节点也会隶属于 Hash,方便快速查找

更重要的是,这个树形结构,整体构成了 Linux 的路径缓存结构,打开访问任何文件,都在先在这棵树下根据路径进行查找,找到就返回属性 inode 和内容,没找到就从磁盘加载路径,添加 dentry 结构,缓存新路径。

这也是为什么当我们在整个根目录下查找某些文件时第一次很慢,第二次查找相同的文件时就会很快的原因。

5.3 挂载:分区与目录的'绑定'

我们已经能够根据 inode 号在指定分区找到对应的文件,也能根据目录文件的内容通过文件名找到指定的 inode,也就是说在指定的分区内,我们可以为所欲为了。可是问题来了,我们该怎么知道我们再哪一个分区呢?这里我们直接说结论:

分区写入文件系统,无法直接使用,需要和指定的目录关联,进行挂载才能使用。所以,可以根据访问目标文件的'路径前缀'准确判断在哪一个分区。

也就是说我们需要将分区挂载到目录上才可以使用这个分区(单单的只对分区初始化是不能直接使用的),通过进入这个目录,就相当于进入这个分区。

分区(如 /dev/vda1)本身是'孤立的逻辑磁盘',必须通过挂载(mount) 与某个目录(如 /mnt)关联,才能被访问。

  • 挂载本质:将分区的文件系统'挂载'到目录上,此后访问该目录,实际是访问分区的文件系统;
  • 挂载命令:mount -t ext4 /dev/vda1 /mnt(将 /dev/vda1 分区以 Ext4 格式挂载到 /mnt);
  • 卸载命令:umount /mnt(解除关联);

查看挂载:df -h 命令,显示所有已挂载的分区和关联目录。

比如用 dd if=/dev/zero of=disk.img bs=1M count=5 创建一个 5MB 的模拟磁盘,用 mkfs.ext4 disk.img 格式化为 Ext4,再 mount disk.img /mnt/mydisk,此时 /mnt/mydisk 就对应这个模拟磁盘的文件系统。

至此,对于文件系统我们已经基本上了解了,下面我们通过一张图来进行一下总结:

六、软硬链接:文件的'别名'机制

有时我们需要给文件创建'别名',Linux 提供了两种链接方式:硬链接和软链接,它们的底层逻辑完全不同。

6.1 硬链接:inode 相同的'亲兄弟'

硬链接是'给文件的 inode 多一个文件名映射'—— 多个文件名对应同一个 inode,本质是'同一文件的不同别名'。

  • 创建命令:ln 目标文件 硬链接名(如 ln test.c test.hard);
  • 核心特点:
    1. 硬链接与目标文件的 inode 号相同(ls -li 可验证);
    2. 硬链接数记录在 inode 的 i_links_count 中(创建硬链接时 + 1,删除时 - 1,为 0 时才释放磁盘);
    3. 不能跨分区(inode 号仅在分区内唯一);
    4. 不能链接目录(防止循环引用,如 ln /home /home/link 会报错)。

用途:文件备份(删除原文件,硬链接仍能访问)、系统特殊目录(. 是当前目录的硬链接,.. 是上级目录的硬链接,ls -li . .. 可看到它们的 inode 号对应目录的 inode)。

红色框圈住的就是每个文件对应的引用计数,也就是硬链接的数量,对于目录文件 demo1 来说,由于在其目录下还有. 目录指向自己,所以它的引用计数是 2。因此我们还可以通过引用计数来计算一个目录下有几个目录:size = 当前目录的引用计数 - 2:

6.2 软链接:存储路径的'快捷方式'

软链接是'独立的文件'—— 其 inode 与目标文件不同,数据块中存储的是'目标文件的路径'(类似 Windows 的快捷方式)。

  • 创建命令:ln -s 目标文件 软链接名(如 ln -s test.c test.soft);
  • 核心特点:
    1. 软链接的 inode 号与目标文件不同(ls -li 显示 lrwxrwxrwx,l 表示软链接);
    2. 目标文件删除后,软链接变成'无效链接'(访问时提示'没有那个文件或目录');
    3. 可跨分区(只需存储目标文件的绝对路径);
    4. 可链接目录(如 ln -s /home/whb/doc /home/whb/link_doc)。

用途:简化路径(如 ln -s /usr/local/bin/python3 /usr/bin/python)、跨分区访问文件。

在 windows 中我们经常使用的快捷方式就是一种软连接,通过删除快捷方式并不会影响对应的文件。

6.3 软硬链接对比

特性硬链接(Hard Link)软链接(Symbolic Link)
inode 号与目标文件相同独立 inode(不同)
数据块内容无独立数据块(复用目标文件)存储目标文件路径
跨分区不支持支持(绝对路径)
链接目录不支持支持
目标文件删除后仍能访问(链接数 > 0)无效(断链)
本质同一文件的别名独立文件(指向目标路径)

七、总结:Ext 文件系统的设计思想

Ext 系列文件系统的成功,源于其清晰的分层设计和高效的资源管理:

  1. 分层管理:磁盘→分区→块组→块/inode,从宏观到微观,既便于维护,又减少磁头移动;
  2. 分离存储:inode 存属性,数据块存内容,目录存'文件名→inode'映射,解耦不同职责;
  3. 高效复用:位图管理空闲资源(O (1) 查找),间接块支持大文件,硬链接复用 inode;
  4. 冗余备份:超级块、GDT 多块组备份,防止单点故障;
  5. 用户友好:通过路径、文件名隐藏底层复杂的 inode 和块地址,同时提供软硬链接灵活管理文件。

理解 Ext 文件系统,不仅能帮我们解决'文件找不到'、'磁盘满了'等实际问题,更能让我们看透 Linux'一切皆文件'的设计哲学 —— 无论是普通文件、目录,还是设备(如 /dev/sda),本质都是通过 inode 和数据块管理,用统一的接口访问。

下次当你执行 touch test.txt 时,不妨想想:Linux 在底层创建了一个 inode,分配了数据块,在当前目录的数据块中添加了 test.txt → inode 号 的记录 —— 这就是文件'住进'磁盘的全过程。

目录

  1. Linux Ext 系列文件系统原理:从磁盘结构到软硬链接
  2. 前言
  3. 一、开篇:为什么需要文件系统?
  4. 二、磁盘基础:从物理结构到逻辑地址
  5. 2.1 磁盘物理结构:盘片、磁道、扇区与柱面
  6. 2.2 磁盘逻辑结构:从物理结构抽象为线性结构
  7. 2.3 寻址方式:从 CHS 到 LBA
  8. 三、Ext 文件系统核心:块、分区与 inode
  9. 3.1 块(Block):文件存取的最小单位
  10. 3.2 分区(Partition):磁盘的“逻辑切片”
  11. 3.3 inode:文件的“身份证”
  12. 四、Ext2 文件系统架构:块组与管理结构
  13. 4.1 宏观认识
  14. 4.2 超级块(Super Block):文件系统的“说明书”
  15. 4.3 GDT(Group Descriptor Table):块组的“目录”
  16. 4.4 块位图(Block Bitmap)与 inode 位图(Inode Bitmap):空闲资源的“登记簿”
  17. 4.5 inode 表(Inode Table):文件属性的“仓库”
  18. 4.6 数据块(Data Blocks):文件内容的“存储柜”
  19. 五、目录、路径解析与挂载:如何找到文件?
  20. 5.1 目录的本质:文件名与 inode 的映射表
  21. 5.2 路径解析和路径缓存:从根目录开始的“寻宝游戏”
  22. 5.3 挂载:分区与目录的“绑定”
  23. 六、软硬链接:文件的“别名”机制
  24. 6.1 硬链接:inode 相同的“亲兄弟”
  25. 6.2 软链接:存储路径的“快捷方式”
  26. 6.3 软硬链接对比
  27. 七、总结:Ext 文件系统的设计思想
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Python 多进程开销解析与 IPC 优化实战
  • Claude 部署与 Copilot 反向代理配置实战
  • C++核心知识点梳理:类型兼容、多继承与虚基类
  • Q-Learning 强化学习无人机三维路径规划原理与 MATLAB 实现
  • GitHub Copilot 配置最佳实践
  • Stable Diffusion v1.5 环境搭建与使用技巧
  • EhViewer:安卓开源漫画阅读器安装与使用指南
  • 算法基础:贪心算法入门 (上)
  • 关闭 Visual Studio 2022 中 Copilot 的 AI 自动代码补全功能
  • SkyWalking 全链路监控实战:Spring Cloud、Dubbo、RocketMQ 及 ShardingSphere 集成
  • OpenClaw Linux 本地 AI 智能体部署指南
  • Gitee 本地账号与密码配置步骤
  • RVC-WebUI 语音转换实战:从零搭建 AI 音色克隆环境
  • SeargeSDXL AI 绘画工作流使用指南
  • Face Fusion 与 Stable Diffusion 集成方案及创意玩法解析
  • 雷达信号处理中的恒虚警率(CFAR)技术详解
  • Ubuntu 下载、安装及软件管理详解
  • FastGPT 集成 MCP 协议构建工具增强型智能体
  • Neo4j Desktop 2 安装与使用指南
  • AI 驱动的接口测试全流程自动化实现

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online