一、磁盘
1.1大体概念
磁盘,即机械硬盘(Hard Disk Drive,HDD),是基于磁记录原理的经典非易失性外部存储介质,也是计算机体系中应用最广泛的大容量存储设备之一。虽然历经飞速的技术迭代,SSD 等更高效的存储介质不断涌现,但是磁盘凭借其较为便宜的价格以及超大的物理容量在技术飞速迭代的环境下依然被作为大批量存储的首选介质。

磁盘的核心硬件由盘片、磁头、电机、控制电路板等组成,其中磁头就相当于磁盘的笔,磁盘盘片相当于纸,其存储原理以磁介质为基础:盘片表面涂有可磁化的磁性涂层,被划分为无数独立磁畴单元,在主轴马达带动下,盘片高速旋转,在需要写入的位置通过磁头产生的定向磁场改变磁畴磁极方向,以此表示二进制 0 和 1,实现数据写入;读取时则利用电磁感应,通过磁畴磁极切割磁头线圈产生的不同方向感应电流,还原二进制数据,且磁化后的磁极方向断电后不消失,具备天然的非易失性。
1.2存储结构


磁盘的盘片并非肉眼所见的光滑整体,其存储面被规划为规整的物理结构:以盘片圆心为中心分布着多层同心圆磁道,从圆心向外呈等角度辐射的线段,将所有同心圆磁道做均匀等分,每一道磁道被截出的微小圆弧段,就是磁盘的最小硬件存储单位扇区(通常为512字节);而相邻两条辐射线段与磁道合围形成的扇形区域,则被称为扇面。完整的机械硬盘并非单一层盘片结构,而是由多片盘片叠加组成的盘片组,单张盘片的正反两个盘面均为有效存储面。

对于多个盘片组成的圆柱体结构,而对于所有盘片同一磁道构成的集合圆柱体成为柱面,且每个存储面都配备独立的磁头,主轴马达则为所有盘片提供统一的高速旋转动力,所有磁头在传动臂的统一带动下进行不同磁道(柱面)之间的切换,马达与传动臂共同构成协同工作的存储机械结构。
1.3寻址方法
对于磁盘这样的立体存储结构,想要精准定位到具体的存储地址(扇区),可通过三层递进的寻址逻辑实现:首先通过柱面(Cylinder)完成整体区域的初步寻址,确定数据所在的径向位置;柱面包含多个盘片的存储面,而每个存储面对应一个独立磁头,因此接下来可通过磁头(Head)完成具体存储面的定位;最后在选定的柱面与存储面对应的磁道上,定位到具体的扇区(Sector),即可精准找到目标存储位置。这就是磁盘经典的三维物理寻址方式 ——CHS 寻址,由柱面、磁头、扇区三个维度共同构成,层层缩小寻址范围,实现对磁盘任意扇区的精准定位。
1.4逻辑结构
4-1理解过程
磁盘看起来虽然是一个较为复杂的立体结构,但是我们可以逐步将其抽象成一个一维线性结构,我们可以从磁带进行切入:

一条磁带,假如我们进行拉直铺平,以存储单元为单位,就可以抽象成一个一维线性结构,那么同理对于一条磁道,虽然无法像磁带一样拉直,但是同样可以通过相同的思考方式抽象成一个一维线性结构,再把每个磁道进行相同的逻辑抽象,那么所有“展开”后的磁道再进行“拼接”,最后再进行顺序编址,就能完成整个磁盘盘面从物理环形结构到一维线性逻辑结构的转化。这样每个扇区就有了线性地址,这种地址就叫做LBA。

那么对于整个立体磁盘结构,虽然磁盘是由多个盘面形成的物理结构,但是我们用柱面的概念可以把整个磁盘转化成由所有柱面“卷起来”的等价模型

那么我们就可以把整个柱面抽象成一个二维数组,那么对于整个磁盘,不就是一个三维数组吗?但是,通过c/c++学习,我们知道,多维数组不就是一维数组的另一种表现形式吗?无论是二维数组还是三维数组实质是都是一维数组,那么我们就完成了磁盘逻辑结构的转化,我们再对整个“数组”进行编址,那么对应每个扇区就有了下标,那么这个下标就是LBA地址,也就是线性地址。
4-2LBA地址与CHS地址的转化
CHS转成LBA:
—磁头数*每磁道扇区数 = 单个柱⾯的扇区总数
—LBA = 柱⾯号C*单个柱⾯的扇区总数 + 磁头号H*每磁道扇区数 + 扇区号S – 1
—即:LBA = 柱⾯号C*(磁头数*每磁道扇区数) + 磁头号H*每磁道扇区数 + 扇区号S – 1
—扇区号通常是从1开始的,⽽在LBA中,地址是从0开始的
—柱⾯和磁道都是从0开始编号的
—总柱⾯,磁道个数,扇区总数等信息,在磁盘内部会⾃动维护,上层开机的时候,会获取到这些参
数。
LBA转成CHS:
—柱⾯号C = LBA // (磁头数*每磁道扇区数)【就是单个柱⾯的扇区总数】
—磁头号H = (LBA % (磁头数*每磁道扇区数)) // 每磁道扇区数
—扇区号S = (LBA % 每磁道扇区数) + 1
—“//": 表⽰除取整
那么对于磁盘使用,作为用户层的我们,直接使用更为直观简单的LBA地址即可,CHS地址只是内核需要进行的地址转化工作。
二、文件系统引入
2.1 “块”的概念
前面提到,磁盘硬件层面的最小读写单位是扇区,经典标准大小为 512 字节。但操作系统若直接以扇区为单位访问磁盘,会因单次读写数据量过小、寻址次数过多导致效率偏低,因此会以块为单位读取磁盘数据。块由连续的多个扇区组成,硬盘的每个分区都会被划分为一个个独立的块,其大小在磁盘格式化时确定,且一旦设定便无法更改,最常见的块大小为 4KB,即由连续 8 个 512 字节的扇区组成。与硬件层的扇区不同,块是操作系统文件系统层面的文件存取最小单位,文件的创建、读取、写入均以块为基本粒度,即使是极小的文件,也会占用至少一个块的存储空间。
知道LBA:块号 = LBA/8
知道块号:LBA=块号*8 + n (n是块内第⼏个扇区)

