探索NetBSD磁盘标签
摘要
这篇文章通过十六进制编辑和C编程示例,详细介绍了NetBSD 11上BSD磁盘标签的格式、文档来源和结构。
<p><a href="https://lobste.rs/s/qknavp/netbsd_playing_with_disklabels">评论</a></p>
查看缓存全文
缓存时间: 2026/09/26 03:20
# NetBSD: 深入磁盘标签
来源:https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html
博客 (https://movq.de/blog/)\-git (https://movq.de/git/)\-桌面 (https://movq.de/desktop/)\-联系 (https://movq.de/contact.html)
---
2026\-09\-25
由于我来自 DOS 和 Linux 环境(并且在我的 OpenBSD 系统上基本忽略了这点——是的,该为此感到羞愧),我对 BSD 磁盘标签不太熟悉。那么,让我们来探究一下。我不能完全确定所有 BSD 系统都使用完全相同的格式,而且据我所知,不同架构之间存在差异,所以让我们具体来说:我正在研究 x86\_64 架构上的 NetBSD 11。我将在虚拟机中运行所有内容,这样我可以轻松地更换磁盘并进行检查。为了避免与 MBR 系统冲突(https://en.wikipedia.org/wiki/BSD_disklabel#Where_disklabels_are_stored),磁盘标签存储在不同的位置。在我的测试虚拟机中,可以在第二个扇区找到它:
bine-initial.png:十六进制编辑器截图:
在偏移量 0x200 处,我们可以看到磁盘标签的魔数。(https://movq.de/blog/postings/2026-09-25/0/bine-initial.png)
我主要对这个磁盘标签的格式感兴趣:各个字节意味着什么?在哪里有规范/文档?
---
## 目录
- 在哪里可以找到文档?(https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#where-to-find-documentation)
- 第 0 部分:磁盘标签在哪里?(https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#part-0-where-is-the-disklabel)
- 第 1 部分:“磁盘元数据”(https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#part-1-disk-metadata)
- 第 2 部分:分区表(https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#part-2-the-partition-table)
- 验证校验和(https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#verifying-the-checksum)
- 练习:在十六进制编辑器中创建一个随机分区(https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#exercise-create-some-random-partition-in-the-hex-editor)
- 总结(https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#conclusion)
---
## 在哪里可以找到文档?
这个问题很容易回答。查看`man disklabel`(正如你会“本能”地那样做,因为它是操作这些标签的工具),在`SEE ALSO`部分有`disklabel(5)`:第 5 部分描述文件格式,所以这是我们想要查看的(`man 5 disklabel`)。不过,对于本文的大部分内容,我直接查看了`/usr/include/sys/disklabel.h`而不是手册页。
## 第 0 部分:磁盘标签在哪里?
首先是最重要的问题。手册页告诉你在哪里找到磁盘标签:`getlabelsector()`和`getlabeloffset()`就是答案。所以让我们写一个小程序来看看它们返回什么。
```netbsd
# cat test.c
#include <stdio.h>
#include <util.h>
int main() {
printf("getlabelsector() = %d\n", getlabelsector());
printf("getlabeloffset() = %ld\n", getlabeloffset());
return 0;
}
# cc -Wall -Wextra -o test test.c -lutil
```
输出:
```
netbsd# ./test
getlabelsector() = 1
getlabeloffset() = 0
```
所以扇区 1(在我的例子中是字节偏移量 512)是正确的。上面截图中的不是随机数据,那*就是*磁盘标签。磁盘标签以一堆关于磁盘的“元数据”开始,然后是实际的分区表。让我们先来看看这些元数据。这有点难以图解。先给出一个概览(在文本旁边的新标签页中打开它),然后我们将逐一查看:
disklabel-metadata.png:我的磁盘标签的十六进制转储。颜色表示各个字段。(https://movq.de/blog/postings/2026-09-25/0/disklabel-metadata.png)
所有这些在我的 Intel 机器上都是*小端序*。
通用磁盘/驱动器信息:
- `5745 5682`:`d_magic`,魔数
- `0f00`:`d_type`,驱动器类型 =`0x0f`= “逻辑磁盘”
- `0000`:`d_subtype`,特定于`d_type`
- `6c64...0000` / `"ld1"`:`d_typename`
- `4d79...0000` / `"My Cool Disk"`:`d_packname`,用户输入的名称
`disklabel.h`包含`d_type`可能值的表格。
几何/大小:
- `0002 0000`:`d_secsize`,每扇区 512 字节
- `3f00 0000`:`d_nsectors`,每磁道 63 个扇区
- `1000 0000`:`d_ntracks`,每柱面 16 个磁道
- `0401 0000`:`d_ncylinders`,每个单元 260 个柱面
- `f003 0000`:`d_secpercyl`,每柱面 1008 个扇区
- `0000 0400`:`d_secperunit`,每个单元 262144 个扇区(128 MiB)
- `0000`:`d_sparespertrack`,每磁道无备用扇区
- `0000`:`d_sparespercyl`,每柱面无备用扇区
- `0000 0000`:`d_acylinders`,“备用柱面”(?)
硬件参数和时序,在 x86\_64 上可能非常无关紧要:
- `100e`:`d_rpm`,3600
- `0100`:`d_interleave`
- `0000`:`d_trackskew`
- `0000`:`d_cylskew`
- `0000 0000`:`d_headswitch`,磁头切换时间(微秒)
- `0000 0000`:`d_trkseek`,磁道到磁道寻道时间(微秒)
- `0000 0000`:`d_flags`,“通用标志” :-)
- 5x`0000 0000`:`d_drivedata`,驱动器类型特定
- 5x`0000 0000`:`d_space`,保留
第一部分的结束:
- `5745 5682`:`d_magic2`,魔数
- `c61d`:`d_checksum`
- `0400`:`d_npartitions`,下面数组中有多少个分区
- `0020 0000`:`d_bbsize`,启动区域在 sn0 的大小(字节)
- `0020 0000`:`d_sbsize`,文件系统超级块的最大大小(字节)
这些是我通过手动读取十六进制转储并与头文件中的`struct disklabel`比较后确定的值。就我所知,它与`disklabel`工具的输出匹配:
```
netbsd# disklabel ld1
# /dev/rld1:
type: ld
disk: ld1
label: My Cool Disk
flags:
bytes/sector: 512
sectors/track: 63
tracks/cylinder: 16
sectors/cylinder: 1008
cylinders: 260
total sectors: 262144
rpm: 3600
interleave: 1
trackskew: 0
cylinderskew: 0
headswitch: 0 # microseconds
track-to-track seek: 0 # microseconds
drivedata: 0
```
很多信息感觉相当过时,至少在“现代” x86\_64 上是这样。(但 MBR 在这方面也没好*多少*,仍然有所有的 CHS 东西。)而且其中很多确实未被使用,只是设置为 0。
## 第 2 部分:分区表
`d_npartitions`说有四个分区。这里是该表格的概览,它紧跟在“元数据”部分之后:
disklabel-partitions-overview.png:与上面相同的十六进制转储,但分区表所在区域被高亮显示。(https://movq.de/blog/postings/2026-09-25/0/disklabel-partitions-overview.png)
这对应于分区`a`、`b`、`c`和`d`。让我们仔细看看分区`a`:
disklabel-partition-a.png:与上面相同的十六进制转储,分区'a'的各个字段被高亮显示。(https://movq.de/blog/postings/2026-09-25/0/disklabel-partition-a.png)
各个字段:
- `0000 0400`:`p_size`,262144 个扇区
- `0000 0000`:`p_offset`,0 个扇区
- `0000 0000`:`p_fsize`,“文件系统基本片段大小”(?)
- `07`:`p_fstype`,文件系统类型 =`0x07`=`4.2BSD / ffs`
- `00`:`p_frag`,“文件系统每个块的片段数”(?)
- `0000`:`p_cpg`或`p_sgs`:UFS 或 LFS 特定
`disklabel.h`包含`p_fstype`可能值的表格。我认为只有三个重要的信息:
- 起始,
- 结束,
- 分区类型。
再次,我的解释与`disklabel`的输出匹配:
```
netbsd# disklabel ld1
...
4 partitions:
# size offset fstype [fsize bsize cpg/sgs]
a: 262144 0 4.2BSD 0 0 0 # (Cyl. 0 - 260*)
d: 262144 0 unused 0 0 # (Cyl. 0 - 260*)
```
根据 Martin Husemann 的这封邮件(https://mail-index.netbsd.org/netbsd-users/2018/09/22/msg021452.html),分区`d`在 x86\_64 上始终存在,并描述整个磁盘。分区`c`将是 NetBSD 可用的区域。为什么这里没有输出`c`,我不确定。(实际上,不,我想我知道原因:大小、偏移量和类型都是零,所以它被隐藏了,我猜。但为什么当我第一次创建磁盘标签时`disklabel -I`没有包含`c`?)
## 验证校验和
头文件说:
```
uint16_t d_checksum; /* xor of data incl. partitions */
```
所以...我想我们将所有这些数据分成 16 字节的块,然后将它们全部异或?让我们试试:
```
$ dd if=zwei.raw bs=512 count=1 skip=1 status=none | od -An -vt x2 -w2 | gawk '{ v = strtonum("0x" $1); cksum = and(xor(cksum, v), 0xFFFF) } END { printf("%04x\n", cksum) }'
0000
```
全零。当你想想:这证实了一切都是正确的。:-) 实际的校验和是那些 16 字节块之一,所以零是正确的答案。从数据中排除校验和:
```
$ dd if=zwei.raw bs=512 count=1 skip=1 status=none | od -An -vt x2 -w2 | sed 69d | gawk '{ v = strtonum("0x" $1); cksum = and(xor(cksum, v), 0xFFFF) } END { printf("%04x\n", cksum) }'
1dc6
```
这就对上了,它与我们在转储中看到的小端序`c61d`匹配。(显然,因为...那是我排除的那一行...是的...但你明白了。)
我还不熟悉 NetBSD 的代码库,但我认为这可能是他们计算校验和的代码(至少它做了和我一样的事情):
https://cvsweb.netbsd.org/bsdweb.cgi/src/sys/lib/libkern/dkcksum.c?rev=1.1.6.2;content-type=text%2Fplain
## 练习:在十六进制编辑器中创建一个随机分区
让我们反其道而行:根据我们现在所知的,打开十六进制编辑器并定义分区`i`,大小 1234 个扇区,从扇区 5678 开始,文件系统类型 ZFS。
分区`i`是第九个分区,所以它的条目应该从磁盘的字节 788 开始:
```
512 + 0x94 + (9 - 1) * 16 = 788
\_/ \____/ \_______/
| | | \- 每个标签大小为 16 字节,参见
| | | disklabel.h。
| | | /- 我们想知道第九个标签的*起始位置*。
| | |-----/
| |-----/(第一个分区条目的起始位置,参见上面的十六进制转储)
|-------/(磁盘上磁盘标签的起始位置)
```
我们将在分区表中写入这些字段(这已经是*小端序*了):
- `p_size`=`d204 0000` 表示 1234 个扇区
- `p_offset`=`2e16 0000` 表示偏移量 5678
- `p_fstype`=`21`,根据`disklabel.h`中的表格
我们还需要将`d_npartitions`更新为`9`。所有中间的分区保持未使用状态。我将把`d_checksum`覆盖为四个零,这样我们就可以直接运行上面的`gawk`片段来计算新的校验和。看哪,它是:`160f`(小端序)。这些是我修改过的字节:
bine-edit.png:我的十六进制编辑器截图,粉色高亮显示我更改的字节。(https://movq.de/blog/postings/2026-09-25/0/bine-edit.png)
让我们再次启动虚拟机,看看我们得到了什么:
```
netbsd# disklabel ld1
# /dev/rld1:
type: ld
disk: ld1
label: My Cool Disk
flags:
bytes/sector: 512
sectors/track: 63
tracks/cylinder: 16
sectors/cylinder: 1008
cylinders: 260
total sectors: 262144
rpm: 3600
interleave: 1
trackskew: 0
cylinderskew: 0
headswitch: 0 # microseconds
track-to-track seek: 0 # microseconds
drivedata: 0
9 partitions:
# size offset fstype [fsize bsize cpg/sgs]
a: 262144 0 4.2BSD 0 0 0 # (Cyl. 0 - 260*)
d: 262144 0 unused 0 0 # (Cyl. 0 - 260*)
i: 1234 5678 ZFS # (Cyl. 5*- 6*)
disklabel: partitions a and i overlap
```
看起来不错!(分区显然重叠了,因为`a`已经占用了整个磁盘。)
## 总结
这是一个有趣的下午,我确实感觉对磁盘标签更熟悉了。和往常一样,我真的需要这样一个*动手实践*的环节才能熟悉某样东西。如你所见,我用`(?)`标记了一些字段,因为它们的含义对我来说还不完全清楚。也许改天再说(或者也许不)。
相似文章
我尝试将 NetBSD 作为桌面系统使用,感觉像是以一种美好的方式步入了 90 年代
文章讲述了作者安装和使用 NetBSD 作为桌面操作系统的体验,强调了它的复古感以及作为 BSD Unix 后裔的历史意义。
NetBSD 11.0 发布
NetBSD 11.0 历经长期延迟后终于发布,现已提供安装镜像和发布说明。该版本承认存在未解决的安全问题,并计划在两个月内发布后续的 11.1 版本。
基于 NetBSD 的研究
本 NetBSD 画廊页面重点展示了各类基于该操作系统的学术及工业研究项目,包括 NASA 的卫星 TCP 实验、KAME IPv6/IPsec 项目以及高速网络演示。
NetBSD GSoC 2026 改进 RAIDframe
这篇文章报道了Google Summer of Code 2026年在NetBSD中改进RAIDframe的工作,包括实现用于多磁盘镜像的N-way RAID 1和添加RAID scrubbing支持。
FreeBSD 吃掉我的内存
一篇解释为什么 FreeBSD 看起来占用大量内存的文章,将其归因于磁盘缓存和虚拟内存管理,类似于 Linux 的“吃掉我的内存”现象。