块设备的最小单位是一块(512 字节或 4 KB),不是一字节。实测从块首与从块中随机读 2000 次,差距在噪声里——因为这个文件全在页缓存里。对齐真正要紧的是【绕过缓存】与【文件系统分配块】这两处。
7.10 的字符设备【不能跳】,这一节的块设备【能跳】。能跳这一条,就是【能装文件系统】的全部前提。
一、块设备的性格:能随机跳
块设备是【按块寻址】的:你可以说【我要第 1000 块】,它就去取第 1000 块。字符设备做不到这一点——它只有【下一个字节】。
所以两者的能力差别是:块设备能 seek,字符设备不能(对字符设备 lseek 返回 ESPIPE)。
为什么这件事这么关键:文件系统的全部本事,就是【把分散的块组织成一个连续的文件】。如果底层不能按块随机访问,这个本事就没法施展。
所以你能挂载的一定是块设备,而 /dev/null、/dev/tty 这类永远挂不上。(/proc、/sys 是另一回事——它们不走块设备,是内核直接实现的一套文件系统,见 7.15。)
块大小有两层含义,容易混:① 设备扇区,通常 512 字节或 4 KB;② 文件系统块,通常 4 KB。两者可以不同,但文件系统块一般是扇区的整数倍。
二、对齐为什么重要
如果一次读跨越了两个块,设备就得读两次——哪怕你只要中间那 100 个字节。所以【偏移按块对齐、长度是块的整数倍】能省掉一次访问。 但实测里这个差距很小:从块首与从块中随机读 2000 次,18.31 毫秒对 23.08 毫秒,而且换一次跑法先后就颠倒。说明这 26% 主要是噪声。 为什么测不出来:因为这个文件刚写完,整份都在页缓存里。页缓存是按 4 KB 页管的——你从块中读,取的还是同一页里的字节,既不额外碰磁盘,也不额外读一块。 所以【对齐】这件事,在【全命中缓存】时根本不存在。沙箱里只能证明这一点,真设备上的那一侧测不到。这跟第 6 章测不到【冷缓存】是同一类问题。
三、对齐真正要紧的三种场合
① 绕过缓存的读(O_DIRECT)。它要求【偏移、长度、缓冲区地址】三者都按块对齐。有一个不对,内核要么退回去用缓存,要么直接返回 EINVAL。数据库自己管缓存时就会用这个。
② 文件系统的块分配。文件的第一个块从哪开始、后续块是否连续,直接决定读的时候要发几次请求。filefrag 能看出来一个文件碎成了几段。
③ 数据库的页大小。InnoDB 的页默认 16 KB,是文件系统块(4 KB)的整数倍,这样一次页读正好对齐四个块。如果页大小不是块大小的整数倍,每次读写都会跨块。
所以【对齐】不是玄学:它的成本在【多一次设备访问】,而这一次访问在有缓存时免费、在没缓存时很贵。
四、挂载:把块设备变成文件树的动作
mount /dev/sda1 /mnt 做的是:读出这块设备开头的超级块,认出它是什么文件系统,然后把它的根接到 /mnt 这个空目录上。
从此 /mnt/foo 就等价于【这块设备上的 foo】。整台机器仍然只有一棵树。
所以【块设备】与【文件系统】是两个层次:同一块设备可以格式化成一堆不同的文件系统;一个文件系统也可以被挂到树上的任意空目录。
这也解释了几件常见的事:① 为什么 df 是按挂载点列的——它列的是文件系统,不是设备。② 为什么 umount 会提示【设备忙】——还有进程在里面有打开的文件,或者当前目录在里面。③ 为什么扩容要先扩分区再扩文件系统——两层各管一段。
五、动手 Demo:块设备——最小单位是一块,不是一字节
这一节真测:同样 2000 次 4 KB 随机读,一次从块首开始,一次每次都错开 100 字节。 结果会说明一件重要的事:在缓存里,对齐测不出来。
import os
import random
import tempfile
import time
MB = 1024 * 1024
BS = 4096
d = tempfile.mkdtemp(prefix="ch29c7_")
p = os.path.join(d, "align.bin")
open(p, "wb").write(b"\0" * (16 * MB))
f = open(p, "rb", buffering=0)
random.seed(7)
offs = [random.randrange(0, 16 * MB - BS - 512) for _ in range(2000)]
def at_start():
for o in offs:
f.seek(o)
f.read(BS)
def at_mid():
for o in offs:
f.seek(o + 100)
f.read(BS)
for name, fn in (("块首", at_start), ("块中", at_mid)):
t0 = time.perf_counter()
fn()
print(name, "%.2f 毫秒" % ((time.perf_counter() - t0) * 1000))
实跑输出:
--------------------------------------------------------------------
块设备——最小单位是一块,不是一字节
--------------------------------------------------------------------
① 真测:从块首读 vs 从块中读
起点 2000 次 4 KB 随机读 说明
----------------------------------------------------------
块首(对齐) 19.25 毫秒 基准
块中(错开 100 字节) 19.39 毫秒 慢 1%
**差 1%,但换一次跑法这个先后就会颠倒**——
**说明这个差距主要在噪声里,不是稳定的差异。**
② 为什么这里拉不开差距
**因为这个文件刚写完,整份都在【页缓存】里。**
**页缓存是按 4 KB 页管的。**
**你从块中读,取的还是同一页里的字节**——
**既不额外碰磁盘,也不额外读一块。**
**所以【对齐】这件事,在【全命中缓存】时根本不存在。**
**沙箱里只能证明这一点,真设备上的那一侧测不到。**
③ 对齐真正要紧的两处
场合 为什么在乎对齐 怎么观察
-----------------------------------------------------------------
绕过缓存的读 必须整块读,错开就重试 dd iflag=direct
文件系统分配块 块边界决定读写次数 filefrag 看块号
数据库页大小 页要比块大且对齐 innodb_page_size
**第一行最典型**:`O_DIRECT` 要求【偏移、长度、缓冲区地址】三者都按块对齐。
**只要有一个不对,内核要么退回去用缓存,要么直接返回 EINVAL。**
📋 **Linux 上验证**:`dd if=/dev/sda of=/dev/null bs=4k count=1000 iflag=direct`;
**把 bs 改成 4000 再跑一次**,会看到速度掉下来。
④ 块设备的接口与字符设备的差别
特性 字符设备 块设备
---------------------------------------------------------
最小单位 1 字节 1 块(512 B 或 4 KB)
能 seek 吗 一般不能 能,按块跳
能当文件系统吗 不能 能,这就是挂载
有没有缓存 没有 有,就是第 6 章那个
典型例子 /dev/null /dev/sda
**块设备的关键在于【能随机跳】。**
**不能跳的东西没法建文件系统**——
**因为文件系统的全部本事,就是【把分散的块组织成一个连续的文件】。**
📋 **Linux 上验证**:`lsblk` 看块设备树;
`blockdev --getbsz /dev/sda1` 看块大小;
`dd if=/dev/sda1 of=/dev/null bs=4k count=1000` 测裸读。
两组差距在噪声里,换次跑法先后就颠倒。因为整份文件都在页缓存里,页是按 4 KB 管的,从块中读取的还是同一页。 所以这个 Demo 的结论是【反向】的:它证明了【缓存会抹掉对齐的差别】。要看到对齐的代价,必须绕过缓存或换真设备——沙箱里做不到,这里不假装做到了。 这也是一句更普遍的话:很多【底层优化】在有缓存的时候测不出来。所以【在应用层看不到效果】不等于【它没起作用】。
📋 Linux 上验证:dd if=/dev/sda of=/dev/null bs=4k count=1000 iflag=direct;把 bs 改成 4000 再跑一次,速度会掉下来。
📋 lsblk 看块设备树;blockdev --getbsz /dev/sda1 看块大小。
30 秒实验
blockdev --getbsz /dev/sda1,看文件系统块多大。lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,看设备与挂载点的对应。filefrag -v 一个大文件,看它碎成了几段、每段从哪块开始。
常见误区
- 【块设备就是磁盘】。块设备是【磁盘的一个可寻址视图】;分区、LVM 卷、RAID 阵列、iSCSI 目标,都是块设备。
- 【一次读 4 KB 一定只碰一个块】。从块中间开始读就会跨两块;有缓存时免费,没缓存时多一次访问。
- 【挂载会把数据拷到系统盘】。挂载只是【把它的根接到树上】,一个字节都不搬。
自测三问(点开对答案)
1. 为什么字符设备不能装文件系统? —— **因为它不能按块随机访问**;**文件系统的全部本事是【组织分散的块】**,**不能跳就没法组织。** 2. 为什么这个 Demo 测不出对齐的代价? —— **因为文件全在页缓存里**,**页按 4 KB 管,从块中读取的还是同一页**——**根本没到设备那一层。** 3. 设备扇区与文件系统块有什么区别? —— **前者是硬件的访问单位(512 B 或 4 KB)**,**后者是文件系统的分配单位(通常 4 KB)**;**后者一般是前者的整数倍。**- ☐ 7.12 各种各样的文件系统
相关笔记
- 字符设备 — 不能跳的那一类
- 高速缓存 — 缓存为什么会抹掉底层差别
- 虚拟文件系统 — 不靠块设备的那种文件系统
- 🐧 Linux是怎样工作的 · 索引