理解硬件
磁盘、服务器、机柜、机房
好的,我们用生活中的比喻来解释这些概念和它们之间的关系,这样就很容易理解了:
磁盘 (Disk / Hard Drive):
- 概念: 就像你电脑里的“硬盘”。它是用来存储数据的最基本单元。就像你家里的“抽屉”或者“储物盒”。
- 作用: 专门存放信息的地方。操作系统、软件、你的照片、文档、电影等等,最终都存放在磁盘上(可能是机械硬盘或固态硬盘)。
- 特点: 容量有大有小(比如500GB, 1TB, 2TB等),速度有快有慢(SSD比HDD快很多)。
服务器 (Server):
- 概念: 可以把它想象成一台功能非常强大的、专门用来“服务”别人的电脑主机。它不像你的家用电脑主要为你一个人服务,而是为很多其他电脑(客户端)提供某种服务(比如存文件、运行网站、处理数据库等)。
- 作用: 它是干活的核心。它里面装着CPU(大脑)、内存(临时工作台)、还有最重要的——多个磁盘(存放数据的抽屉)。服务器运行特定的软件(如Web服务器软件、数据库软件、文件共享软件)来提供各种服务。
- 特点: 性能强、可靠性高、通常24小时不间断运行。服务器的主板上有插槽可以安装很多块磁盘。
- 与磁盘的关系: 服务器肚子里装着磁盘。一台服务器通常会安装多个磁盘(就像一个大柜子里有好几个抽屉),用来存储它要处理或提供的大量数据。磁盘是服务器提供存储服务的核心硬件。
机柜 (Rack):
- 概念: 想象一个高大的、带有很多层架子的金属柜子,就像图书馆里放书的书架,或者更衣室里放衣服的储物柜。
- 作用:
- 物理支撑: 用来整齐地摆放和固定多台服务器以及其他设备(如网络交换机、电源分配器)。
- 空间管理: 让机房空间利用率更高,设备排列有序。
- 布线和散热: 机柜前后门通常有孔洞,方便连接电源线和网线,也有利于空气流通散热。机柜内部有专门的理线架管理杂乱的线缆。
- 特点: 标准宽度(通常是19英寸),高度以“U”为单位(1U=1.75英寸)。常见的服务器有1U、2U、4U高。
- 与服务器的关系: 机柜是服务器的“家”和“工位”。一台机柜里可以并排安装很多台服务器(比如10台、20台甚至更多),把它们垂直叠放固定在机柜的导轨上。服务器只有放进机柜,才能被安全、稳定、整齐地管理和使用。
机房 (Data Center / Server Room):
- 概念: 就是专门用来放机柜(以及里面服务器)的那个大房间或者一栋建筑。可以想象成一个超级图书馆,里面不是放书的书架,而是放满了机柜。
- 作用: 提供一个安全、稳定、可控的环境,让里面的服务器和网络设备能够7x24小时可靠运行。这是所有IT基础设施的“大本营”。
- 关键设施:
- 强大的电力供应: 不间断电源、备用发电机,确保永远不断电。
- 精密空调: 服务器运行产生大量热量,需要强力制冷保持恒温恒湿。
- 消防系统: 特殊气体灭火,避免水损毁设备。
- 物理安全: 门禁系统、监控摄像头、保安,防止未经授权进入。
- 网络接入: 高速光纤连接到互联网骨干网。
- 与机柜的关系: 机房是机柜的“安身之所”。一个机房里面会放置很多排机柜(称为“机柜列”)。机房的所有基础设施(电、冷、网、安防)都是为了保障机柜里的设备能正常工作。
- 与服务器/磁盘的关系: 机房是服务器和磁盘所处的最终物理环境。你的数据最终就存放在机房某个机柜里某台服务器的某块磁盘上。
总结一下它们之间的关系(从小到大):
- 磁盘是存储数据的最小单元,就像抽屉。
- 多个磁盘安装在服务器里,服务器就像装了很多抽屉的柜子,并且是干活的电脑。
- 多台服务器(以及网络设备等)安装固定在机柜里,机柜就像图书馆的书架,提供工位和管理。
- 多个机柜整齐排列在机房中,机房就像整个图书馆大楼,提供电力、制冷、安全等基础保障。
访问数据的路径(从外到内):
- 你在家用电脑访问一个网站。
- 你的请求通过网络到达机房。
- 进入机房后,根据信息找到对应的机柜。
- 在机柜里找到运行该网站程序的服务器。
- 服务器上的程序去读取它内部的磁盘上的数据(网页文件、数据库信息等)。
- 服务器处理完数据,通过网络把结果(网页)送回给你的电脑显示出来。
简单来说:磁盘存数据,服务器处理数据,机柜管理和安放服务器,机房保护和管理所有机柜和设备。 它们一层套一层,共同构成了我们使用的互联网服务背后的物理基础。
磁盘物理结构
磁盘的存储结构
扇区:是磁盘存储数据的基本单位,512字节,块设备
磁道:
盘片:一个盘片有两个面
柱面:
如何定位⼀个扇区呢?
- 可以先定位磁头(header)
- 确定要访问哪一个磁道(cylinder)
- 最后可以定位扇区(sector)
- 这个也叫做
CHS
定址
文件 = 内容+属性 都是数据,无非就是占据那几个扇区的问题!能定位⼀个扇区了,能不能定位多个扇区呢?
扇区是从磁盘读出和写⼊信息的最小单位,通常大小为 512 字节。
• 磁头(head)数:每个盘⽚⼀般有上下两⾯,分别对应1个磁头,共2个磁头
• 磁道(track)数:磁道是从盘⽚外圈往内圈编号0磁道,1磁道…,靠近主轴的同⼼圆⽤于停靠磁
头,不存储数据
• 柱⾯(cylinder)数:磁道构成柱⾯,数量上等同于磁道个数
• 扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同
• 圆盘(platter)数:就是盘⽚的数量
• 磁盘容量=磁头数 × 磁道(柱⾯)数 × 每道扇区数 × 每扇区字节数
• 细节:传动臂上的磁头是共进退的
柱⾯(cylinder
),磁头(head
),扇区(sector
),显然可以定位数据了,这就是数据定位(寻址)⽅式之⼀,CHS
寻址⽅式。
fdisk -l //用于列出系统检测到的磁盘及其分区信息。
通用磁盘信息(对 /dev/loopX
和 /dev/vda
都适用):
Disk /dev/XXXX:
- 含义: 标识一个磁盘设备。
/dev/
目录下的文件代表系统中的硬件设备。 /dev/loopX
: 这些是虚拟磁盘设备(loop device)。它们通常用于将文件(如 ISO 镜像、Snap 应用的 squashfs 文件)挂载成块设备,让系统像访问真实磁盘分区一样访问文件内容。常见于容器、Snap 应用或手动挂载镜像文件。/dev/vda
: 这是一个虚拟化环境(如 KVM, VMware, VirtualBox)中常见的虚拟硬盘名称。vd
通常代表 Virtio Block Device (虚拟 IO 块设备),a
表示系统中的第一块虚拟硬盘。在物理服务器上,你更可能看到/dev/sda
(SCSI/SATA) 或/dev/nvme0n1
(NVMe)。
- 含义: 标识一个磁盘设备。
44.5 MiB, 46604288 bytes, 91024 sectors
/40 GiB, 42949672960 bytes, 83886080 sectors
- 含义: 显示磁盘的总容量,用三种单位表示:
MiB
/GiB
: Mebibytes / Gibibytes (二进制单位,1 MiB = 1024 KiB, 1 GiB = 1024 MiB)。更符合计算机内存/存储的实际计算方式。bytes
: 总字节数 (最基础的容量单位)。sectors
: 总扇区数。磁盘读写的最小寻址单位(虽然现代文件系统和硬件有更小的原子操作,但寻址仍以扇区为单位)。
- 含义: 显示磁盘的总容量,用三种单位表示:
Units: sectors of 1 * 512 = 512 bytes
- 含义: 指定了后面信息中
sectors
字段使用的单位大小。这里明确说明 每个扇区的大小是 512 字节。这是一个非常传统的扇区大小(称为“512e” - 512字节模拟),即使在物理扇区大小更大的现代磁盘(如 4K)上,固件也常常模拟成 512 字节扇区给操作系统看。
- 含义: 指定了后面信息中
Sector size (logical/physical): 512 bytes / 512 bytes
- 含义: 详细说明扇区大小。
logical
(逻辑扇区大小): 操作系统看到的扇区大小。这里是 512 字节。physical
(物理扇区大小): 磁盘硬件实际使用的扇区大小。这里也是 512 字节。对于现代高级格式磁盘(4K 物理扇区),这里可能会显示512 bytes / 4096 bytes
(512e) 或4096 bytes / 4096 bytes
(4Kn)。对于 loop 设备和这个虚拟磁盘,都是 512。
- 含义: 详细说明扇区大小。
I/O size (minimum/optimal): 512 bytes / 512 bytes
- 含义: 建议操作系统进行磁盘 I/O 操作时使用的大小。
minimum
(最小 I/O 大小): 一次 I/O 操作的最小字节数。这里也是 512 字节。optimal
(最优 I/O 大小): 能获得最佳性能的 I/O 操作大小(通常与物理扇区大小或 RAID 条带大小对齐)。这里建议也是 512 字节。对于物理磁盘,这个值通常更大(如 4096 或 65536 字节),表示一次读写多个扇区性能更好。
- 含义: 建议操作系统进行磁盘 I/O 操作时使用的大小。
分区表信息(仅出现在有分区表的磁盘 /dev/vda
上):
Disklabel type: dos
- 含义: 指定磁盘使用的分区表类型。
dos
是最常见的 MBR (Master Boot Record) 分区表。另一种常见的类型是gpt
(GUID Partition Table)。MBR 有局限性(如最大支持 2TB 磁盘,最多 4 个主分区),GPT 更现代,支持更大磁盘和更多分区。
- 含义: 指定磁盘使用的分区表类型。
Disk identifier: 0x44451b07
- 含义: 磁盘分区表的唯一标识符(一个 32 位的十六进制数)。主要用于区分不同的磁盘。这个值通常在你对磁盘分区时由工具随机生成。实际管理中用到的机会不多。
分区列表表头:
Device Boot Start End Sectors Size Id Type
Device
: 分区设备文件名 (e.g.,/dev/vda1
)。Boot
: 指示该分区是否包含可引导标志(一个星号*
表示该分区被标记为可引导/活动分区,通常是安装操作系统的分区)。MBR 只能有一个活动分区。Start
: 分区在磁盘上的起始扇区号。End
: 分区在磁盘上的结束扇区号。Sectors
: 分区占用的总扇区数 (End - Start + 1
)。Size
: 分区的容量(根据扇区数和扇区大小自动计算得出,以人类易读的单位如 GiB, MiB 显示)。Id
: 分区的类型 ID(一个两位的十六进制数)。这个 ID 告诉操作系统分区内预期的文件系统类型或用途。Type
: 对Id
的文字描述。解释了这个分区类型 ID 通常代表什么。
分区条目:
/dev/vda1 * 2048 83884031 83881984 40G 83 Linux
/dev/vda1
: 这是/dev/vda
磁盘上的第一个分区。*
: 该分区被标记为可引导/活动分区。Start: 2048
: 分区从磁盘的第 2048 个扇区开始。通常跳过开头的扇区(如 MBR 本身、保留空间、对齐到 1MiB 边界)。End: 83884031
: 分区结束于磁盘的第 83884031 个扇区。Sectors: 83881984
: 分区总共有 83881984 个扇区 (83884031 - 2048 + 1)。Size: 40G
: 分区容量约为 40 GiB(83881984 扇区 * 512 字节/扇区 ≈ 42949672960 字节 ≈ 40 GiB)。Id: 83
: 分区类型 ID 是0x83
。Type: Linux
: ID0x83
表示这是一个标准的 Linux 文件系统分区(通常格式化为 ext4, ext3, xfs, btrfs 等)。这是安装 Linux 根文件系统或家目录最常用的分区类型。
/dev/vda
和 /dev/loopX
设备在 Linux 系统中的核心区别:
核心概念区别:
/dev/vda
: 块设备 (Block Device)- 代表什么? 它代表一个物理或虚拟的硬盘驱动器本身。
- 物理性: 在物理服务器上,你可能会看到
/dev/sda
(SCSI/SATA),/dev/nvme0n1
(NVMe)。/dev/vda
是虚拟化环境(如 KVM, VMware, VirtualBox, 云服务器)中常见的虚拟硬盘名称。vd
代表 Virtio Block Device (一种高效的虚拟化 I/O 设备标准),a
表示系统中的第一块虚拟硬盘。 - 功能: 它是操作系统安装、用户数据存储的主要持久化存储设备。系统通常会在这个设备上创建分区(如
/dev/vda1
),然后在分区上创建文件系统(如 ext4, xfs),最后挂载文件系统(如/
,/home
)供系统和用户读写文件。 - 大小: 通常比较大(如你例子中的 40GB),用于存储操作系统、应用程序和用户数据。
- 持久性: 设备及其上的数据在服务器重启后仍然存在(除非被删除或覆盖)。
- 管理: 需要手动或通过安装程序进行分区、格式化和挂载。
/dev/loopX
: 回环设备 (Loop Device)- 代表什么? 它本身不是一个物理或虚拟硬盘。它是一个内核驱动程序,提供了一种机制,将一个普通文件(如 .iso, .img, .squashfs)当作一个块设备来访问。
- 物理性: 纯软件模拟,没有对应的物理硬件。它是一个虚拟接口。
- 功能:
- 挂载文件系统镜像: 最常见的用途。例如,挂载一个下载的 Linux 安装
.iso
文件,让你能像访问光盘一样访问里面的文件,而无需真正刻录到光盘。 - 支持特定应用格式: 像 Snap 包(Ubuntu 的一种应用打包格式)就使用
.squashfs
文件。系统会自动创建/dev/loopX
设备来挂载这些文件,使得 Snap 应用像安装在独立分区中一样运行。 - 创建虚拟磁盘: 可以创建一个大文件(如
disk.img
),格式化为 ext4 文件系统,然后通过/dev/loopX
挂载它,当作一个额外的磁盘分区使用(常用于测试或特殊需求)。
- 挂载文件系统镜像: 最常见的用途。例如,挂载一个下载的 Linux 安装
- 大小: 大小完全取决于它所关联的那个文件的大小(如你例子中从 44.5MB 到 141.9MB)。通常比较小,只包含特定应用或镜像所需的内容。
- 持久性:
- 设备本身 (
/dev/loopX
) 是临时的,重启后可能编号会变或消失。 - 底层关联的文件是持久的。只要文件还在,就可以(手动或自动)重新关联到一个 loop 设备并挂载。
- 设备本身 (
- 管理: 通常由系统自动管理(如 Snap 或挂载 ISO 时)。也可以手动使用
losetup
命令创建和管理:sudo losetup -fP --show yourfile.iso
会找到一个空闲 loop 设备并关联文件,然后mount /dev/loopX /mnt/point
挂载它。
总结关键区别:
特性 | /dev/vda (及其分区 /dev/vda1 ) |
/dev/loopX |
---|---|---|
本质 | (虚拟)硬盘驱动器 | 将普通文件模拟成块设备的 内核接口 |
来源 | 虚拟化平台提供的虚拟磁盘 | Linux 内核提供的功能 (loop 驱动) |
代表 | 存储设备本身 | 一个特定的文件 (ISO, IMG, SquashFS 等) |
大小 | 通常较大 (GB 级别),是主要存储 | 由关联文件决定,通常较小 (MB 级别),用于特定用途 |
主要用途 | 安装操作系统,存储用户数据和应用程序 | 挂载文件系统镜像 (ISO), 支持 Snap 应用等 |
分区 | 通常包含分区表 (dos /gpt ) 和分区 |
通常没有分区表,整个设备对应文件系统(直接挂载文件) |
持久性 | 设备和数据持久存在 | 设备临时,关联的文件持久 |
管理 | 手动分区 (fdisk , parted )、格式化 (mkfs )、挂载 (mount ) |
系统自动管理 (Snap, ISO 挂载器) 或手动 (losetup , mount ) |
典型环境 | 虚拟机、云服务器 | 任何 Linux 系统 (物理机/虚拟机) |
在你的输出中 | 有分区表信息,有分区 (/dev/vda1 ) |
没有分区表信息,显示为单一磁盘设备 |
简单来说:
/dev/vda
是你的“电脑硬盘”(虚拟的)。你在它上面装系统、存文件。/dev/loopX
是系统的“魔术工具”。它能让系统把一个文件(比如一个光盘镜像.iso
或一个 Snap 应用包)**当作一个“假硬盘”**来读取和使用。系统用完后(比如卸载了镜像或关闭了应用),这个“假硬盘”就消失了,但原始文件还在。
总结:
/dev/vda
是虚拟机的主硬盘,有分区 (/dev/vda1
),安装了 Linux 系统。- 那些
/dev/loop0
到/dev/loop5
设备,很可能是系统自动创建的,用于挂载一些文件(比如 Snap 应用包)。它们没有分区表,整个设备就直接对应一个文件系统(通常是只读的 squashfs)。
磁盘的逻辑结构
理解过程
把一圈圈的磁盘给拉直就成了一个一维数组,单位大小是一个扇区。
这样每⼀个扇区,就有了⼀个线性地址(其实就是数组下标),这种地址叫做LBA
真实过程
⼀个细节:传动臂上的磁头是共进退的
柱⾯是⼀个逻辑上的概念,其实就是每⼀⾯上,相同半径的磁道逻辑上构成柱⾯。
所以,磁盘物理上分了很多⾯,但是在我们看来,逻辑上,磁盘整体是由“柱⾯”卷起来的。
磁道:一维数组
柱面:二维数组
整盘:三维数组
整个磁盘不就是多张⼆维的扇区数组表(三维数组?)
所有,寻址⼀个扇区:先找到哪⼀个柱⾯(Cylinder) ,在确定柱⾯内哪⼀个磁道(其实就是磁头位置,Head),在确定扇区(Sector),所以就有了CHS。
我们之前学过C/C++的数组,在我们看来,其实全部都是⼀维数组。
所以,每⼀个扇区都有⼀个下标,我们叫做LBA(Logical Block Address)地址,其实就是线性地址。所以怎么计算得到这个LBA地址呢?
CHS && LBA地址
1. CHS 寻址模式是什么?
- Cylinder (柱面):硬盘由多个盘片堆叠组成,所有盘片上相同半径的磁道构成一个柱面。
- Head (磁头):每个盘面有一个磁头负责读写,磁头数量等于盘面数。
- Sector (扇区):磁道被划分为固定大小的扇区(通常为 512 字节)。
CHS 寻址通过指定 柱面号©、磁头号(H)、扇区号(S) 来定位数据。
2. 地址位数的限制
系统为 CHS 的三个参数分配了固定位数:
- 柱面地址 ©:
10 bit
→ 能表示 (2^{10} = 1024) 个柱面(编号 0~1023)。 - 磁头地址 (H):
8 bit
→ 能表示 (2^8 = 256) 个磁头(编号 0~255)。 - 扇区地址 (S):
6 bit
→ 能表示 (2^6 = 64) 个扇区(编号 1~63,0 保留不用)。
✅ 关键细节:扇区号从 1 开始计数(不是 0),因此实际可用扇区数为 63(1~63)。
3. 最大容量计算
根据 CHS 参数限制:
总扇区数 = 柱面数 × 磁头数 × 扇区数
= 1024 *256 *63总容量 = 总扇区数 × 扇区大小
= 1024 * 256*63 * 512bytes
计算过程:
4. 容量单位的两种解释
按二进制(操作系统视角)
即 8,064 MiB(Mebibytes)。
按十进制(硬盘厂商视角)
通常简称为 8.4 GB。
5. 为什么 CHS 被淘汰?
- 物理限制突破:现代硬盘的柱面/磁头数远超 CHS 的地址范围。
- 逻辑抽象需求:硬盘内部使用 物理扇区到逻辑块 的映射,对外暴露 LBA (Logical Block Addressing) 模式:
- 直接用线性地址(如扇区号 0, 1, 2…)访问数据。
- 支持超大容量(如 48-bit LBA 支持 (2^{48} \times 512) 字节 ≈ 128 PB)。
- 兼容性:BIOS 和操作系统通过 LBA 转换 模拟 CHS,突破 8.4 GB 限制。
总结
- CHS 是早期硬盘的物理寻址方式,受限于位数只能支持约 8.4 GB。
- 现代硬盘使用 LBA,彻底解决了容量瓶颈。
- 计算中的单位差异(8064 MB vs 8.4 GB)源于 二进制 vs 十进制 的换算标准。
💡 如今你看到的磁盘工具(如
fdisk
)显示的扇区地址(如/dev/vda1
的Start=2048
)已是 LBA 模式下的逻辑扇区号,与物理 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
• “//”: 表⽰除取整
所以:从此往后,在磁盘使⽤者看来,根本就不关⼼CHS地址,⽽是直接使⽤LBA地址,磁盘内部⾃⼰转换。
所以:从现在开始,磁盘就是⼀个 元素为扇区 的⼀维数组,数组的下标就是每⼀个扇区的LBA地址。OS使⽤磁盘,就可以⽤⼀个数字访问磁盘扇区了。
引入文件系统
引入"块"概念
其实硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,其实是不会⼀个个扇区地读取,这样效率太低,⽽是⼀次性连续读取多个扇区,即⼀次性读取⼀个”块”(block)。
硬盘的每个分区是被划分为⼀个个的”块”。⼀个”块”的大小是由格式化的时候确定的,并且不可以更改,最常⻅的是4KB
,即连续八个扇区组成⼀个 ”块”。”块”是⽂件存取的最小单位。
stat file_name //用于显示文件或文件系统的详细状态信息
执行 stat main.c 会显示如下信息:
File: main.c # 文件名
Size: 1024 Blocks: 8 IO Block: 4096 regular file # 文件大小、占用块数、块大小
Device: fd00h/64768d Inode: 917527 Links: 1 # 设备号、Inode 编号、硬链接数
Access: (0644/-rw-r--r--) Uid: ( 1000/ user) Gid: ( 1000/ group) # 权限、所有者/组
Access: 2024-06-16 10:30:00.000000000 +0800 # 最后访问时间(atime)
Modify: 2024-06-15 15:45:00.000000000 +0800 # 最后修改时间(mtime,内容修改)
Change: 2024-06-15 15:45:00.000000000 +0800 # 最后状态变更时间(ctime,元数据修改)
Birth: 2024-06-10 09:00:00.000000000 +0800 # 创建时间(birth time,部分系统支持)
注意:
• 磁盘就是⼀个三维数组,我们把它看待成为⼀个"⼀维数组",数组下标就是LBA,每个元素都是扇区。
• 每个扇区都有LBA,那么8个扇区⼀个块,每⼀个块的地址我们也能算出来。
• 知道LBA:块号 = LBA/8。
• 知道块号:LAB=块号*8 + n. (n是块内第⼏个扇区)。
引入"分区"概念
其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有⼀块磁盘并且将它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的⼀种格式化。但是Linux的设备都是以⽂件形式存在,那是怎么分区的呢?
柱⾯是分区的最小单位,我们可以利用参考柱⾯号码的⽅式来进行分区,其本质就是设置每个区的起始柱面和结束柱面号码。 此时我们可以将硬盘上的柱面(分区)进行平铺,将其想象成⼀个大的平⾯。
柱⾯⼤⼩⼀致,扇区个位⼀致,那么其实只要知道每个分区的起始和结束柱⾯号,知道每
⼀个柱⾯多少个扇区,那么该分区多⼤,其实和解释LBA是多少也就清楚了。
文件系统的载体是分区。
引入"inode"概念
到这我们要思考⼀个问题,⽂件数据都储存在”块”中,那么很显然,我们还必须找到⼀个地⽅储存⽂件的元信息(属性信息),⽐如⽂件的创建者、⽂件的创建⽇期、⽂件的⼤⼩等等。这种储存⽂件元信息的区域就叫做inode,中⽂译名为”索引节点”。
ls -i
-i:显示每个文件或目录的inode 编号(inode 是文件系统用于存储元数据的唯一标识符)
-l:以长格式显示文件和目录的详细信息。
Linux
下⽂件的存储是属性和内容分离存储的。
Linux
下,保存⽂件属性的集合叫做inode
,⼀个⽂件,⼀个inode
,inode
内有⼀个唯⼀的标识符,叫做inode号。
所以⼀个⽂件的属性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 */
};
/*
* 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)
备注:EXT2_N_BLOCKS=15
• ⽂件名属性并未纳⼊到inode数据结构内部
• inode的⼤⼩⼀般是128字节或者256,我们后面统⼀128字节
• 任何⽂件的内容⼤⼩可以不同,但是属性大小一定是相同的
到⽬前为⽌,相信⼤家还有两个问题:
- 我们已经知道硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,读取的基本单位
是”块”。“块”⼜是硬盘的每个分区下的结构,难道“块”是随意的在分区上排布的吗?那要怎么找到“块”呢?- 还有就是上⾯提到的存储⽂件属性的inode,⼜是如何放置的呢?⽂件系统就是为了组织管理这些的!!
总结:inode
就是保存⽂件属性的集合,是一个结构体,inode
内有⼀个唯⼀的标识符,叫做inode号。
ext2 文件系统
宏观认识
文件系统和操作系统的关系是什么?
核心依赖关系: 文件系统是操作系统不可或缺的核心组成部分之一。操作系统需要文件系统来管理计算机最基本的资源之一:存储设备(硬盘、SSD、U盘等)上的数据和程序。
抽象层: 文件系统为操作系统(以及运行在其上的应用程序)提供了一个统一、抽象、易于使用的接口来访问存储设备。它将底层物理存储设备(复杂的扇区、块、磁道)的细节隐藏起来,呈现给用户和程序的是直观的“文件”和“目录/文件夹”概念。
管理者: 操作系统通过文件系统来执行对存储设备的组织、管理、访问控制和保护。没有文件系统,操作系统无法有效、安全地存储和检索用户程序和数据。文件系统是操作系统的一部分吗?
是的,文件系统通常是操作系统内核或核心服务的一部分。当你安装一个操作系统时,它必然包含了对一种或多种文件系统的支持。操作系统启动后,其内核必须能够加载并运行文件系统驱动才能访问自己的系统文件和用户数据。
文件系统没有在硬件上吗?
文件系统是软件(规则和实现代码),但这些软件定义的结构信息必须存储在硬件(磁盘)上,才能让操作系统在下次启动时识别和挂载该磁盘上的文件系统。
那么文件系统如何管理磁盘上的文件呢?
后续讲解…
简而言之,操作系统依赖文件系统(软件逻辑)来组织和解释存储在磁盘(硬件)上的数据结构和内容(文件与目录)。
所有的准备⼯作都已经做完,是时候认识下⽂件系统了。我们想要在硬盘上储⽂件,必须先把硬盘格
式化为某种格式的⽂件系统,才能存储⽂件。⽂件系统的⽬的就是组织和管理硬盘中的⽂件。在Linux
系统中,最常⻅的是 ext2
系列的⽂件系统。其早期版本为 ext2
,后来⼜发展出ext3
和 ext4
。ext3
和 ext4
虽然对 ext2
进⾏了增强,但是其核⼼设计并没有发⽣变化,我们仍是以较⽼的 ext2
作为演示对象。
ext2
⽂件系统将整个分区划分成若⼲个同样⼤⼩的块组 (Block Group
),如下图所⽰。只要能管理⼀个
分区就能管理所有分区,也就能管理所有磁盘⽂件。
上图中启动块(Boot Block/Sector)
的⼤⼩是确定的,为1KB
,由PC
标准规定,⽤来存储磁盘分区信
息和启动信息,任何⽂件系统都不能修改启动块。启动块之后才是ext2
⽂件系统的开始。
Block Group
ext2⽂件系统会根据分区的⼤⼩划分为数个Block Group。⽽每个Block Group都有着相同的结构组成。政府管理各区的例⼦
管理好了一个 Block Group,就能管理好整个分区,管理好整个分区,就能管理好所有的分区。
块组内部构成
超级块(Super Block)
存放⽂件系统本⾝的结构信息,描述整个分区的⽂件系统信息。记录的信息主要有:bolck
和 inode
的
总量,未使⽤的block
和inode
的数量,⼀个block
和inode
的⼤⼩,最近⼀次挂载的时间,最近⼀次写⼊数据的时间,最近⼀次检验磁盘的时间等其他⽂件系统的相关信息。Super Block
的信息被破坏,可以说整个⽂件系统结构就被破坏了。
超级块在每个块组的开头都有⼀份拷⻉(第⼀个块组必须有,后⾯的块组可以没有)。 为了保证⽂件系统在磁盘部分扇区出现物理问题的情况下还能正常⼯作,就必须保证⽂件系统的
super block
信息在这种情况下也能正常访问。所以⼀个⽂件系统的super block
会在多个block group
中进⾏备份,这些super block
区域的数据保持⼀致。
/*
* 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 */
/*
* These fields are for EXT2_DYNAMIC_REV superblocks only.
*
* Note: the difference between the compatible feature set and
* the incompatible feature set is that if there is a bit set
* in the incompatible feature set that the kernel doesn't
* know about, it should refuse to mount the filesystem.
*
* e2fsck's requirements are more strict; if it doesn't know
* about a feature in either the compatible or incompatible
* feature set, it must abort and not try to meddle with
* things it doesn't understand...
*/
__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 */
/*
* Performance hints. Directory preallocation should only
* happen if the EXT2_COMPAT_PREALLOC flag is on.
*/
__u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/
__u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */
__u16 s_padding1;
/*
* Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.
*/
__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 */
};
GDT(Group Descriptor Table)
块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描
述符存储⼀个块组 的描述信息,如在这个块组中从哪⾥开始是inode Table
,从哪⾥开始是Data Blocks
,空闲的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];
};
块位图(Block Bitmap)
Block Bitmap
中记录着Data Block
中哪个数据块已经被占⽤,哪个数据块没有被占⽤,Block Bitmap
是一个位图。
inode位图(Inode Bitmap)
每个bit
表⽰⼀个inode
是否空闲可⽤。Inode Bitmap
中记录着Inode Table
中哪个inode
已经被占⽤,哪个inode
没有被占⽤
i节点表(Inode Table)
- 存放⽂件属性 如 ⽂件⼤⼩,所有者,最近修改时间等
- 当前分组所有Inode属性的集合
- inode编号以分区为单位,整体划分,不可跨分区
Data Block
数据区:存放⽂件内容,也就是⼀个⼀个的Block。根据不同的⽂件类型有以下⼏种情况:
- 对于普通⽂件,⽂件的数据存储在数据块中。
- 对于⽬录,该⽬录下的所有⽂件名和⽬录名存储在所在⽬录的数据块中,除了⽂件名外,ls -l命令看到的其它信息保存在该⽂件的inode中。
- Block 号按照分区划分,不可跨分区。
inode和datablock映射(弱化)
知道了inode
号,就找到了对应的文件,那么文件存放的数据在哪里呢?在Data Blocks
里,Data Blocks
里有那么对的块,我怎么知道哪几个块属于我呢?
inode
内部存在 __le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
,EXT2_N_BLOCKS
=15,就是⽤来进⾏inode
和block
映射的。该数组存放的是块号,通过块号就可以找多对应的数据块。
这样⽂件=内容+属性,就都能找到了。
数组的大小只能存放15个块号,够吗?够的!!!
文件系统解决大文件存储的关键机制——多级间接块寻址。
1. 基础结构:inode 中的块指针数组
- 在经典的 Unix/Linux 文件系统(如 ext2/ext3/ext4)中,inode 结构体确实包含一个固定大小的块指针数组(通常是 15 个条目)。
- 这 15 个条目被分为 4 种类型:
类型 数量 作用 直接指向 直接块 12 存储小文件的数据块编号 数据块本身 一级间接块 1 存储一个“块编号数组”的地址 间接块(指针块) 二级间接块 1 存储“一级间接块地址数组”的地址 二级间接块 三级间接块 1 存储“二级间接块地址数组”的地址 三级间接块
2. 如何解决“15个不够用”的问题?
核心思想:用指针块存储更多的指针,形成多级树状结构。
假设一个块大小 = 4KB
,每个块编号用 4字节
存储(32位系统):
① 直接块 (12个)
- 直接指向 12 个数据块。
- 最大文件大小 =
12 块 × 4KB/块 = 48KB
。
② 一级间接块 (1个)
- 该条目指向一个 间接块(专门存放块编号的块)。
- 一个 4KB 的块可存储
4096 / 4 = 1024 个块编号
。 - 新增容量 =
1024 块 × 4KB = 4MB
。
累计大小 =48KB + 4MB ≈ 4.05MB
。
③ 二级间接块 (1个)
- 该条目指向一个 二级间接块(存放“一级间接块地址”的块)。
- 二级间接块可存储
1024 个一级间接块地址
。 - 每个一级间接块又可指向
1024 个数据块
。 - 新增容量 =
1024 × 1024 块 × 4KB = 4,194,304 块 × 4KB ≈ 16GB
。
累计大小 ≈4.05MB + 16GB ≈ 16GB
。
④ 三级间接块 (1个)
- 该条目指向一个 三级间接块(存放“二级间接块地址”的块)。
- 三级间接块可存储
1024 个二级间接块地址
。 - 每个二级间接块可指向
1024 个一级间接块
→ 每个一级间接块指向1024 个数据块
。 - 新增容量 =
1024 × 1024 × 1024 块 × 4KB = 1,073,741,824 块 × 4KB ≈ 4TB
。
累计总大小 ≈16GB + 4TB ≈ 4TB
(32位系统下)。
3. 实际计算示例(ext4 文件系统)
寻址级别 | 数据块数量 | 容量 (4KB/块) |
---|---|---|
直接块 | 12 | 48 KB |
一级间接 | 1024 | 4 MB |
二级间接 | 1024 × 1024 | 4 GB |
三级间接 | 1024 × 1024 × 1024 | 4 TB |
总计 | ≈ 10 亿块 | ≈ 4 TB |
注:64位系统使用 8字节 块编号时,三级间接可支持 ~17,000 TB 的文件(理论值)。
请解释:知道inode号的情况下,在指定分区,请解释:对⽂件进⾏增、删、查、改是在做什么?
结论:
• 分区之后的格式化操作,就是对分区进⾏分组,在每个分组中写⼊SB、GDT、Block Bitmap、Inode Bitmap
等管理信息,这些管理信息统称: ⽂件系统
• 只要知道⽂件的inode号,就能在指定分区中确定是哪⼀个分组,进⽽在哪⼀个分组确定是哪⼀个inode
• 拿到inode⽂件属性和内容就全部都有了。
创建⼀个新⽂件主要有以下4个操作:
目录与文件名
问题:
- 我们访问⽂件,都是⽤的⽂件名,没⽤过inode号啊?
- ⽬录是⽂件吗?如何理解?
答案:
⽬录也是⽂件,但是磁盘上没有⽬录的概念,只有⽂件属性+⽂件内容的概念。
⽬录的属性不⽤多说,内容保存的是:⽂件名和Inode号的映射关系
所以,访问⽂件,必须打开当前⽬录,根据⽂件名,获得对应的inode号,然后进⾏⽂件访问
所以,访问⽂件必须要知道当前⼯作⽬录,本质是必须能打开当前⼯作⽬录⽂件,查看⽬录⽂件的
内容!
路径解析
问题:打开当前⼯作⽬录⽂件,查看当前⼯作⽬录⽂件的内容?当前⼯作⽬录不也是⽂件吗?我们访问
当前⼯作⽬录不也是只知道当前⼯作⽬录的⽂件名吗?要访问它,不也得知道当前⼯作⽬录的inode吗?
- 答案1:所以也要打开:当前⼯作⽬录的上级⽬录,额…,上级⽬录不也是⽬录吗??不还是上⾯的问
题吗?- 答案2:所以类似"递归",需要把路径中所有的⽬录全部解析,出⼝是"/"根⽬录。
最终答案3:⽽实际上,任何⽂件,都有路径,访问⽬标⽂件,⽐如:/home/zx/code/test/test/test.c都要从根⽬录开始,依次打开每⼀个⽬录,根据⽬录名,依次访问每个⽬录下指定的⽬录,直到访问到test.c。这个过程叫做Linux路径解析。
💡 注意:
• 所以,我们知道了:访问⽂件必须要有⽬录+⽂件名=路径的原因
• 根⽬录固定⽂件名,inode
号,⽆需查找,系统开机之后就必须知道
可是路径谁提供?
• 你访问⽂件,都是指令/⼯具访问,本质是进程访问,进程有CWD!进程提供路径。
• 你open⽂件,提供了路径
可是最开始的路径从哪里来?
• 所以Linux为什么要有根⽬录, 根⽬录下为什么要有那么多缺省⽬录?
• 你为什么要有家⽬录,你⾃⼰可以新建⽬录?
• 上⾯所有⾏为:本质就是在磁盘⽂件系统中,新建⽬录⽂件。⽽你新建的任何⽂件,都在你或者系统指定的⽬录下新建,这不就是天然就有路径了嘛!
• 系统+⽤⼾共同构建Linux路径结构.
ls -l /proc/<PID>/cwd //查看进程的cwd
路径缓存
问题1:
Linux
磁盘中,存在真正的⽬录吗?
答案:不存在,只有⽂件。只保存⽂件属性+⽂件内容。
问题2:访问任何⽂件,都要从
/
⽬录开始进⾏路径解析?
答案:原则上是,但是这样太慢,所以Linux会缓存历史路径结构。
问题2:
Linux
⽬录的概念,怎么产⽣的?
答案:打开的⽂件是⽬录的话,由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 */
/*
* The next three fields are touched by __d_lookup. Place them here
* so they all fit in a cache line.
*/
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 */
/*
* d_child and d_rcu can share memory
*/
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结构,包括普通⽂件。这样所有被打开的⽂件,就可以在内存中
形成整个树形结构
• 整个树形节点也同时会⾪属于LRU(Least Recently Used,最近最少使⽤)结构中,进⾏节点淘汰
• 整个树形节点也同时会⾪属于Hash,⽅便快速查找
• 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何⽂件,都在先在这
棵树下根据路径进⾏查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径
挂载分区
我们已经能够根据inode号在指定分区找⽂件了,也已经能根据⽬录⽂件内容,找指定的inode了,在指定的分区内,我们可以为所欲为了。
问题:inode不是不能跨分区吗?Linux不是可以有多个分区吗?我怎么知道我在哪⼀个分区???
什么是挂载分区?
核心概念: 挂载分区就是将存储设备(如硬盘分区、U盘、光盘、网络存储等)上的文件系统,关联到Linux
系统目录树中的一个特定目录(称为挂载点) 的过程。
挂载的主要目的是灵活地组织和管理存储资源(多硬盘、分区)、隔离系统与用户数据、优化性能、方便地使用可移动介质和网络存储,并为系统管理和高级功能提供基础。
磁盘–>分区–>格式化–>我们依然不能使用这个分区 。我们的分区一定要和一个特定目录进行关联,通过进入这个目录,就进入这个分区 – > 挂载。
文件系统总结
软硬连接
硬链接
我们看到,真正找到磁盘上⽂件的并不是⽂件名,⽽是inode。其实在linux中可以让多个⽂件名对应于同⼀个inode。
ln <源文件路径> <硬链接路径>
软链接
硬链接是通过inode引⽤另外⼀个⽂件,软链接是通过名字引⽤另外⼀个⽂件,但实际上,新的⽂件和被引⽤的⽂件的inode不同,应⽤常⻅上可以想象成⼀个快捷⽅式。
ln -s <源文件或目录的真实路径> <链接的路径/名称>
软硬连接对比
- 软连接是独⽴⽂件
- 硬链接只是⽂件名和⽬标⽂件inode的映射关系
软硬连接的用途
硬链接
.
和..
就是硬链接- ⽂件备份
- 软连接
- 类似快捷⽅式