第1章 硬盘硬件工作原理
1.1 机械硬盘物理结构
磁盘结构
机械硬盘内部由多个磁盘面(platter)叠加组成,每个盘面上方有一个磁头(head)负责读写。逻辑结构包含以下概念:
- 磁盘面:磁盘由一叠盘面构成,每个盘面有对应的磁头。
- 磁道(Track):盘面上围绕圆心划分的多个同心圆圈。
- 柱面(Cylinder):所有盘片上同一位置的磁道组成的立体柱面。
- 扇区(Sector):每个磁道又划分为多个扇区,是磁盘存储的最小单位。
扇区与纠错码
扇区之间并非紧挨,每个扇区结尾存储纠错码(ECC)。若读取时发生错误,磁头会在下一圈旋转时重新读取该扇区。
存储容量计算:
- 单柱面容量 = 每个扇区字节数 × 每柱面扇区数 × 磁盘面数
- 总容量 = 单柱面容量 × 总柱面数
动手查看:Linux 下 fdisk 命令
# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sdb 8:16 0 20T 0 disk
`-sdb1 8:17 0 20T 0 part /search
sda 8:0 0 278.5G 0 disk
|-sda1 8:1 0 200M 0 part /boot
`-sda2 8:2 0 278.3G 0 part
|-vgroot-lvroot (dm-0) 253:0 0 10G 0 lvm /
|-vgroot-lvswap (dm-1) 253:1 0 8G 0 lvm [SWAP]
|-vgroot-lvvar (dm-2) 253:2 0 15G 0 lvm /var
|-vgroot-lvusr (dm-3) 253:3 0 10G 0 lvm /usr
`-vgroot-lvopt (dm-4) 253:4 0 136.7G 0 lvm /opt# fdisk -l /dev/sda
Disk /dev/sda: 299.0 GB, 298999349248 bytes
255 heads, 63 sectors/track, 36351 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk identifier: 0x00053169
Device Boot Start End Blocks Id System
/dev/sda1 * 1 26 204800 83 Linux
Partition 1 does not end on cylinder boundary.
/dev/sda2 26 36352 291785728 8e Linux LVM关键参数解读:
- 255 heads:逻辑磁头数(实际物理磁头较少,是虚拟值)。
- 36351 cylinders:每个盘面上有36351个磁道。
- 63 sectors/track:每个磁道63个扇区。
- Sector size (logical/physical):逻辑扇区512字节,物理扇区4096字节(4K对齐)。
- Units:每个磁道容量 = 255盘面 × 63扇区 × 512字节 = 8225280字节。
- 总容量:36351 cylinders × 8225280 bytes ≈ 299 GB。
关于 fdisk 结果的几个疑问
-
每个磁道容量是否相同?
老式磁盘确实每个磁道容量相同(等密度),但现代磁盘采用 等密度结构,外圈磁道扇区比内圈多,并使用 LBA(Logic Block Address) 线性编址,由磁盘控制器完成 CHS 到 LBA 的转换。 -
为什么物理扇区 size 是 4096 bytes?
为兼容旧软件链(BIOS、操作系统等),新磁盘通过控制器将 4096 字节物理扇区映射为 8 个 512 字节逻辑扇区。可通过以下命令查看:# cat /sys/block/sda/queue/physical_block_size # cat /sys/block/sda/queue/logical_block_size -
磁头真的有255个?
不是。实际物理磁头通常只有几个(如 2~8 个),fdisk显示的 255 heads 是虚拟值,与 cylinders 一样,都是为了兼容旧寻址方式而模拟的(使用 CHS 虚拟地址,实际映射为 LBA 线性地址)。
1.2 磁盘分区方法
分区目的与方案对比
操作系统对磁盘管理的第一步是分区。假设一块硬盘有 50 个盘面、3000 个柱面,有两种分区方案:
- 方案一:按盘面分区(C盘 0-10 盘面,D盘 10-20 盘面……)
- 方案二:按柱面分区(C盘 0-1000 柱面,D盘 1001-20001 柱面……)
磁盘读写耗时 = 寻道时间 + 旋转延迟 + 存取时间
- 寻道时间:3-15 ms,取决于磁头当前与目标磁道的距离。
- 旋转延迟:万转磁盘约 0-6 ms。
- 存取时间:零点几 ms(电磁操作)。
性能关键
方案一导致磁头在 3000 多个磁道间频繁跳转,寻道时间居高不下;方案二将磁头活动范围限制在连续柱面内,显著降低寻道时间。因此所有操作系统均采用 按柱面分区 的方案。
实践:fdisk 分区操作
分区时指定起始和结束柱面号:
fdisk /dev/sdb
Command (m for help): n
First cylinder (1-13054, default 1): 1
Last cylinder, +cylinders or +size{K,M,G} (1-13054, default 13054): +1000第0号柱面通常保留给引导加载程序和分区表,分区不能从 0 柱面开始。
小结
操作系统通过按柱面划分分区,最大化利用磁盘顺序存取的优势,降低寻道时间,提升读写性能。这一设计在后续的 RAID 阵列 和 文件系统设计 中也有体现。
所以所有的操作系统采用的都是方案二,没有用方案一的。如果你在Linux下使用过fdisk进行过分区的话可以注意到以下信息:
分区的过程就是你输入起始柱面号和截至柱面号的过程。不过在实际中,分区并不能从0号柱面开始的,因为磁盘的第一个磁道对应的柱面会被用来安装引导加载程序以及磁盘分区表。
所以,操作系统通过按磁道对应的柱面划分分区,来降低磁盘IO所花费的的寻道时间,最终提高磁盘的读写性能。
1.3 硬盘阵列
我们使用的计算机的全称叫电子计算机,前面有电子两个字,这说的是整个计算机中的核心元器件基本上都是电子单元组成的。但机械硬盘却是一个特殊的例外,它更多是用机械技术做出来的一个产品。当把带有机械技术基因的磁盘搭到计算机,尤其是再应用到服务器领域的时候,暴露出了机械技术的两个严重问题:
- 速度慢。 如果把内存和CPU的速度比作汽车和飞机的话,这个大哥毫秒级别的延迟几乎就是牛车级别的。
- 容易坏。 经常听说谁谁的磁盘坏了,很少有听说过谁的内存条,CPU坏了。笔者就有在读研期间实验室里正在拷贝资料,突然一个断电直接废了一块硬盘的经历。
要想保证服务器运转的稳定和高速,就必须解决硬盘从娘胎里带出来的这两个缺陷。
多硬盘连接
问题简单明了,我们要解决速度慢、容易坏两个问题。很自然,单兵作战不行,那我们就想到往上怼一个班,多块硬盘一起上。但问题是,假如给你N块硬盘,让你来出一个技术方案,你会怎么设计呢?
第一个方案就是,把一个文件分成N片,每一片都散列在不同的硬盘上。这样当文件进行读取的时候,就可以N块硬盘一起来工作,从而达到读取速度提高到N倍的效果,这就是 RAID 0。
不过这个方案没有解决容易坏的问题,任何一块硬盘坏了都会导致存储系统故障。
第二个方案就是,仍然把文件分片,但是所有的分片都存在一块硬盘上,其它的硬盘只存拷贝。 这既提高了硬盘的访问速度,也解决了坏的问题。任意一块硬盘坏了,存储系统都可以正常使用,只不过速度会打一点折扣。
不过这个方案又带来了新的问题,那就是实现成本有点高了。假如我们用256G硬盘想实现512G的存储容量的话,最少得需要4块硬盘才能实现。
有没有折中一点的方案呢?有,而且很多。我们这里只说下最常见的 RAID 5。
RAID 5同样要对文件进行分片,但是不对存储的数据进行备份,而是会再单独存一个校验数据片。假如文件分为A1 A2 A3,然后需要再存一个校验片到别的磁盘上。这样不管A1,A2还是A3那一片丢失了,都可以根据另外两片和校验片合成出来。既保证了数据的安全性,又只用了一块磁盘做冗余存储。
假如我们有8块256GB的硬盘,那么RAID 5方案下的磁盘阵列从用户角度来看可用的存储空间是7×256GB,只“浪费”了一块盘的空间,所以目前RAID 5应用比较广泛。
Raid卡缓存
硬盘延迟是毫秒级别的,即使是多快硬盘并行,也只能提升数倍而已,不能够达到量级的提升。和CPU内存的纳秒级别工作频率比起来,还是太慢了。
在计算机界,没有缓存解决不了的速度问题,如果有,那就再加一层。现代磁盘本身也基本都带了缓存,另外在一些比较新的raid卡里,硬件开发者们又搞出来了一层“内存”,并且还自己附带一块电池,这就是 RAID卡缓存。我们看下几款主流RAID卡的配置:
| RAID卡型号 | 描述 | 缓存与电池 | 支持的RAID级别 |
|---|---|---|---|
| PERC S120 | 入门软件阵列卡,主板集成无缓存 | 无 | RAID 0, 1 |
| PERC H330 | 入门硬件RAID卡 | 无板载缓存 | RAID 0, 1, 5, 10, 50 |
| PERC H730 | 主流硬件RAID卡 | 1GB缓存和电池 | RAID 0, 1, 5, 6, 10, 50, 60 |
| PERC H730P | 高性能硬件RAID卡 | 2GB缓存和电池 | RAID 0, 1, 5, 6, 10, 50, 60 |
| PERC H830 | 同H730P,但没有内置接口,使用外置接口连接附加存储磁盘柜用 | 2GB缓存和电池 | 同H730P |
拿目前服务器端出镜率比较高的H730和H730P来看,他们分别带了1G和2G的缓存卡,并且自带电池。电池的作用就是当发现主机意外断电的时候,能够快速把缓存中的数据写回到磁盘中去。对于写入,一般操作系统写到这个RAID卡里就完事了,所以速度快。对于读取也是,只要缓存里有,就不会透传到磁盘的机械轴上。
另外想再补充说一点的就是文件相关函数里设置DIRECT I/O仅仅只能绕开操作系统本身的Page Cache,而RAID卡里的缓存,对于Linux来说,可以说是算是一个黑盒。换句话说,就是操作系统并不清楚RAID卡是从缓存里吐的数据,还是真正从硬盘里读的。
动手查看你的RAID配置
了解了raid基本原理以后,我们可以实际动手查看一下机器上的raid情况。这里拿我手头的一台服务器举例。
通过 cat /proc/scsi/scsi 我们可以查看到raid卡的型号:
Host: scsi10 Channel: 02 Id: 01 Lun: 00
Vendor: DELL Model: PERC H730 Mini Rev: 4.27
Type: Direct-Access ANSI SCSI revision: 05
可以看到我的这台服务器raid卡使用的是PERC H730,这块RAID卡带有1G缓存和电池。
再看我们的硬盘阵列:
# /opt/MegaRAID/MegaCli/MegaCli64 -LDInfo -Lall -aALL
Virtual Drive: 1 (Target Id: 1)
Name :
RAID Level : Primary-5, Secondary-0, RAID Level Qualifier-3
Size : 1.633 TB
Sector Size : 512
Is VD emulated : No
Parity Size : 278.875 GB
State : Optimal
Strip Size : 128 KB
Number Of Drives : 7
Span Depth : 1
RAID Level列标明的就是当前RAID组的RAIN级别。对照下面:
- RAID 1: Primary-1, Secondary-0, RAID Level Qualifier-0
- RAID 0: Primary-0, Secondary-0, RAID Level Qualifier-0
- RAID 5: Primary-5, Secondary-0, RAID Level Qualifier-3
- RAID 10: Primary-1, Secondary-3, RAID Level Qualifier-0
可以看到,本机RAID Level是RAID 5。
另外Stripe Size叫做条带大小,我机器上的配置是128KB。假如有一个512KB的文件,它就会被分成4个条带,每个128KB。这些条带可能会分散在不同的磁盘上,如果一次性读取的话,多个硬盘就可以一起转动机械轴,读取速度就会提高到原来的数倍。不过要说明的是,如果文件小于这个条带大小,小于128K,那么RAID下的多块硬盘对于该文件的读取耗时也不会有帮助。
继续查看组成RAID的所有的磁盘的状况:
# /opt/MegaRAID/MegaCli/MegaCli64 -PDList -aALL
......
Drive's position: DiskGroup: 1, Span: 0, Arm: 0
Device Id: 1
...
Raw Size: 279.396 GB [0x22ecb25c Sectors]
Non Coerced Size: 278.896 GB [0x22dcb25c Sectors]
Coerced Size: 278.875 GB [0x22dc0000 Sectors]
Sector Size: 512
Logical Sector Size: 512
Physical Sector Size: 512
Inquiry Data: SEAGATE ST300MM0008 TT31S42310JR
...
...
...
Drive's position: DiskGroup: 1, Span: 0, Arm: 6
Device Id: 7
Raw Size: 279.396 GB [0x22ecb25c Sectors]
Non Coerced Size: 278.896 GB [0x22dcb25c Sectors]
...
看到属于DiskGroup 1的总共有7块硬盘,每块的大小都是278GB左右。6块盘的总容量大小就是1.6T左右,确实是只“浪费”了一块硬盘的容量来保障数据的安全性!
另外Inquiry Data也显示了硬盘的制造商,已经型号。这些磁盘都是希捷的300G的机械硬盘,经Google查询其转速为1万转每分钟。
小结
机械技术和电子技术比较起来,稳定性要差很多。所以机械背景出身的硬盘既慢又容易坏。为了解决这个问题,RAID技术应运而生。通过一定的冗余原始存储或者校验数据提供安全性的保障,通过增加带电子基因的缓存,合理调度多块磁盘的机械轴,提高了磁盘IO的读写速度。在Linux下可以通过MegaCli工具来查看你的RAID组情况,强烈你建议实际动手查看一下!
1.4 固态硬盘结构
前面几篇文章,主要围绕的是机械硬盘来说的。目前SSD目前应用也越来越广了,值得我们花精力琢磨琢磨。SSD硬盘是地地道道电子技术下的产品,因为不像机械硬盘IO时依赖两个耗时的机械轴行为:磁盘旋转以及磁道寻道,SSD硬盘的访问延迟要比机械硬盘要低的多,在随机IO下的表现尤其明显。我们今天从最底层出发,看看SSD的几个内部机理。
SSD的组成结构
机械硬盘和SSD虽然都同为硬盘,但底层实现技术却完全不一样,机械硬盘使用的是磁性材料记忆,而SSD用的是类似U盘的闪存技术。实现技术的不同,必然在硬盘内部结构上他们就有天壤之别。他们的果照对比如下图所示:
(图:机械硬盘与SSD内部结构对比,机械硬盘内部有一摞圆形碟片,SSD则由电路和黑色存储颗粒构成)
不像机械硬盘里的一摞子圆形碟片,SSD是由一些电路和黑色的存储颗粒构成。SSD硬盘是基于NAND Flash存储技术的,属于非易失性存储设备,换成人话说,就是掉电了数据不会丢。其中我们眼睛看见的黑色的存储颗粒叫一个NAND Package,每个Package里面会包含多个Die。我们“拆开”一个Die来看一下:
(图:Die内部结构,包含多个Plane,每个Plane包含多个Block,每个Block包含多个Page)
每个Die有若干个Plane,每个Plane有若干个Block,每个Block有若干个Page。Page是磁盘进行读写的最小单位,一般为2KB/4KB/8KB/16KB等。
SSD里的扇区
前面我们介绍机械硬盘的时候,说到由于历史原因,操作系统等软件里,512B扇区的概念是“根深蒂固”。新的机械硬盘虽然把物理扇区已经做到4KB的了,但没办法,为了兼容老系统还得整出个逻辑扇区的概念来适配。到了SSD里也一样,虽然每一个物理Page的大小为2K到16K不等,但是为了兼容性,也必须得整出个逻辑扇区才行。
SSD控制器在逻辑上会把整个磁盘再重新划分成一个个的“扇区”,采用和新机械硬盘一样的LBA方式来进行编址(整个磁盘的扇区从0到某个最大值方式排列,并连成一条线)。当需要读取某几个”扇区”上数据的时候,SSD控制器通过访问这个LBA Map Table,再来找到要实际访问的物理Page,如下图:
(图:LBA映射到物理Page的示意图)
不过SSD最小的读写单位就是Page,他是没办法只扇区来进行读写的。
最底层的闪存单元
上面我们看到SSD是由一个个的Page组成。而在每一个Page里,又包含了许许多多的闪存单元。现代的闪存单元有多种类型,目前主流的主要分为SLC、MLC和TLC。
| 闪存类型 | 单cell电压变化 | 单cell表示bit数 | 速度 | 擦写次数 | 价格 |
|---|---|---|---|---|---|
| SLC (Single-Level Cell) | 两种 | 1bit | 快 | 约10W次 | 贵 |
| MLC | MLC (Multi-Level Cell) | 四种 | 2bit | 一般 | 约3K次 |
| TLC (Trinary-Level Cell) | 八种 | 3bit | 慢 | 约1K次 | 便宜 |
在SLC里,一个单元的电压只分成高低两种状态,所以只能表示1bit数据。到了MLC,硬是把一个单元里的电压按照高低分成了四种状态,所以可以表示2bit。到了TLC,直接一个单元应拆分成8个电压高低不同的状态,为了表示3bit。由于TLC在数据读写需要八种不同电压状态,而施加不同的电压状态、就需要更精确,也就需要更长的时间才能得以实现。另外由于电压状态多,出错的可能性也会更大。所以,以上三种闪存单元对比:从性能和稳定性角度来看,SLC最好。从容量角度看,TLC最大。这就是为什么日常我们看到的工业级的SSD要比笔记本SSD要贵很多,其中一个很重要的原因就是工业级的盘往往采用的闪存单元是SLC或MLC,而我们家庭用的笔记本一般都是TLC,因为便宜嘛。
目前个人PC上消费用的主流闪存类型大部分都是TLC的,因为价格便宜,容量大。
思考
假设某SSD的Page大小是4KB,一个文件是16KB。那么该文件是存在一个黑色的存储颗粒里,还是多个?
我们先看看SSD的逻辑结构用一个直观一点的图来看:
(图:SSD内部多个Flash通道示意图,多个存储颗粒通过多条通道连接到控制器)
假设只写在一个颗粒里,那么对该文件进行读取的时候,就只能用到一条Flash通道,这样速度就会比较慢。如果存在相邻的4个颗粒里,每个写入4KB。这样多条Flash通道的带宽会充分发挥出来,传输速度也更快。所以,实际中是分散在多个颗粒上的。
思考
这个例子说明,SSD的性能不仅取决于闪存单元类型,还与内部并行度(通道数)密切相关。多通道并行读写是SSD高速的重要基础。
Github: https://github.com/yanfeizhang/coder-kung-fu
欢迎关注个人公众号“开发内功修炼”,了解你的每一比特,用好你的每一纳秒。