第一章 硬盘硬件工作原理

1.1 机械硬盘物理结构

今天我以磁盘结构作为硬盘的开篇,来分享我这些年在这方面的思考。

磁盘结构

为了方便讨论,我们还是先从最基本的磁盘物理结构说起吧,对于常见的机械磁盘,分磁盘面磁道柱面扇区。(注意本文只讨论机械磁盘,SSD先放一放再说)。机械硬盘拆开以后,结构如下。

[Image 39 on Page 4] [Image 40 on Page 4]

我们再用一个逻辑图看一下。

[Image 45 on Page 5]

可见有以下概念:

  • 磁盘面:磁盘是由一叠磁盘面叠加组合构成,每个磁盘面上都会有一个磁头负责读写。
  • 磁道(Track):每个盘面会围绕圆心划分出多个同心圆圈,每个圆圈叫做一个磁道。
  • 柱面(Cylinders):所有盘片上的同一位置的磁道组成的立体叫做一个柱面。
  • 扇区(Sector):以磁道为单位管理磁盘仍然太大,所以计算机前辈们又把每个磁道划分出了多个扇区。

所以,磁盘存储的最小组成单位就是扇区

容量计算公式

单柱面的存储容量 = 每个扇区的字节数 × 每个柱面扇区数 × 磁盘面数
整体磁盘的容量就等于单柱面容量乘以总的柱面数字。

扇区与扇区之间其实不是紧挨着的,而是在每个扇区结尾其实还有一个存储纠错码的位置。假设某一个扇区读取时发生了错误,这样在扇区结尾的纠错码就能发现。磁头就会在磁盘下一圈转过来的时候再读取一遍。

动手实际查看

Linux相比较 windows 操作系统,一个优点就是对开发非常友好和透明。只要你愿意,你总能扒到你想要的信息。Linux上可以通过 fdisk 命令,来查看当前系统使用的磁盘的这些物理信息。

首先我们查看服务器上安装的硬盘数量以及大小,这需要借助 lsblk 这个命令。

# 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

通过上面命令我们可以看到,笔者的服务器上装了两块硬盘,分别是 sda(278.5G)和 sdb(20T)。接下来我们再通过 fdisk 这个命令来查看硬盘更详细的信息:

# 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

可以看出 sda 这块磁盘:

  • 有 255 个 heads(磁头),也就是说共有 255 个盘面。
  • 36351 个 cylinders,也就是说每个盘面上都有 36351 个磁道。
  • 63 sectors/track 说的是每个磁道上共有 63 个扇区。
  • 逻辑扇区大小是 512 bytes。
  • 上面的 Units 说的是每个磁道的存储容量大小,8225280 bytes(= 255 盘面 × 63 扇区 × 逻辑扇区大小 512 字节)。
  • 那么该磁盘的总大小 = 36351 cylinders × Units(8225280 bytes) = 299 GB。

关于 fdisk 结果中的几个疑问

问题1:每一个 units 的可存储的数据都是一样的,都是 8225280 字节?

按理说,磁道是一组同心圆,越是外圈的磁道周长会越长,存储的数据应该越多才对。这个问题的答案其实应该按时间来看:

在老式的磁盘里,确实是每个磁道数据都是一样的。这样越是内圈磁道的存储密度越大。目的就是为了访问方便,通过一个 CHS 地址:柱面地址(Cylinders)磁头地址(Heads)扇区地址(Sectors) 直接定位到存储数据所在的扇区。但是这产生的问题就是外圈磁道的数据密度没有充分发挥出来,造成磁盘存储容量很难提升。

现代的磁盘人们改用等密度结构生产硬盘,也就是说,外圈磁道的扇区比内圈磁道多。这种磁盘里扇区是线性编号的,即从 0 到某个最大值方式排列,并连成一条线。这种寻址模式叫做 LBA,全称为 Logic Block Address(即扇区的逻辑块地址)。磁盘内部是自己会通过磁盘控制器来完成 CHS 到 LBA 的转换,进而定位到具体的物理扇区。

问题2:为什么在 fdisk 命令的结果里,存在一个 physical Sector size 是 4096 bytes?