2.2 “分区”的概念
磁盘可被划分为多个独立分区(partition),以 Windows 系统为例,一块物理磁盘常被分区为 C、D、E 等盘符,分区本质上是对硬盘的一次基础格式化与空间划分;而 Linux 系统中所有设备均以文件形式管理,其磁盘分区逻辑则依托硬件结构设计 ——柱面是 Linux 磁盘分区的最小单位,分区的核心操作就是为每个分区指定明确的起始柱面与结束柱面,通过划定柱面的范围,实现物理磁盘空间的分割与独立管理。此时我们可以将硬盘上的柱⾯(分区)进⾏平铺,将其想象成⼀个⼤的平⾯。
2.3 “inode”的概念
对于一个文件,必然有两部分组成:内容+属性,我们执行命令ls -l就可以看到当前目录下的文件元数据(属性):

那么对应属性从左到右依次表示模式、硬链接数、文件所有人、组、大小、最后修改时间、文件名。
到这我们要思考⼀个问题,⽂件数据都储存在”块”中,那么很显然,我们还必须找到⼀个地⽅储存
⽂件的元信息(属性信息),⽐如⽂件的创建者、⽂件的创建⽇期、⽂件的⼤⼩等等。这种储存⽂件元信息的区域就叫做inode(注意,在linux系统,文件的属性和内容分开储存),中⽂译名为”索引节点”。

可以看到我们通过添加 -i选项就可以看到每一个文件都有对应的且唯一的inode,里面包含了相关文件的一些信息,那么打开源码可以翻看到inode具体内容:
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 */
};
/*
* Constants relative to the data blocks
*/
#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)
值得注意的是:inode信息中不包含文件名属性,并且任何文件的内容大小可以不相同,但是属性大小一定相同。
三、文件系统
3.1对文件系统的宏观认识
我们想要在硬盘上储存文件,必须先把硬盘格式化成某种格式的文件系统才能储存文件。文件系统的目的就是组织和管理硬盘中的文件。在 Linux 系统中,最常⻅的是 ext2 系列的⽂件系统。其早期版本为 ext2,后来⼜发展出 ext3 和 ext4。 ext3 和 ext4 虽然对 ext2 进⾏了增强,但是其核⼼设计并没有发⽣变化,我们仍是以较⽼的 ext2 作为 演⽰对象。
ext2⽂件系统将整个分区划分成若⼲个同样⼤⼩的块组 (Block Group),如下图所⽰。只要能管理⼀个 分区就能管理所有分区,也就能管理所有磁盘⽂件。

