【Linux笔记】Linux磁盘文件系统
发布时间:2026/10/1 2:43:10来源:尧图网络
一、磁盘1.1 磁盘的认识磁盘是计算机中用于长期存储数据的外部存储设备相比内存RAM磁盘具有断电不丢失数据的特性但读写速度远低于内存。主要分类 - 机械硬盘HDD, Hard Disk Drive基于磁性存储依靠机械臂在高速旋转的盘片上读写数据。容量大、价格低、寿命长但抗震性差、速度相对较慢。 - 固态硬盘SSD, Solid State Drive基于闪存芯片NAND Flash无机械结构读写速度快、抗震、功耗低但价格相对高、写入寿命有限。 - 混合硬盘SSHD结合 HDD 大容量与 SSD 缓存的方案。1.2 磁盘的物理结构磁盘结构示意图部件作用盘片Platter存储数据的圆形金属片通常涂有磁性介质一个硬盘有 1~N 张主轴Spindle带动盘片高速旋转常见 5400 / 7200 / 10000 RPM磁头Head读取/写入盘片数据每个盘面需要一个磁头磁头臂Actuator Arm承载磁头在盘片径向方向移动音圈马达VCM驱动磁头臂摆动控制电路处理 I/O 指令、缓存数据1.3 磁盘的存储结构盘片结构示意图核心结构盘片的讲解盘片结构自顶向下看 - 磁道Track盘片上的同心圆 - 扇区Sector每个磁道被划分为若干弧段是磁盘的最小读写单位通常 512 字节传统标准现代多为 4K 字节 - 柱面Cylinder多个盘片上相同半径的磁道组成柱面1.4 磁盘的逻辑结构操作系统为磁盘提供了一套逻辑视图使得用户和软件无需关心物理细节即可寻址。早期使用 CHS 寻址现代使用 LBA 寻址。1.4.1 CHS 地址CHS 是最早期的磁盘寻址方式直接对应磁盘的物理结构用三个维度来定位一个扇区C (Cylinder) ── 柱面号定位到哪个半径位置 H (Head) ── 磁头号定位到哪个盘面 S (Sector) ── 扇区号定位到磁道上的哪一段 备注其中的磁头号可以对应磁盘上的盘面号CHS地址定位过程分析定位过程 1. 通过 Head 选择盘面哪一张盘片的哪一面 2. 通过 Cylinder 控制磁头臂移动到对应的同心圆 3. 等待盘片旋转使目标扇区转到磁头下方 4. 读/写该扇区CHS 的编号起始值C柱面 起始值为0 H磁头 起始值为0 S扇区 起始值为 1 (历史遗留从1开始因为0号保留作无效标记)磁盘 数据填充顺序第一优先扇区号 S 递增盘片旋转最快 第二优先磁头号 H 递增电子切换磁头很快 第三优先柱面号 C 递增机械移动磁头臂最慢假设一个硬盘有 2 个柱面C0, C1 2 个磁头H0, H1 每磁道 3 个扇区S1, 2, 3 填充顺序如下所示 柱面0, 磁头0盘面0: [S1] [S2] [S3] ← 先填满一条磁道的扇区 柱面0, 磁头1盘面1: [S1] [S2] [S3] ← 再切换磁头同柱面内 柱面1, 磁头0盘面0: [S1] [S2] [S3] ← 最后切换柱面移动磁头臂 柱面1, 磁头1盘面1: [S1] [S2] [S3]1.4.2 LBA 地址LBA 是 (逻辑块Logical Block Addressing 的缩写是现代磁盘统一采用的线性寻址方式。基本思想将磁盘上的柱面进行展开其中所有扇区从 0 开始顺序编号形成一个连续的线性地址空间。假设一个硬盘有 A. 2 个柱面C0, C1 B. 4 个磁头H0,H1,H2,H3 C. 每磁道 4 个扇区S1, 2, 34 柱面0, 磁头0 (盘面0): CHS(0,0,1) → LBA 0 CHS(0,0,2) → LBA 1 CHS(0,0,3) → LBA 2 CHS(0,0,4) → LBA 3 柱面0, 磁头1 (盘面1): CHS(0,1,1) → LBA 4 CHS(0,1,2) → LBA 5 CHS(0,1,3) → LBA 6 CHS(0,1,4) → LBA 7 柱面0, 磁头2 (盘面2): CHS(0,2,1) → LBA 8 CHS(0,2,2) → LBA 9 CHS(0,2,3) → LBA 10 CHS(0,2,4) → LBA 11 柱面0, 磁头3 (盘面3): CHS(0,3,1) → LBA 12 CHS(0,3,2) → LBA 13 CHS(0,3,3) → LBA 14 CHS(0,3,4) → LBA 15 柱面1, 磁头0 (盘面0): CHS(1,0,1) → LBA 16 CHS(1,0,2) → LBA 17 CHS(1,0,3) → LBA 18 CHS(1,0,4) → LBA 19 柱面1, 磁头1 (盘面1): CHS(1,1,1) → LBA 20 CHS(1,1,2) → LBA 21 CHS(1,1,3) → LBA 22 CHS(1,1,4) → LBA 23 柱面1, 磁头2 (盘面2): CHS(1,2,1) → LBA 24 CHS(1,2,2) → LBA 25 CHS(1,2,3) → LBA 26 CHS(1,2,4) → LBA 27 柱面1, 磁头3 (盘面3): CHS(1,3,1) → LBA 28 CHS(1,3,2) → LBA 29 CHS(1,3,3) → LBA 30 CHS(1,3,4) → LBA 311.4.3 LBA 与 CHS 的转换参数符号说明C : 柱面号 H : 磁头号 S : 扇区号A. LBA地址 → CHS地址已知 LBA 地址 转换为 CHS 地址1. 柱面号C LBA / (磁头总数 * 每磁道的扇区数) 2. 磁头号H LBA % (磁头总数 * 每磁道的扇区数) / 每磁道的扇区数 3. 扇区号S LBA % 每磁道的扇区数 1B. CHS地址 → LBA地址已知 CHS地址 转换为 LBA地址LBA 地址 C * (磁头总数 * 每磁道的扇区数) H * 每磁道的扇区数 S - 1二、磁盘文件系统2.1 磁盘文件系统的认识操作系统的一个核心职责就是管理外存主要是磁盘而文件系统正是操作系统中负责管理磁盘数据的子系统。用户通过文件系统来创建、读写、删除文件而不需要关心数据在磁盘上的物理存放位置。若要深入理解文件系统的运作机制需要逐步引入三个关键概念块、分区 、inode。2.1.1 块的引入A. 为什么需要块磁盘是一种机械或固态设备其读写操作具有以下特点寻道时间磁头移动到目标磁道和旋转延迟盘片旋转到目标扇区占据了访问磁盘的大部分时间。磁盘硬件的最小读写单位是扇区sector通常为512 字节新型磁盘为 4KB。事实上操作系统不会以扇区为单位来读写磁盘而是将若干个连续的扇区组合成一个更大的逻辑单位——块block作为磁盘 I/O 的基本单位。B. 块的定义块Block操作系统对磁盘进行读写操作的基本逻辑单位通常由若干个连续的扇区组成。常见的块大小为4KB即 8 个 512 字节的扇区或 1 个 4KB 的扇区其中块大小可以在格式化文件系统时指定。C. 块带来的好处- 减少 I/O 次数一次读写一个块4KB比逐字节操作高效得多。 - 简化管理操作系统只需追踪哪些块被使用、哪些空闲而不必追踪每个扇区。 - 对齐硬件块的大小通常为扇区大小的整数倍便于与硬件协作。2.1.2 分区的引入A. 为什么需要分区一块物理磁盘可以被整个当作一个大的存储空间来使用但在实际使用中我们通常将磁盘划分为多个独立的逻辑区域原因如下多操作系统共存一台计算机可能需要同时安装 Windows 和 Linux不同操作系统的文件系统格式不同需要各自的独立空间。数据隔离与安全将系统文件和用户数据放在不同分区重装系统时不会丢失用户数据。管理便利不同分区可以设置不同的权限、配额和备份策略。性能优化将频繁访问的数据放在磁盘外圈传输速率更高的分区中。B. 分区的定义分区Partition将一块物理磁盘在逻辑上划分为若干个独立的区域每个分区在操作系统看来就像一块独立的磁盘可以各自格式化为不同的文件系统。C. 分区的典型结构/dev/sda ← 整块物理磁盘 ├── /dev/sda1 ← 分区1/boot引导分区约 512MB ├── /dev/sda2 ← 分区2/根分区系统文件 ├── /dev/sda3 ← 分区3/home用户数据 └── /dev/sda4 ← 分区4swap交换空间2.1.3 inode的引入A. 为什么需要inode在文件系统中文件 内容 属性 所以对于一个文件而言有两部分信息需要管理 - 文件的元数据属性文件大小、权限、所有者、创建时间、修改时间、数据所在块的位置等。 - 文件的实际数据内容文件的内容本身存放在数据块中。如果将元数据属性和数据内容混在一起存储管理会非常混乱。Linux 文件系统采用了一种精妙的设计将文件的元数据与文件数据分开存储。元数据属性存放在一个专门的数据结构中这就是inodeindex node索引节点。B.inode的定义inode索引节点文件系统中存储文件或目录元数据的固定大小的数据结构每个文件或目录对应一个唯一的inode。C.inode中存储的信息inode中重要的存储信息如下所示信息说明文件类型普通文件、目录、符号链接、设备文件等文件权限读/写/执行权限rwx所有者信息UID用户ID、GID组ID文件大小以字节为单位时间戳atime访问时间、mtime修改时间、ctime状态变更时间硬链接计数指向该inode的目录项数量数据块指针指向存放文件实际数据的磁盘块编号注意inode中 不存储文件名。文件名存储在目录的数据块中通过目录项dentry将文件名映射到inode编号。磁盘上的索引节点结构体如下/* * 磁盘上索引节点inode的结构 */ struct ext2_inode { __le16 i_mode; /* 文件模式 */ __le16 i_uid; /* 所有者UID的低16位 */ __le32 i_size; /* 文件大小字节 */ __le32 i_atime; /* 访问时间 */ __le32 i_ctime; /* 创建时间 */ __le32 i_mtime; /* 修改时间 */ __le32 i_dtime; /* 删除时间 */ __le16 i_gid; /* 组ID的低16位 */ __le16 i_links_count; /* 链接计数 */ __le32 i_blocks; /* 块计数 */ __le32 i_flags; /* 文件标志 */ .../*其他字段*/ };三、ext2 磁盘文件系统3.1 宏观认识 ext2 文件系统操作系统将磁盘划分为多个分区不同对应的分区文件系统不同磁盘级别的文件系统主要引用的是ext系列文件系统。以ext2文件系统为例理解磁盘级的文件系统。ext2文件系统将整个磁盘分区划分为若干个大小相同的块组Block Group。ext2文件系统将每个块组划分为----------------------------------------------------------------------------- | Super Block | GDT | Block Bitmap | Inode Bitmap | Inode Table | Data Blocks | ----------------------------------------------------------------------------- ↓ ↓ ↓ ↓ ↓ ↓ 超级块 组描述符 块位图 索引节点位图 索引节点表 数据块备注数据块Data Blocks和 索引节点 Inode 是可以跨块组Block Group。3.2 块组内部构成3.2.1Data Blocks--数据块数据块 存放文件的实际内容也就是文本、二进制数据。不同文件类型的数据块存储内容不同- 普通文件数据块中存储文件的实际内容文本、二进制数据等。 - 目录文件数据块中存储目录项directory entry。 每个目录项包含 - 文件名字符串 - 对应的 inode 编号 - 这样就把文件名和inode关联了起来。3.2.2Inode Table--索引节点表Inode Table存放当前块组中所有inode的具体内容其中每个inode记录了一个文件的 全部元数据属性信息。Inode Table核心特征预先分配格式化时就确定了大小之后不能动态扩展连续存储在磁盘上占据一段连续的扇区定长记录每个inode占用固定字节数ext2 默认 128 字节编号寻址通过inode号数组下标直接定位O(1) 时间复杂度Inode Table表如下所示 --------------------------------------------------- | Inode Table | --------------------------------------------------- | 索引 | 内容 | --------------------------------------------------- | struct ext2_inode 0 | (通常是保留的不使用) | --------------------------------------------------- | struct ext2_inode 0 | (通常是保留的不使用) | --------------------------------------------------- | struct ext2_inode 1 | (通常也是保留的) | --------------------------------------------------- | struct ext2_inode 2 | (根目录 inode) | --------------------------------------------------- | struct ext2_inode 3 | (lostfound 目录) | --------------------------------------------------- | struct ext2_inode 4 | (用户文件/目录) | --------------------------------------------------- | struct ext2_inode 5 | (用户文件/目录) | --------------------------------------------------- | ... | | --------------------------------------------------- | struct ext2_inode N | (最后一个 inode) | ---------------------------------------------------3.2.3Inode Bitmap--索引节点位图Inode Bitmap它是一个比特数组bit array每个bit位严格对应索引节点表中的一个inode用来记录Inode Tbale中已用/空闲状态总览表。假设某个块组有 8192 个 inode Inode Bitmap 大小 8192 bits 1024 bytes 1 KB 位编号: 0 1 2 3 4 5 6 7 ... 8191 比特值: 1 1 1 0 1 0 0 1 ... 0 ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ 含义: 已用 已用 已用 空闲 已用 空闲 空闲 已用 空闲 对应关系 bit[0] 1 → inode 1 已被占用 bit[3] 0 → inode 3 空闲可用 bit[5] 0 → inode 5 空闲可用3.2.4Block Bitmap--数据块位图Block Bitmap 是一个纯粹的比特数组bit array但它的每一位代表一个数据块Data Block 的使用状态。假设块大小Block Size为 4KB某个块组有 32768 个数据块 Block Bitmap 大小 32768 bits 4096 bytes 4 KB正好占满一个数据块 位编号: 0 1 2 3 4 5 6 7 ... 32767 比特值: 1 1 0 0 1 1 1 0 ... 1 ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ 含义: 已用 已用 空闲 空闲 已用 已用 已用 空闲 已用 对应关系 bit[0] 1 → 第 0 号数据块已被占用可能存着某个文件的内容 bit[2] 0 → 第 2 号数据块空闲可以分配给新写入的数据3.2.5GDT--组描述符GDT整体描述一整个块组Block group的信息。struct ext2_group_desc { __u32 bg_block_bitmap; // 本块组块位图所在块号 __u32 bg_inode_bitmap; // 本块组inode 位图所在块号 __u32 bg_inode_table; // 本块组inode 表起始块号 __u16 bg_free_blocks_count; // 本块组空闲块数 __u16 bg_free_inodes_count; // 本块组空闲 inode 数 __u16 bg_used_dirs_count; // 本块组目录数 __u16 bg_pad; // 填充对齐 __u32 bg_reserved[3]; // 保留 // ... 后续版本扩展字段 };3.2.6Super Block--超级块Super Block整体描述一整个分区Partion中的信息。struct ext2_super_block { __u32 s_inodes_count; // 总 inode 数 __u32 s_blocks_count; // 总块数 __u32 s_r_blocks_count; // 保留块数保留给 root __u32 s_free_blocks_count; // 空闲块数 __u32 s_free_inodes_count; // 空闲 inode 数 __u32 s_first_data_block; // 第一个数据块号通常 1 __u32 s_log_block_size; // 块大小 1024 s_log_block_size __u32 s_log_frag_size; // 碎片大小 __u32 s_blocks_per_group; // 每块组块数 __u32 s_frags_per_group; // 每块组碎片数 __u32 s_inodes_per_group; // 每块组 inode 数 __u32 s_mtime; // 最后挂载时间 __u32 s_wtime; // 最后写入时间 __u16 s_mnt_count; // 挂载次数 __u16 s_max_mnt_count; // 强制检查前的最大挂载次数 __u16 s_magic; // 魔数 0xEF53识别 ext2 的标识 __u16 s_state; // 文件系统状态 __u16 s_errors; // 错误处理方式 __u16 s_minor_rev_level; // 次版本号 __u32 s_lastcheck; // 最后检查时间 __u32 s_checkinterval; // 检查间隔 __u32 s_creator_os; // 创建者操作系统 __u32 s_rev_level; // 主版本号 __u16 s_def_resuid; // 保留块默认 UID __u16 s_def_resgid; // 保留块默认 GID // ... 后续版本扩展字段 };注意每个块组Block Group都冗余存储超级块Super Block让超级块所在的块组损坏时可以用其他块组的副本恢复这是ext系列文件系统的抗损机制。3.3Inode管理机制3.3.1 判断空闲的Inode文件系统在内存中维护着每个块组Block Group的 块组描述符表GDT其中明确记录了该组当前空闲的Inode数量。判断时内核无需读取磁盘位图直接遍历内存中的 GDT 表检查各组的空闲计数是否大于零即可。3.3.2 分配Inode的完整流程步骤 1选择目标块组分配策略注重数据局部性 - 普通文件 优先分配在父目录所在的块组Block Group使文件与目录在物理位置上接近减少后续操作的寻道时间。 - 目录文件 会寻找空闲 Inode 和 空闲数据块 均较充裕的块组为其预留扩展空间。步骤 2位图占位选中指定的块组Block Group后 1. 将该组的 Inode 位图从磁盘读入内存 2. 扫描位图找到第一个空闲位值为 0 3. 将该位置标记为 1表示已占用 4. 更新内存 GDT 中的空闲 Inode 计数减 1 5. 将修改标记为“脏页”等待后台线程异步写回磁盘步骤 3初始化Inode结构计算出该位对应的全局Inode编号后在Inode Table中找到对应槽位填入文件的基本属性权限、时间戳、所有者等。3.3.3 释放Inode的流程删除文件时释放Inode 1. 根据 Inode 编号计算其所属块组Block Group及位图Inode Bitmap中的偏移位置 2. 读取该组的位图( Inode Bitmap)到内存 3. 将对应bit位进行清零标记为空闲 4. 更新 GDT 中的空闲 Inode 计数加 1 5. 标记为脏页异步写回到磁盘中。3.4Data Blocks管理机制3.4.1 空闲数据块的判断方式文件系统在内存中维护着每个块组Block Group的 GDT组描述符其中明确记录了该组当前空闲数据块的数量。判断时内核无需读取磁盘上的块位图直接遍历内存中的 GDT 表检查各组的空闲数据块计数是否大于零即可。3.4.2 分配数据块的完整流程当文件需要写入数据时例如向file.txt追加内容内核会调用ext2_new_blocks()函数分配新的数据块。步骤 1选择目标块组分配策略Ext2 采用局部性优先的分配策略 - 优先选择与 Inode 所在块组相同的块组使文件的元数据Inode和数据Data Blocks在物理位置上接近减少后续读写操作的寻道时间。 - 将逻辑上相邻的文件数据分配到磁盘上物理相邻的块中。 - 将碎片分配给尽量少的文件从全局上减少磁盘碎片。步骤 2在块位图中“占坑”选中目标块组Block Group后 1. 将该组的块位图Block Bitmap从磁盘读入内存 2. 在块位图Block Bitmap中扫描寻找第一个bit位为 0 的空闲位 3. 将该bit位置为 1标记为已占用 4. 同时更新内存 GDT 中的空闲数据块计数减 1 5. 将修改标记为“脏页”等待后台线程异步写回磁盘步骤 3关联到Inode1. Date Blocks分配成功后内核将分配到的块号填入 Inode 的 i_block[] 数组中更新当前文件所占据数据块的块号位置 2. 更新 Inode 中的 i_blocks 字段更新当前文件所占据的数据块数。3..4.3 释放数据块的流程当文件被删除或截断时内核会调用ext2_free_blocks()函数释放不再使用的数据块。1. 根据要释放的数据块号计算其所属的块组Block Group 2. 读取该组的块位图Block Bitmap到内存 3. 将对应的bit位置为0标记为空闲 4. 更新 GDT 中的空闲数据块计数加 1 5. 同时更新 Inode 中的 i_blocks 字段减少相应数量 6. 标记为脏页异步写回到磁盘中特别注意释放数据块时数据块中原有的文件内容不会被清除。位图标记为空闲后旧数据依然保留在磁盘上直到下次被新文件覆盖。这也是数据恢复的基本原理。3.5 目录与文件名3.5.1 目录是一种文件在 ext2 文件系统中目录本质上是一种特殊的文件。它与普通文件的区别仅在于 - 普通文件的数据块data block中存储的是用户数据 - 目录文件的数据块中存储的是目录项directory entry即一组 文件名 - inode 号 的映射关系。3.5.2 目录的数据块内容 ---dentry假设有一个目录/home/user/它的目录的数据块里存储的内容大致是这样的这张表叫作 目录项Directory Entry简称 ------------------------------------- | 文件名 (name) | inode 编号 (inode) | ------------------------------------- | . | 1048577 | -- 当前目录自身 | .. | 1048576 | -- 父目录 | test.txt | 20000 | | photo.jpg | 20001 | | project | 20002 | -- 这是一个子目录 | notes.md | 20003 | -------------------------------------3.5.3inode与 文件名的分离设计inode索引节点中不存储文件名。inode存储的重要信息 1. 文件类型普通文件、目录、符号链接等 2. 文件权限rwx 3. 文件所有者uid/gid 4. 文件大小 5. 时间戳atime/mtime/ctime 6. 指向数据块的指针12个直接指针 1个间接 1个双重间接 1个三重间接 7. 链接计数link count文件名只存在于它所在目录的数据块中的目录项里。1. 同一个 inode 可以被多个目录项引用这就是硬链接的本质。 2.通过路径 /home/user/file.txt 访问文件时系统需要逐级解析路径在每一级目录的数据块中查找文件名inode号映射 最后再通过 inode 号读取 inode 信息。3.5.4 为什么文件名与inode分离A. 硬链接因为inode里不存文件名所以 多个文件名可以指向同一个inode目录 /home/user/ 的数据块 --------------------------- | 文件名 | inode | --------------------------- | report.txt | 20000 | -- | report_backup | 20000 | -- -- 两个名字指向同一个 inode --------------------------- | v Inode 20000 文件大小、权限、数据块指针……B. 文件名长度不受inode限制1. Inode 的大小是固定的通常 128 / 256 字节里面要塞大量元数据(属性数据)。 2. 如果把文件名也放进去文件名长度就会受到严格限制把文件名放在目录的数据块中文件名长度的限制就取决于目录数据块的格式。C. 一个文件可以有不同名字出现在不同目录中目录 /etc/ 的数据块 目录 /tmp/ 的数据块 ------------------------ ------------------------ | config.conf | 5000 | | temp_conf | 5000 | ------------------------ ------------------------ | | 同一个 inode v Inode 50003.6 目录与分区挂载3.6.1 为什么需要挂载Linux 和 Windows 在目录结构上有根本区别系统结构特点Windows多根目录树每个分区有独立盘符C:\ D:\ E:\Linux单根目录树全系统只有一棵树根是/所有分区必须融入这棵树问题Linux 只有一棵树但系统可能有多个分区系统盘、数据盘、U盘。这些独立的分区如何接入这棵唯一的树答案挂载Mount。注意Linux系统中的根/ 是内存级别的在系统启动时就已经存在。3.6.2 挂载的本质挂载 把一个分区的文件系统 接入 到 Linux 统一目录树的某个目录节点上。mount /dev/sda2 /data 命令解释 1. 把 /dev/sda2 分区的分区根目录接入到统一目录树的 /data 节点上。 2. 从此访问 /data 就等于访问 /dev/sda2 的根目录。其中被选中的那个目录/data叫作挂载点Mount Point。挂载前 Linux 统一目录树/dev/sda1 /dev/sda2独立无法访问 / ┌──────────────┐ ├── bin/ │ 分区根目录 │ ├── etc/ │ ├── photos/ │ ├── home/ │ ├── videos/ │ ├── data/ -- 普通空目录 │ └── docs/ │ └── var/ └──────────────┘ 挂载后mount /dev/sda2 /data / ├── bin/ 来自 /dev/sda1 ├── etc/ 来自 /dev/sda1 ├── data/ 现在指向 /dev/sda2 的分区根目录 │ ├── photos/ 来自 /dev/sda2 │ ├── videos/ 来自 /dev/sda2 │ └── docs/ 来自 /dev/sda2 └── var/ 来自 /dev/sda13.6.3 磁盘分区只能挂载到根目录吗任何目录都可以作为挂载点。 /dev/sda1 挂载到 / -- 根挂载系统启动时必须且唯一 /dev/sda2 挂载到 /home -- 用户数据 /dev/sda3 挂载到 /var -- 日志数据 /dev/sdb1 挂载到 /data -- 数据盘 /dev/sdc1 挂载到 /media/usb -- U盘 /dev/sdd1 挂载到 /data/photos -- 甚至可以嵌套挂载唯一的特殊之处根目录/是第一个被挂载的也是必须被挂载的。因为 Linux 的目录树必须有一个起点系统启动时内核做的第一件事就是把根分区挂载到/这棵树才算长出来。3.6.4 分区与根目录的关系分区的根目录和Linux 系统的根目录这两个根目录不是一回事。概念位置数量本质Linux 统一目录树的根/内存中VFS 层全系统只有一个内核启动时创建的空节点分区的根目录磁盘上每个分区各有一个ext2 中是inode2 对应的目录解释分区的根目录 ext2 文件系统在设计时人为规定了前几个 inode 编号的用途 inode 0未使用保留 inode 1坏块 inode 记录磁盘上的坏扇区列表 inode 2根目录. 这就是分区的根目录 inode 3ACL inode 访问控制列表某些版本使用 inode 4保留 inode 5保留 ... inode 11第一个可用的普通 inode用户创建的文件从这里开始分配挂载就是对接两个根挂载前 内存中内核创建的 硬盘上/dev/sda1 VFS 的 /节点内存中空 /dev/sda1 的分区根目录硬盘上inode 2 | │ NULL ├── bin/ ├── etc/ ├── lib/ └── ... 挂载后mount /dev/sda1 VFS 的 / 节点 ──── 指针连接 ──── /dev/sda1 的分区根目录inode 2访问 / 就等于访问 /dev/sda1 的根目录内存中内核创建的 硬盘上/dev/sda1 VFS 的 /节点内存中空 /dev/sda1 的分区根目录硬盘上inode 2 | │ NULL ├── bin/ ├── etc/ ├── lib/ └── ... 挂载前两者没有任何关系一个存在于内存另一个存在于磁盘互不相通。 挂载前 / 节点.数据源 NULL 挂载后 / 节点.数据源 /dev/sda1 的分区根目录3.6.5 系统启动时的根分区挂载1. BIOS/UEFI 启动 - 加载 GRUB 2. GRUB 从硬盘读取内核vmlinuz到内存 - 交给内核 3. 内核在内存中初始化 - 创建空的目录树根节点是 / - 此时 / 存在但是空的 4. 挂载根分区关键步骤 - mount /dev/sda1 / - / 不再为空可以看到 bin/、etc/、lib/ 等 5. 启动 init 进程 - 执行 /sbin/init现在可以访问了 - 读取 /etc/fstab知道还需要挂载哪些分区 6. 挂载其他分区 - mount /dev/sda2 /home - mount /dev/sdb1 /data - ... 7. 启动系统服务 - 显示登录界面3.6.6 挂载的覆盖效应核心理解挂载不会删除原目录的内容只是将其遮盖将其卸载后原内容恢复。 挂载前ls /data - old_file1.txt old_file2.txt 执行mount /dev/sda2 /data 挂载后ls /data - photos/ videos/ documents/ old_file1.txt 被遮盖了但还在 /dev/sda1 上 执行umount /data 卸载后ls /data - old_file1.txt old_file2.txt 原内容恢复3.6.7 访问路径时与挂载分区的关系假设挂载状态 /dev/sda1 挂载到 / /dev/sda2 挂载到 /data /dev/sdb1 挂载到 /data/backup 访问 /data/backup/photo.jpg 时 第 1 步从 / 开始/dev/sda1 第 2 步查找 data - 发现是挂载点 - 切换到 /dev/sda2 第 3 步在 /dev/sda2 中查找 backup - 发现是挂载点 - 切换到 /dev/sdb1 第 4 步在 /dev/sdb1 中查找 photo.jpg - 找到 一个路径三次分区切换对用户完全透明。3.7 路径解析当用户在Xshell中输入cat /home/user/test.txt时完整的底层流程如下第 1 级从根目录 / 开始 - 根目录的 inode 编号固定为 2硬编码在内核中 - 读取 inode 2找到根目录的数据块 - 在根目录的数据块中查找 home找到它的 inode 编号假设是 1000 第 2 级进入 /home/ - 读取 inode 1000找到 /home 目录的数据块 - 在其中查找 user找到它的 inode 编号假设是 2000 第 3 级进入 /home/user/ - 读取 inode 2000找到 /home/user/ 目录的数据块 - 在其中查找 test.txt找到它的 inode 编号假设是 25000 第 4 级读取文件 - 读取 inode 25000获取文件属性和数据块指针 - 根据指针读取数据块获得文件内容 - 输出到终端流程图示如下所示用户输入: cat /home/user/test.txt | | v inode 2 (根目录 /) | | 查找目录项 home v inode 1000 (/home/) | | 查找目录项 user v inode 2000 (/home/user/) | | 查找目录项 test.txt v inode 25000 (test.txt 的元数据) | | 读取数据块指针 v Data Block 70000, 70001, ... | | 读取实际内容 v 输出到终端3.8 路径缓存 ---Dentry Cache3.8.1 路径缓存是什么路径缓存缓存的不是文件内容而是目录项dentry。路径缓存的核心思想把文件名 -inode编号的映射关系缓存在内存中下次直接查内存不再读磁盘。目录项就是一条映射记录存储在Data Blocks 数据块中。 目录项 在哪个父目录下 叫什么名字 对应哪个 inode 例如/home/user/test.txt 目录项 1父目录 /inode 2名字 home对应 inode 1000 目录项 2父目录 /homeinode 1000名字 user对应 inode 2000 目录项 3父目录 /home/userinode 2000名字 test.txt对应 inode 250003.8.2 为什么需要路径缓存假设没有路径缓存时每次访问/home/user/test.txt都要逐级读磁盘导致效率极其低下需要多次IO操作。第 1 步读取根目录inode 2的数据块 - 查找 home - 得到 inode 1000 第 2 步读取 inode 1000 的数据块 - 查找 user - 得到 inode 2000 第 3 步读取 inode 2000 的数据块 - 查找 test.txt - 得到 inode 25000 第 4 步读取 inode 25000 的数据块 - 得到文件内容 读磁盘 → 根目录数据块 → 找 home → 读磁盘 → 找 user → 读磁盘 → 找 test.txt每一步都要读磁盘对于一个三级路径而言光找到文件就要读 3 次磁盘还没算读文件内容本身。如果每次cat /home/user/test.txt都要重复这个过程效率极低。3.8.3 如何实现路径缓存Linux内核通过描述 组织 的方式进行实现路径缓存通过在内核上维护dentry结构体 哈希表 树 进行实现A. 描述dentrystruct dentry { struct inode *d_inode; // 指向对应的 inode核心 struct dentry *d_parent; // 指向父目录的 dentry struct qstr d_name; // 文件名如 home、test.txt struct list_head d_child; // 在父目录的子项链表中 struct list_head d_subdirs; // 自己的子目录项链表 unsigned int d_count; // 引用计数有多少进程正在使用 unsigned int d_flags; // 标志位 struct hlist_node d_hash; // 挂在哈希表上的节点 ... };B. 组织方式哈希表 树结构 1哈希表用于快速查找哈希表dentry_hashtable 桶 0: - dentry_A - dentry_X - ... 桶 1: - dentry_B - ... 桶 2: - dentry_C - dentry_Y - dentry_Z - ... 桶 3: - ... ... 哈希键 hash(父目录的 inode 编号 文件名) 例如 hash(inode_2 home) - 桶 5 hash(inode_1000 user) - 桶 12 hash(inode_2000 test.txt) - 桶 3查找流程如下所示要查找 /home/user/test.txt 中的 test.txt 已知父目录是 /home/userinode 2000文件名是 test.txt 计算hash(2000, test.txt) 3 去桶 3 中遍历链表逐个比较 dentry_Z: d_name other.txt - 不匹配 dentry_Y: d_name test.txt - 匹配返回这个 dentry - 从 dentry-d_inode 得到 inode 25000 时间复杂度O(1)平均结构 2树形结构用于遍历和回收dentry 树 /dentryd_inode inode 2 ├── bindentryd_inode inode 500 ├── etcdentryd_inode inode 600 ├── homedentryd_inode inode 1000 │ ├── userdentryd_inode inode 2000 │ │ ├── test.txtdentryd_inode inode 25000 │ │ └── .bashrcdentryd_inode inode 25001 │ └── admindentryd_inode inode 3000 └── vardentryd_inode inode 7003.8.4 工作流程访问 /home/user/test.txt 第 1 步解析 home - 计算 hash(inode_2, home) - 查哈希表 - 命中 是 - 直接得到 dentryd_inode inode 1000不读磁盘 否 - 读磁盘在根目录数据块中查找 home - 得到 inode 1000 - 创建新的 dentry插入哈希表和树中缓存起来 第 2 步解析 user - 计算 hash(inode_1000, user) - 查哈希表 - 命中 是 - 直接得到 dentryd_inode inode 2000不读磁盘 否 - 读磁盘...同上 第 3 步解析 test.txt - 计算 hash(inode_2000, test.txt) - 查哈希表 - 命中 是 - 直接得到 dentryd_inode inode 25000不读磁盘 否 - 读磁盘...同上 第 4 步读取文件内容 - 通过 inode 25000 找到数据块 - 数据块可能也在 Page Cache 中文件内容缓存
网站建设高端定制企业官网