在基础IO中我们详细介绍了文件存储在文件描述符表中,也就是被打开的文件会被文件描述符表管理起来,那未被打开的文件存储在哪里呢?是怎么被找到的呢?这些问题我们会在后文中给出解释
1. 理解硬件
1.1 磁盘、服务器、机柜、机房
1. 磁盘(机械磁盘)
- 是计算机中唯一的机械设备:机械磁盘是计算机系统中唯一的机械设备,因为它包含盘片、磁头等物理运动部件,而其他组件(如CPU或内存)是纯电子设备。这使其在计算机中独特,但也导致其速度较慢。
- 是外设:磁盘被归类为外部存储设备(外设),因为它位于计算机主系统之外,负责永久性数据存储,与内存等内部组件不同。作为外设,它通过接口(如SATA)与主机连接。
- 慢:机械磁盘的读写速度较慢,主要原因是其机械结构:盘片需要旋转(由马达驱动),磁头需要物理移动来定位数据。这导致延迟较高(毫秒级),远低于固态硬盘或内存的速度。企业虽常用其存储数据,但其性能是瓶颈之一。
- 容量大,价格便宜:机械磁盘的存储容量大(常见从数百GB到数TB),且单位存储成本低,适合存储大量数据(如企业备份或冷数据)。价格优势源于成熟技术和磁性材料的使用,而固态硬盘虽快但成本更高。
2. 服务器
- 服务器是高性能计算机,用于运行应用和服务(如网站或数据库)。它们通常安装在机柜中,并放置在机房内。服务器运行时产生大量热量,需要高效散热(如风扇或空调),且依赖稳定电力供应以避免中断。
- 服务器“运行起来就不会轻易关闭”,因此机房需确保散热和电力稳定。
- 服务器机架和机柜行业涉及这些设备的部署和管理。
3. 机柜
- 机柜(或机架)是用于集中存放和保护服务器、网络设备等的物理框架。它提供结构支撑、散热管理和电缆整理功能。机柜的设计影响设备密度和散热效率,是企业数据中心的基础设施。
- 机柜行业专注于优化这些设备,以支持高密度服务器部署。
4. 机房
- 机房是专门设计用于放置服务器、机柜等设备的房间。关键考虑因素包括:
- 散热效果:服务器运行时发热量大,必须通过空调、通风系统等散热管理,防止设备过热损坏或性能下降。
- 电力供应:需要稳定且冗余的电力(如UPS或发电机),避免停电导致数据丢失或服务中断。
- 机房建设成本高,但对企业至关重要,尤其用于数据中心。
题外话
- 关于机房:如上所述,机房的核心需求是散热和电力。详细解释了原因:服务器热量积累可能导致硬件故障,而电力中断会引发服务不可用。因此,机房位置需优先考虑这些因素,以确保设备可靠运行。
- 关于磁盘和磁铁:磁盘使用磁性材料存储数据,类似于磁铁原理。说明磁盘“用磁来存储”,盘片上的磁性颗粒通过磁头(电磁铁)改变极性(N/S极)来记录0/1二进制数据。删除数据时,磁性状态未立即改变,因此数据可恢复。磁盘“由亿个小磁铁构成”,磁头通过改变NS极与计算机交互。磁头“充放电改变盘面南北极”写入数据,但磁头与盘面不直接接触,以防刮伤。磁盘在某些语境中被称为“磁盘”,因其磁性存储本质。
总结
- 机械磁盘是计算机唯一的机械设备和外设,以容量大、价格便宜为优势,但速度慢。
- 服务器是核心计算设备,依赖机柜和机房环境。
- 机柜提供物理支撑和散热,机房确保稳定运行环境(散热和电力)。
- 磁盘的磁性存储原理类似磁铁,利用极性变化记录数据。
1.2 磁盘物理结构
盘片(Platter)
- 由铝制或玻璃基板覆盖磁性材料制成(如铁磁性涂层),用于存储数据 。
- 多个盘片叠放组成磁盘组,围绕主轴高速旋转(典型转速为5400/7200/15000 RPM) 。
磁头(Read/Write Head)
- 每个盘面对应一个磁头,采用“读写合一”的电磁感应设计:
- 写入磁头:传统感应式,通过电流改变磁性颗粒极性(N/S极) 。
- 读取磁头:现代磁盘多使用MR(磁阻)磁头,灵敏度更高 。
- 磁头悬浮于盘面纳米级高度(不直接接触),通过传动臂移动定位 。
- 每个盘面对应一个磁头,采用“读写合一”的电磁感应设计:
传动系统(Actuator Assembly)
- 传动臂:承载磁头,沿盘片半径方向移动,定位目标磁道 。
- 电动机:驱动主轴旋转盘片,保持恒定转速 。
1.3 磁盘的存储结构
磁道(Track)
- 盘片表面的同心圆环,数据沿磁道存储 。
- 相邻磁道间保留间隙(避免磁性干扰) 。
扇区(Sector)
- 磁道被等分的弧段,是最小读写单元(传统大小为512B,现代磁盘支持4KB) 。
- 每个扇区含数据区(存储数据)和间隙区(标识扇区边界) 。
柱面(Cylinder)
- 所有盘面上相同半径的磁道组成的圆柱体(如0号磁道在所有盘面构成0号柱面) 。
- 数据读写优化:同一柱面的数据无需移动传动臂即可通过切换磁头访问,减少寻道时间 。
1.4 磁盘的工作机制
数据读写流程
- 寻道(Seeking) :传动臂移动磁头至目标磁道(耗时3~15ms) 。
- 旋转延迟(Rotation) :盘片旋转至目标扇区下方(耗时2~4ms) 。
- 数据传输:磁头读取/写入数据(速度极快,可忽略) 。
数据存储顺序
- 柱面优先:先写满同一柱面的所有磁道(切换磁头),再移动至下一柱面(移动传动臂) 。
- 扇区顺序:同一磁道按扇区编号顺序写入 。
性能瓶颈
- 机械延迟:寻道与旋转延迟占访问时间90%以上(合计5~15ms),远低于内存/CPU的纳秒级速度 。
- 优化策略:
- 批量读写连续扇区(减少寻道次数) 。
- 磁盘调度算法(如电梯算法)优化磁头移动路径 。
如何定位一个扇区呢?
一、扇区定位的核心原理:CHS寻址
CHS寻址(Cylinder-Head-Sector)是一种通过物理坐标定位磁盘扇区的方法,其核心步骤如下:
- 定位磁头(Head) :选择具体盘面。
- 每个盘面对应一个磁头(双面盘片有2个磁头),磁头号从0开始编号(如0号磁头对应上盘面)。
- 定位柱面(Cylinder) :确定磁道位置。
- 所有盘面上相同半径的磁道构成一个柱面,编号从外圈向内递增(最外圈为0号柱面)。
- 传动臂上的磁头共进退,因此同一时刻所有磁头位于同一柱面。
- 定位扇区(Sector) :在磁道上精确定位。
- 每个磁道被划分为多个扇区(通常512字节),扇区编号从1开始(非0)。
示例:CHS地址
(0,0,1)
表示:
- 0号柱面(最外圈磁道)
- 0号磁头(第一个盘面)
- 1号扇区(该磁道起始扇区)
二、CHS寻址的物理基础与数据结构
1. 磁盘物理结构关联性
组件 | 作用 | 与CHS的关联 |
---|---|---|
磁头(Head) | 读写数据,每盘面一个 | 直接对应H 参数,选择盘面 |
柱面(Cylinder) | 所有盘面同半径磁道的集合 | 对应C 参数,定位磁道半径位置 |
扇区(Sector) | 最小存储单元(512字节) | 对应S 参数,确定磁道内具体位置 |
2. 寻址过程的技术细节
- 物理寻址流程:
- 传动臂移动至目标柱面半径位置(耗时3~15ms)。
- 激活目标磁头,等待盘片旋转至目标扇区下方(旋转延迟2~4ms)。
- 读写数据(纳秒级)。
- 地址表示规则:
- 柱面号(C)、磁头号(H)、扇区号(S)构成三元组
- 早期系统用 24位二进制存储CHS:
- 10位柱面号(最大1024柱面)
- 8位磁头号(最大256磁头)
- 6位扇区号(最大63扇区/磁道)。
三、多扇区定位与文件存储的实现
1. 文件数据的物理存储逻辑
- 文件属性与内容均以二进制形式存储,占用一个或多个连续/离散扇区。
- 多扇区定位能力:
- CHS可定位任意单个扇区,通过组合多个CHS地址即可读写跨扇区文件。
- 示例:读取占用10个扇区的文件时,操作系统按顺序访问10组CHS地址。
2. 磁盘容量的计算与限制
容量公式:
容量 = 磁头数 × 柱面数 × 每磁道扇区数 × 512字节
- 举例:256磁头 × 1024柱面 × 63扇区 × 512B ≈ 8.4GB(按1MB=1,000,000B计)。
CHS的容量瓶颈:
- 24位地址空间上限仅支持8.4GB(柱面数≤1024,磁头数≤256,扇区数≤63)。
- 根源:寄存器位数限制(10+8+6)无法表示更大地址。
以上CHS寻址前提是:每个磁道上有同样数量的扇区 ,早期硬盘上也的确遵循这个。不过随着硬盘容量增加,盘面的数据密度也随之增加,单位面积中理论能容纳的二进制位数量有限。 理论
上,如果保持相同密度的话,盘片外圈能比内圈容纳更多数据。因此硬盘厂商们开始在盘面上将磁道划分出 区块(zone),外圈区块中的磁道可以比内圈区块中的磁道多放入一些扇区。这种方式生产出的硬盘叫 区位记录硬盘(Zone bit recoding, ZBR)
1.5 磁盘的逻辑结构
为解决CHS的容量限制,现代磁盘采用 LBA(Logical Block Addressing)
原理:
- 将整个磁盘抽象为线性扇区数组,扇区从0开始连续编号。
- 操作系统直接使用LBA号,磁盘控制器自动转换为物理CHS地址
柱面是一个逻辑上的概念,它由每个盘面上相同半径的磁道组合而成。虽然磁盘物理上由多个盘面组成,但从逻辑角度来看,整个磁盘可以被视为由这些"柱面"卷绕而成。
实际上,磁盘的工作原理是这样的:
磁道结构:
将盘面上的单个磁道展开来看:
即一维数组
柱面:
整个磁盘所有盘面的同一个磁道,即柱面展开:
- 每个柱面的磁道包含相同数量的扇区
- 这不就是二维数组吗?
整盘:
整个磁盘本质上是由多张二维扇区表组成的(可以理解为三维数组结构)。
要定位一个扇区需要三步:首先确定柱面位置(Cylinder),然后在选定柱面上确定磁道位置(即磁头位置,Head),最后确定具体扇区(Sector)——这就是CHS寻址方式的由来。
正如我们学习C/C++数组时所了解的,从程序的角度来看,所有数组本质上都是一维结构。
每个扇区都有一个对应的编号,称为LBA(Logical Block Address)地址,实际上就是线性地址。那么,这个LBA地址是如何计算出来的呢?
现代操作系统只需使用LBA(逻辑块地址)即可完成磁盘访问!磁盘固件(包括硬件电路和伺服系统)会自动处理LBA与CHS(柱面-磁头-扇区)地址之间的双向转换,无需操作系统介入。
1.6 CHS && LBA地址
一、CHS 与 LBA 寻址的本质差异
特性 | CHS(柱面-磁头-扇区) | LBA(逻辑块地址) |
---|---|---|
定位方式 | 三维物理坐标(柱面号、磁头号、扇区号) | 一维线性地址(连续编号的扇区索引) |
抽象层级 | 直接对应磁盘物理结构 | 屏蔽物理细节,提供逻辑扇区视图 |
起始编号 | 扇区号从 1 开始,柱面/磁头从 0 开始 | 所有地址从 0 开始连续编号 |
容量限制 | 最大 8.4GB(24位地址限制) | 支持 TB 级及以上(如 48位LBA) |
使用场景 | 早期 BIOS/MBR 引导、磁盘控制器内部转换 | 现代操作系统、文件系统、UEFI 固件 |
核心矛盾:
CHS 依赖磁盘物理参数(磁头数、柱面数、每磁道扇区数),而不同磁盘参数差异巨大,导致操作系统需适配无数硬件型号。LBA 通过逻辑抽象解决此问题,使上层软件仅关注线性扇区数组
CHS与LBA转换公式:
CHS转LBA:
- 单个柱面扇区数 = 磁头数 × 每磁道扇区数
- LBA = 柱面号(C) × 单个柱面扇区数 + 磁头号(H) × 每磁道扇区数 + 扇区号(S) - 1
- 注:扇区号从1开始计数,而LBA地址从0开始编号;柱面和磁道编号均从0开始
示例(Western Digital 硬盘参数:磁盘总磁头数(每柱面磁头数)
=16
, 每磁道扇区数=63
:
CHS(2, 3, 4)
→ LBA =(2×16×63) + (3×63) + (4-1)
=2016 + 189 + 3
= 2208
LBA转CHS:
- 柱面号(C) = LBA ÷ 单个柱面扇区数(取整)
- 磁头号(H) = (LBA % 单个柱面扇区数) ÷ 每磁道扇区数(取整)
- 扇区号(S) = (LBA % 每磁道扇区数) + 1
示例(参数同上,LBA=2208):
C = 2208 // (16×63) = 2208 // 1008 = 2
- 余数
R1 = 2208 % 1008 = 192
H = 192 // 63 = 3
S = (192 % 63) + 1 = 3 + 1 = 4
→ CHS(2, 3, 4)
注意:磁盘内部自动维护总柱面数、磁道数和扇区总数等参数,系统启动时会获取这些信息。
层 级 | 角色 | 使用地址 | 说明 |
---|---|---|---|
硬件层 | 磁盘控制器 | CHS/LBA | 自动转换 LBA → 物理CHS,对上层透明 |
固件层 | UEFI/GPT | LBA | 分区表直接记录起始/终止 LBA |
操作系统层 | 文件系统(ext4/NTFS) | LBA | 通过块设备接口读写逻辑扇区 |
应用层 | 数据库/虚拟机 | LBA | 直接调用 read/write 系统调用 |
最终结论:
- CHS 是磁盘物理结构的直接映射,LBA 是面向逻辑的抽象层。
- 转换公式的数学本质是三维坐标与一维线性空间的映射(受限于磁盘参数)。
- 现代系统中,磁盘 = 扇区的一维数组,LBA 是数组下标,CHS 仅存在于控制器内部转换层。
- 操作系统通过 磁盘总磁头数(每柱面磁头数)、每磁道扇区数 等参数初始化磁盘驱动,后续完全使用 LBA 操作设备 。
2. 引入文件系统
2.1 引入"块"概念
其实硬盘是典型的"块"设备,操作系统读取硬盘数据的时候,并不是直接以扇区为单位进行读取的。这是因为现代硬盘的扇区大小通常为512字节(较新的硬盘可能使用4KB扇区),如果以单个扇区为单位进行读取,会导致大量的I/O操作,严重影响系统性能。
为了提高效率,操作系统会一次性连续读取多个扇区,即读取一个"块"(block)。在大多数现代文件系统中:
- 块大小的典型值为4KB(即8个512字节的扇区)
- 块是文件系统进行I/O操作的最小单位
- 块大小在格式化时确定,之后不可更改
从磁盘寻址的角度来看:
- 磁盘可以视为一个三维数组(柱面、磁头、扇区),但为了简化,我们可以将其抽象为一个一维数组
- 每个扇区都有唯一的LBA(Logical Block Addressing)地址
- 块地址可以通过LBA计算得出:
- 已知LBA求块号:块号 = LBA / 8(当块大小为4KB时)
- 已知块号求LBA:LBA = 块号 * 8 + n(n表示块内第n个扇区)
2.2 引入"分区"概念
磁盘可以被划分为多个分区(partition),这是对硬盘进行逻辑划分的一种方式。分区的本质是对硬盘的格式化,不同分区可使用不同文件系统(如NTFS、EXT4)。从用户角度来看:
- 在Windows系统中,分区表现为不同的驱动器(如C:、D:、E:等我们常说的盘)
- 在Linux系统中,分区表现为特殊的设备文件(如/dev/sda1、/dev/sda2等)
分区的基本原理:
- 分区的最小单位是柱面(由多个磁道组成的圆柱形区域)
- 每个分区通过起始柱面号和结束柱面号来定义
- 分区信息记录在磁盘的分区表中(如MBR或GPT)
从寻址角度来看:
操作系统通过分区表(如MBR)记录各分区的起止柱面,进而映射到LBA地址。由于柱面大小固定,且每个柱面包含的扇区数相同
- 只要知道:
- 分区的起始柱面号
- 结束柱面号
- 每个柱面包含的扇区数
- 就可以计算出:
- 分区的大小
- 分区内任意位置的LBA地址
这种分区方式使得磁盘空间管理更加灵活,不同的分区可以使用不同的文件系统,也可以用于不同的用途(如系统分区、数据分区等)。
2.3 引入"inode"概念
在Linux系统中,文件由两部分组成:文件数据(内容)和文件属性(元数据)。当我们使用ls -l命令时,就能看到这些文件属性信息。让我们通过一个更详细的例子来理解:
ltx@hcss-ecs-d90d:~$ ls -l
total 16
-rw-rw-r-- 1 ltx ltx 71 Jul 8 16:45 code.c
drwxrwxr-x 2 ltx ltx 4096 Jul 20 12:31 Linux_network
drwxrwxr-x 10 ltx ltx 4096 Jul 23 23:07 Linux_system
drwxr-xr-x 3 root root 4096 Jul 7 14:21 mydir
这个输出实际上展示了7个关键属性:
- 文件类型和权限(模式):如"-rw-rw-r--"表示普通文件,所有者可读写,组用户可读写,其他用户只读
- 硬链接数:表示有多少个文件名指向这个inode
- 文件所有者:通常是创建该文件的用户
- 所属组:决定哪些用户可以访问该文件
- 文件大小:以字节为单位
- 最后修改时间:文件内容最后一次被修改的时间
- 文件名:用户可见的文件标识符
ls -l读取存储在磁盘上的文件信息,然后显示出来
更详细的信息可以通过stat命令查看,它展示了文件系统中更底层的元数据:
ltx@hcss-ecs-d90d:~$ stat code.c
File: code.c
Size: 71 Blocks: 8 IO Block: 4096 regular file
Device: fc01h/64513d Inode: 142886 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ ltx) Gid: ( 1000/ ltx)
Access: 2025-07-16 19:13:03.446155686 +0800
Modify: 2025-07-08 16:45:26.024247415 +0800
Change: 2025-07-08 16:45:26.028247456 +0800
Birth: 2025-07-08 16:45:26.024247415 +0800
这里引出了inode的核心概念:在文件系统中,文件数据存储在"块"中,而元信息(包括上述所有属性)则存储在称为inode(索引节点)的数据结构中。每个文件都有一个对应的inode,其中包含以下关键信息:
- 文件类型和权限(i_mode)
- 所有者ID和组ID(i_uid, i_gid)
- 文件大小(i_size)
- 时间戳(访问/创建/修改时间)
- 链接计数(i_links_count)
- 文件数据块的位置(i_block[])
- 其他文件系统相关信息
可以通过ls -i命令查看文件的inode编号:
ltx@hcss-ecs-d90d:~$ ls -li
total 16
142886 -rw-rw-r-- 1 ltx ltx 71 Jul 8 16:45 code.c
148723 drwxrwxr-x 2 ltx ltx 4096 Jul 20 12:31 Linux_network
148728 drwxrwxr-x 10 ltx ltx 4096 Jul 23 23:07 Linux_system
150851 drwxr-xr-x 3 root root 4096 Jul 7 14:21 mydir
每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。为了能解释清楚inode,我们需要深入了解一下文件系统。
inode在文件系统中的组织形式可以用以下结构体表示(以ext2文件系统为例):
/*
* 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 */
};
/*
* 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)
核心字段:
字段 | 含义 | 示例(stat 命令输出) |
---|---|---|
i_mode |
文件类型和权限(如-rw-r--r-- ) |
Access: (0664/-rw-rw-r--) |
i_uid /i_gid |
所有者和所属组ID | Uid: (1000/ltx) |
i_size |
文件大小(字节) | Size: 71 |
i_atime |
最后访问时间 | Access: 2025-07-16 19:13:03 |
i_mtime |
最后修改时间 | Modify: 2025-07-08 16:45:26 |
i_ctime |
属性最后变更时间 | Change: 2025-07-08 16:45:26 |
i_links_count |
硬链接数量 | Links: 1 |
i_blocks |
占用的磁盘块数 | Blocks: 8 |
i_block[] |
文件数据块的指针数组 | - |
i_generation |
文件版本(用于NFS) | - |
关于inode需要特别注意的几点:
- 文件名并不存储在inode中。文件名和inode编号的映射关系保存在目录文件中。
- inode大小通常是固定值(128或256字节),与文件内容大小无关。
- 每个inode都有一个唯一的编号,通过这个编号可以快速定位文件的元信息和数据块位置。
- inode中存储了指向文件数据块的指针,包括12个直接指针、1个一级间接指针、1个二级间接指针和1个三级间接指针(EXT2_N_BLOCKS=15)。
文件系统通过inode实现了高效的文件管理:
- 通过inode编号可以快速定位文件属性和数据块位置
- 硬链接机制允许多个文件名指向同一个inode
- 权限管理基于inode中的用户/组ID和权限位
- 文件访问时间等信息都记录在inode中
理解inode是理解Linux文件系统工作原理的关键,它解决了文件属性存储、快速访问和数据块定位等核心问题。
下一篇文章我们将详细介绍ext2文件系统