上图中启动块(Boot Block/Sector)的⼤⼩是确定的,为1KB,由PC标准规定,⽤来存储磁盘分区信 息和启动信息,任何⽂件系统都不能修改启动块。启动块之后才是ext2⽂件系统的开始。
其中Block Group是文件系统根据分区大小对分区进行的块组划分,每个块组(Block Group)有相同的结构组成。
3.2块组内部组成
2-1超级块(Super Block)
存放⽂件系统本⾝的结构信息,描述整个分区的⽂件系统信息。记录的信息主要有:block 和inode的总量,未使⽤的block和inode的数量,⼀个block和inode的⼤⼩,最近⼀次挂载的时间,最近⼀次写⼊数据的时间,最近⼀次检验磁盘的时间等其他⽂件系统的相关信息。Super Block的信息被破坏,可以说整个⽂件系统结构就被破坏了
超级块在每个块组的开头都有⼀份拷⻉(第⼀个块组必须有,后⾯的块组可以没有)。 为了保证⽂件系统在磁盘部分扇区出现物理问题的情况下还能正常⼯作,就必须保证⽂件系统的super block信息在这种情况下也能正常访问。所以⼀个⽂件系统的super block会在多个block group中进⾏备份,这些super block区域的数据保持⼀致。
2-2块组描述符表(GDT)
块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储⼀个块组 的描述信息,如在这个块组中从哪⾥开始是inode Table,从哪⾥开始是Data
Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有⼀份拷⻉。
2-3块位图(Block Bitmap)
Block Bitmap中记录着Data Block中哪个数据块已经被占⽤,哪个数据块没有被占
2-4inode位图(Inode Bitmap)
每个bit表⽰⼀个inode是否空闲可⽤。
2-5i节点表(Inode Table)
存放⽂件属性 如 ⽂件⼤⼩,所有者,最近修改时间等
当前分组所有Inode属性的集合
inode编号以分区为单位,整体划分,不可跨分
2-6Data Block
数据区:存放⽂件内容,也就是⼀个⼀个的Block。根据不同的⽂件类型有以下⼏种情况:
对于普通⽂件,⽂件的数据存储在数据块中。
对于⽬录,该⽬录下的所有⽂件名和⽬录名存储在所在⽬录的数据块中,除了⽂件名外,ls -l命令
看到的其它信息保存在该⽂件的inode中。
Block 号按照分区划分,不可跨分区
3.3inode->Data Block的映射
ext 系列文件系统中,inode 与数据块的映射关系是通过inode 内部的直接 / 间接指针字段实现的 ——inode 结构体中专门预留了若干个指针位,每个指针指向文件数据所在的文件系统块的全局编号,通过「直接指针 + 一级 / 二级 / 三级间接指针」的分层设计,既保证小文件的读写效率,又支持大文件的存储,核心设计和映射逻辑如下
ext 系列 inode 结构体中,默认有 15 个数据块指针位(32 位 / 64 位系统仅指针宽度不同,数量不变),这 15 个指针分为 4 类,按访问效率从高到低、支持文件大小从小到大排列,所有指针存储的都是分区内全局的文件系统块号(而非块组内偏移,内核可通过块号直接定位物理块):

文件新建和删除的原理
通过以上总结,其实文件的新增是从inode 位图找到首个值为 0(未占用)的 inode 号并置 1 → 初始化该 inode 的元数据(权限、时间等) → 从块位图找到足够多值为 0(空闲)的 data block 并置 1 → 将这些 data block 的全局块号写入 inode 的指针字段 → 把文件数据写入对应的 data block,完成新增。文件的删除更加简单,找到文件对应的 inode 号 → 根据 inode 指针字段,遍历所有关联的 data block,将这些块在块位图中的位逐一置 0 → 将该 inode 号在inode 位图中的位置 0,并清空 inode 的指针 / 元数据 → 删除目录项中 “文件名 – inode 号” 的映射(让文件无法被上层访问)。(所以删除文件本质只是置0相应比特位,而非删除真正的数据,当误删文件的时候,千万不要着急操作导致其他文件覆盖式写入误删位置,导致数据真正丢失)
3.4目录与文件名
4-1目录其实也是普通文件
在 Linux/UNIX 类文件系统(如 ext 系列)中,目录的本质是一种专门用于存储「文件名 – inode 号映射关系」的特殊文件,它与普通文件一样拥有唯一的 inode 号、占用对应的 data block,遵循文件系统的所有底层管理规则(如位图标记、inode-data block 映射),唯一的核心区别是存储的内容和功能不同:普通文件的 data block 存储的是实际业务数据(如文本、二进制、视频等),而目录文件的 data block 存储的是目录项(dentry)—— 即一组组 “文件名 + 对应 inode 号” 的键值对,这是目录的核心特殊属性。
简单来说,目录不是独立于文件系统的特殊结构,而是文件系统为实现 “文件按名查找” 设计的、具备特定功能的普通文件,其所有底层操作(创建、删除、修改)均与普通文件一致,仅上层功能被赋予了 “管理文件 / 子目录映射关系” 的职责。
4-2目录与文件名的映射
目录是文件的 “按名查找索引”,普通文件是实际数据的存储载体,二者通过「目录项的文件名 – inode 号映射」和「inode 的 data block 指针」形成完整的文件访问链路,目录为上层应用提供 “易理解的文件名访问方式”,屏蔽了底层 inode 号和数据块的复杂寻址逻辑,二者相互依存、缺一不可。
文件系统中文件名并非文件的唯一标识,inode 号才是(inode 号是分区内全局唯一的文件 ID),普通文件本身不存储自己的文件名,文件名仅存在于其所属父目录的目录项中。上层应用通过 “路径名(如/home/test/file.txt)” 访问文件时,文件系统会从根目录开始,逐层解析每个目录的目录项:
–解析根目录/的 data block,找到home对应的 inode 号;
–解析home目录的 data block,找到test对应的 inode 号;
–解析test目录的 data block,找到file.txt对应的 inode 号;最终通过 inode 号定位到文件的 inode,再通过 inode 的指针找到数据块,完成文件访问。
所以我们之前说inode中并没有保存文件名,其实是目录为我们做了inode->文件名的映射,为用户层提供了极大的便利。
网硕互联帮助中心




评论前必须登录!
注册