现在新的磁盘真正的扇区也不是 512 字节,真正磁盘的 I/O size 和 physical Sector size 都是 4096 bytes。但这时存在一个问题是扇区大小为 512 字节的假设已经贯穿于整个软件链,比如 BIOS,启动加载器,操作系统内核,文件系统代码,以及磁盘工具,等等。直接切换到 4096 byte 兼容性问题太大了,所以每个新的磁盘控制器将 4096 字节的物理扇区对应成了 8 个 512 字节的逻辑扇区,兼容各种老软件。

除了 fdisk -l 命令外,如下方式也可以查看物理/逻辑扇区大小:

# cat /sys/block/sda/queue/physical_block_size
# cat /sys/block/sda/queue/logical_block_size

问题3:磁头真的有 255 个?

我们先来看一张从磁盘上拆下来的磁头的真实照片。

[Image 52 on Page 7]

上面的图片里只有几个磁头,如果硬盘里真的装下 255 个这样的磁头的话,很难想象磁盘得有多厚。而且磁头多了以后硬盘的可靠性就越差,因为多磁头出故障的几率总会比单磁头要高一些。所以 fdisk -l 里看到的 255 heads 其实和扇区一样,也是虚拟出来的。另外 cylinders 也一样,也是虚拟出来的。


1.2 固态硬盘结构

前面几篇文章,主要围绕的是机械硬盘来说的。目前 SSD 目前应用也越来越广了,值得我们花精力琢磨琢磨。SSD 硬盘是地地道道电子技术下的产品,因为不像机械硬盘 IO 时依赖两个耗时的机械轴行为:磁盘旋转,以及磁道寻道,SSD 硬盘的访问延迟要比机械硬盘低的多,在随机 IO 下的表现尤其明显。我们今天从最底层出发,看看 SSD 的几个内部机理。

SSD 的组成结构

机械硬盘和 SSD 虽然都同为硬盘,但底层实现技术却完全不一样,机械硬盘使用的是磁性材料记忆,而 SSD 用的是类似 u 盘的闪存技术。实现技术的不同,必然在硬盘内部结构上他们就有天壤之别。他们的果照对比如下图所示:

[Image 56 on Page 8] [Image 57 on Page 8]

不像机械硬盘里的一摞子圆形碟片,SSD 是由一些电路和黑色的存储颗粒构成。SSD 硬盘是基于 NAND Flash 存储技术的,属于非易失性存储设备,换成人类话说,就是掉电了数据不会丢。其中我们眼睛看见的黑色的存储颗粒叫一个 NAND Package,每个 Package 里面会包含多个 Die。我们“拆开”一个 Die 来看一下:

[Image 62 on Page 9]

每个 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,如下图:

[Image 63 on Page 9]

不过 SSD 最小的读写单位就是 Page,他是没办法只扇区来进行读写的。

最底层的闪存单元

上面我们看到 SSD 是由一个个的 Page 组成的。而在每一个 Page 里,又包含了许许多多的闪存单元。现代的闪存单元有多种类型,目前主流的主要分为 SLCMLCTLC

在 SLC 里,一个单元的电压只分成高低两种状态,所以只能表示 1bit 数据。到了 MLC,硬是把一个单元里的电压按照高低分成了四种状态,所以可以表示 2bit。到了 TLC,直接一个单元应拆分成 8 个电压高低不同的状态,为了表示 3bit。由于 TLC 在数据读写需要八种不同电压状态,而施加不同的电压状态、就需要更精确,也就需要更长的时间才能得以实现。另外由于电压状态多,出错的可能性也会更大。所以,以上三种闪存单元对比:从性能和稳定性角度来看,SLC 最好。从容量角度看,TLC 最大。这就是为什么日常我们看到的工业级的 SSD 要比笔记本 SSD 贵很多,其中一个很重要的原因就是工业级的盘往往采用的闪存单元是 SLC 或 MLC,而我们家庭用的笔记本一般都是 TLC,因为便宜嘛。我们用表格再对它们直观对比一下:

闪存类型单 cell 电压变化单 cell 表示 bit 数速度擦写次数价格
SLC(Single-Level Cell)两种1bit约 10 万次
MLC(Multi-Level Cell)四种2bit一般约 3K 次一般
TLC(Trinary-Level Cell)八种3bit约 1K 次便宜

目前个人 PC 上消费用的主流闪存类型大部分都是 TLC 的,因为价格便宜,容量大。

思考

假设某 SSD 的 Page 大小是 4KB,一个文件是 16KB。那么该文件是存在一个黑色的存储颗粒里,还是多个?

[Image 67 on Page 10]

我们先把 SSD 的逻辑结构用个直观一点的图来看:

假设只写在一个颗粒里,那么对该文件进行读取的时候,就只能用到一条 Flash 通道,这样速度就会比较慢。如果存在相邻的 4 个颗粒里,每个写入 4KB。这样多个 Flash 通道的带宽会充分发挥出来,传输速度也更快。所以,实际中是分散在多个。


1.3 RAID 硬盘阵列

我们使用的计算机的全称叫电子计算机,前面有电子两个字,这说的是整个计算机中的核心元器件基本上都是电子单元组成的。但机械硬盘却是一个特殊的例外,它更多是用机械技术做出来的一个产品。当把带有机械技术基因的磁盘搭到计算机,尤其是再应用到服务器领域的时候,暴露出了机械技术的两个严重问题:

  1. 速度慢。如果把内存和 CPU 的速度比作汽车和飞机的话,这个大哥毫秒级别的延迟几乎就是牛车级别的。
  2. 容易坏。经常听说谁谁的磁盘坏了,很少有听说过谁的内存条、CPU 坏了。笔者就有在读研期间实验室里正在拷贝资料,突然一个断电直接废了一块硬盘的经历。

要想保证服务器运转的稳定和高速,就必须解决硬盘从娘胎里带出来的这两个缺陷。

多硬盘连接

问题简单明了,我们要解决速度慢、容易坏两个问题。很自然,单兵作战不行,那我们就想到往上怼一个班,多块硬盘一起上。但问题是,假如给你 N 块硬盘,让你来出一个技术方案,你会怎么设计呢?

第一个方案就是,把一个文件分成 N 片,每一片都散列在不同的硬盘上。这样当文件进行读取的时候,就可以 N 块硬盘一起来工作,从而达到读取速度提高到 N 倍的效果,这就是 RAID 0

不过这个方案没有解决容易坏的问题,任何一块硬盘坏了都会导致存储系统故障。

第二个方案就是,仍然把文件分片,但是所有的分片都存在一块硬盘上,其它的硬盘只存拷贝。这既提高了硬盘的访问速度,也解决了坏的问题。任意一块硬盘坏了,存储系统都可以正常使用,只不过速度会打一点折扣。

不过这个方案又带来了新的问题,那就是实现成本有点高了。假如我们用 256G 硬盘想实现 512G 的存储容量的话,最少得需要 4 块硬盘才能实现。

有没有折中一点的方案呢?有,而且很多。我们这里只说下最常见的 RAID 5

RAID 5 同样要对文件进行分片,但是不对存储的数据进行备份,而是会再单独存一个校验数据片。

[Image 71 on Page 11]

假如文件分为 A1 A2 A3,然后需要再存一个校验片到别的磁盘上。这样不管 A1,A2 还是 A3 那一片丢失了,都可以根据另外两片和校验片合成出来。既保证了数据的安全性,又只用了一块磁盘做冗余存储。

[Image 72 on Page 11]

假如我们有 8 块 256GB 的硬盘,那么 RAID5 方案下的磁盘阵列从用户角度来看可用的存储空间是 7 × 256GB,只“浪费”了一块盘的空间,所以目前 RAID5 应用比较广泛。

RAID 卡缓存

硬盘延迟是毫秒级别的,即使是多块硬盘并行,也只能提升数倍而已,不能够达到量级的提升。和 CPU 内存的纳秒级别工作频率比起来,还是太慢了。

在计算机界,没有缓存解决不了的速度问题,如果有,那就再加一层。现代磁盘本身也基本都带了缓存,另外在一些比较新的 RAID 卡里,硬件开发者们又搞出来了一层“内存”,并且还自己附带一块电池,这就是 RAID 卡缓存。我们看下几款主流 RAID 卡的配置:

RAID 卡型号描述缓存支持的 RAID 级别
PERC S120入门软件阵列卡,主板集成无缓存RAID 0, 1
PERC H330入门硬件 RAID 卡无板载缓存RAID 0, 1, 5, 10, 50
PERC H730主流硬件 RAID 卡1G 缓存 + 电池RAID 0, 1, 5, 6, 10, 50, 60
PERC H730P高性能硬件 RAID 卡2G 缓存 + 电池RAID 0, 1, 5, 6, 10, 50, 60
PERC H830同 H730P,没有内置接口,使用外置接口连接附加存储磁盘柜用—拿目前服务器端出镜率比较高的 H730 和 H730P 来看,他们分别带了 1G 和 2G 的缓存卡,并且自带电池。 电池的作用就是当发现主机意外断电的时候,能够快速把缓存中的数据写回到磁盘中去。对于写入,一般操作系统写到这个 RAID 卡里就完事了,所以速度快。对于读取也是,只要缓存里有,就不会透传到磁盘的机械轴上。

另外想再补充说一点的就是文件相关函数里设置 DIRECT I/O 仅仅只能绕开操作系统本身的 Page Cache,而 RAID 卡里的缓存,对于 Linux 来说,可以说算是一个黑盒。换句话说,就是操作系统并不清楚 RAID 卡是从缓存里吐的数据,还是真正从硬盘里读的。

动手查看你的 RAID 配置

了解了 RAID 基本原理以后,我们可以实际动手查看一下机器上的 RAID 情况。这里拿我手头的一台服务器举例。

通过 cat /proc/scsi/scsi ,我们可以查看到 RAID 卡的型号。

[Image 78 on Page 12]

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 缓存和电池。

再看我们的硬盘阵列。

[Image 86 on Page 14]

# /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 组的 RAID 级别。对照下面:

  • RAID 1: Primary-1, Secondary-0, RAID Level Qualifier-0 → RAID 1
  • RAID 0: Primary-0, Secondary-0, RAID Level Qualifier-0 → RAID 0
  • RAID 5: Primary-5, Secondary-0, RAID Level Qualifier-3 → RAID 5
  • RAID 10: Primary-1, Secondary-3, RAID Level Qualifier-0 → RAID 10

可以看到,本机 RAID Level 是 RAID 5。

另外 Strip Size 叫做条带大小,我机器上的配置是 128 KB。假如有一个 512 KB 的文件,它就会被分成 4 个条带,每个 128 KB。这些条带可能会分散在不同的磁盘上,如果一次性读取的话,多个硬盘就可以一起转动机械轴,读取速度就会提高到原来的数倍。不过要说明的是,如果文件小于这个条带大小,小于 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]
Coerced Size: 278.875 GB [0x22dc0000 Sectors]

看到属于 DiskGroup 1 的总共有 7 块硬盘,每块的大小都是 278 GB 左右。6 块盘的总容量大小就是 1.6 T 左右,确实是只“浪费”了一块硬盘的容量来保障数据的安全性!

另外 Inquiry Data 也显示了硬盘的制造商以及型号。这些磁盘都是希捷的 300 G 的机械硬盘,经 Google 查询其转速为 1 万转每分钟。

小结

机械技术和电子技术比较起来,稳定性要差很多。所以机械背景出身的硬盘既慢又容易坏。为了解决这个问题,RAID 技术应运而生。通过一定的冗余原始存储或者校验数据提供安全性的保障,通过增加带电子基因的缓存,合理调度多块磁盘的机械轴,提高了磁盘 IO 的读写速度。在 Linux 下可以通过 MegaCli 工具来查看你的 RAID 组情况,强烈建议你实际动手查看一下!


GitHubhttps://github.com/yanfeizhang/coder-kung-fu

欢迎关注个人公众号“开发内功修炼”,了解你的每一比特,用好你的每一纳秒。

有想继续加入知识星球的同学微信扫描下面的二维码即可加入。另外在公众号后台发送「星球优惠券」可以获取开发内功修炼读者的专属优惠券。