《Windows Internals》10.1.25 Reliability:为什么注册表不是“写进去就完了”,而是从 base block 序列号、增量日志到恢复流程都在围绕“崩溃后还能回来”做设计?
- 《Windows Internals》10.1.25 Reliability:为什么注册表不是“写进去就完了”,而是从 base block 序列号、增量日志到恢复流程都在围绕“崩溃后还能回来”做设计?》
- 1. 先说结论:注册表可靠性的核心,不是“避免出错”,而是“出错后还能恢复”
- 2. 第一层基础:什么叫 stable storage?为什么每个 nonvolatile hive 都有自己的 log hive?
- 2.1 为什么不是只要一个日志文件?
- 2.2 这些日志文件长什么样?
- 3. 第二层基础:dirty sector array 到底是什么?为什么它是“谁被改过”的总账本?
- 3.1 为什么不是直接记“改了哪个 key/value”?
- 3.2 它什么时候更新?
- 4. 第三层基础:lazy writer 为什么不是“立刻重写主 hive”,而是“先写日志”?
- 4.1 为什么不直接写主 hive?
- 4.2 这背后的设计思想是什么?
- 5. base block 的两个 sequence number 为什么是可靠性的“红绿灯”?
- 5.1 这两个序列号到底起什么作用?
- 6. 还有一个常被忽略的结构:unreconciled array 到底是干什么的?
- 6.1 dirty 和 unreconciled 有什么区别?
- dirty sector array
- unreconciled array
- 7. Reconciler 为什么默认一小时才跑?这背后是“性能”和“可恢复性”的平衡
- 7.1 为什么不是每次改完就同步主 hive?
- 7.2 那这样会不会丢数据?
- 8. 增量日志(incremental logging)到底改进了什么?为什么 Windows 8.1 是个分水岭?
- 8.1 旧算法(Windows 8.1 之前)怎么干?
- 8.2 新算法(incremental logging)怎么优化?
- 9. Cell 的四种状态为什么特别值得记?它其实是理解恢复流程的钥匙
- 9.1 这四种状态为什么重要?
- 9.2 这对理解 crash case 有什么帮助?
- 10. 为什么说大多数时候 main hive 其实都处于“dirty state”?这点特别反直觉
- 10.1 这为什么反直觉?
- 11. 启动后的恢复与自修复为什么这么重要?因为真正的最坏场景永远是“系统已经崩过一次”
- 11.1 这说明什么?
- 12. 从桌面支持和排障视角,这一节到底有什么现实价值?
- 12.1 它能帮我理解为什么 System.log1 / System.log2 这些文件不是“无用残留”
- 12.2 它能帮我理解为什么“注册表写入完成”不等于“主 hive 已经同步完成”
- 12.3 它能帮我理解为什么某些注册表修改在崩溃后“还能找回来”
- 12.4 它能帮我理解为什么系统有时会自修复后继续启动,但配置看起来少了一块
- 12.5 它能帮我建立更成熟的“注册表不是文本文件”的认识
- 13. 最容易误解的 7 个点,我帮你一次理顺
- 13.1 误区一:改注册表就是直接重写主 hive
- 13.2 误区二:一个 hive 只要一个日志文件就够了
- 13.3 误区三:两个 sequence number 只是版本计数
- 13.4 误区四:Reconciler 一跑完,主 hive 就一定是完全干净状态
- 13.5 误区五:incremental logging 只是“多写几次日志”
- 13.6 误区六:系统崩溃后,只要有日志就一定完全无损恢复
- 13.7 误区七:一旦 hive 损坏,Windows 只能放弃启动
- 14. 我的学习理解:这一节真正教会我的,不是“怎么写注册表”,而是“怎么让系统在最差场景下还活着”
- 15. 总结提升
- 下一篇预告
《Windows Internals》10.1.25 Reliability:为什么注册表不是“写进去就完了”,而是从 base block 序列号、增量日志到恢复流程都在围绕“崩溃后还能回来”做设计?》
很多人第一次接触注册表时,脑子里默认的写入模型其实很朴素:
- 改一个值
- 系统写到文件
- 完事
但《Windows Internals》10.1.25 Reliability这一节,几乎是把这种“朴素想象”彻底打碎了。
因为 Windows 对注册表的真实要求,从来不是:
“能写进去”就行。
而是:
“就算系统在写到一半时崩了、断电了、刷盘没刷完、主 hive 还没同步,下一次启动时也尽量能把这份 hive 拉回到可恢复、可继续使用的状态。”
书里讲得非常明确:
为了让nonvolatile hive始终保持recoverable state,Configuration Manager 会为每个非易失 hive 维护对应的log hive,并采用双日志(.log1 / .log2)、dirty sector array、lazy writer、base block 双序列号、incremental logging、Reconciler、以及启动时的恢复与自修复机制,共同保证可靠性。
所以这篇文章,我就围绕10.1.25 Reliability,把 Windows 注册表为什么不是“写进去就完了”,而是一整套围绕“崩溃后还能回来”做设计的系统,彻底讲透。
1. 先说结论:注册表可靠性的核心,不是“避免出错”,而是“出错后还能恢复”
如果只用一句话总结这一节,我会这样说:
Windows 注册表可靠性设计的目标,不是保证系统永远不会在写入过程中崩溃,而是保证即便崩溃发生在最糟糕的时机,hive 也尽量还能被恢复到一致状态。
这一点特别重要,因为它决定了 Windows 在注册表写入时的策略不是:
- 直接覆盖主 hive 文件
- 祈祷别出问题
而是:
- 先记日志
- 再逐步推进
- 用序列号和恢复逻辑判断哪一步完成了
- 下次加载时尽量把 hiveroll forward到一致状态。
也就是说,注册表可靠性不是“零风险写入”,而是“带恢复语义的写入”。
2. 第一层基础:什么叫 stable storage?为什么每个 nonvolatile hive 都有自己的 log hive?
书里在这一节一开头先讲了一个特别关键的概念:
Stable storage
它的意思非常直接:
为了让一个nonvolatile registry hive(也就是带磁盘文件的 hive)始终处于可恢复状态,Configuration Manager 会为它维护配套的log hives。
书里明确说:
- 每个nonvolatile hive
- 都有一个关联的log hive
- 这个日志文件是隐藏文件
- 文件名和主 hive 同基名,但扩展名是logN
- 为了确保forward progress
- 系统采用dual-logging scheme,也就是.log1 / .log2两套日志。
2.1 为什么不是只要一个日志文件?
因为一个日志文件本身也可能在写入过程中失败。
书里明确解释了双日志的意义:
- 如果
.log1已经写了,但后续把脏数据写入主 hive 过程中失败了 - 下一次 flush 时就会切换到
.log2 - 如果
.log2也失败,就继续用累积脏数据写.log2 - 成功后再切回
.log1。
这说明双日志不是“冗余得好看”,而是:
为了保证日志写入本身也具备继续前进(forward progress)的能力。
2.2 这些日志文件长什么样?
书里还给了非常具体的例子:
System.log1Sam.log1- 以及其他
.log1 / .log2文件
通常就在%SystemRoot%\System32\Config目录里,只是默认隐藏。
这也解释了为什么有时你在系统目录里会看到这些“看起来像注册表又不像主 hive”的文件。
它们不是垃圾文件,而是注册表可靠性体系的一部分。
3. 第二层基础:dirty sector array 到底是什么?为什么它是“谁被改过”的总账本?
书里接着引入了另一个特别关键的结构:
dirty sector array
它的逻辑非常清楚:
- hive 初始化时
- Configuration Manager 会分配一个bit array
- 每一位代表 hive 中一个512-byte sector
- 某位被置位,表示对应 sector 的数据在内存里已经被修改,后续必须回写。
这东西我更喜欢把它理解成:
“主 hive 哪些扇区已经脏了”的位图总账。
3.1 为什么不是直接记“改了哪个 key/value”?
因为最终写盘时,系统关心的不只是逻辑对象,还要知道:
- 对应主 hive 文件里的哪些扇区发生了变化
- 后续刷日志和回写主文件时该覆盖哪些区域。
所以 dirty sector array 记录的,不是“逻辑树节点”,而是:
磁盘布局视角下,哪些 sector 已经不再和内存内容一致。
3.2 它什么时候更新?
书里说得很明确:
- 创建新 key / value
- 修改现有 key / value
- 这些操作发生时
- Configuration Manager 会把对应主 hive 里变化的 sector 标记到 dirty sector array。
也就是说,只要内存里的 hive 内容改了,脏位图就会同步记账。
4. 第三层基础:lazy writer 为什么不是“立刻重写主 hive”,而是“先写日志”?
这是整节最值得理解的一个转折。
书里明确说:
- 当 hive 有修改后
- Configuration Manager 会安排一次lazy flush operation / log sync
- lazy writer 线程会在请求发出1 分钟后唤醒
- 它会根据 dirty sector array 生成新的日志条目
- 并把这些 dirty sectors写到 log file
- 不是立刻写到 primary hive file。
4.1 为什么不直接写主 hive?
书里直接给出答案:
如果 lazy writer 直接把所有 dirty sectors 写主 hive,而系统恰好在中途崩了,那主 hive 就可能处于不一致、损坏且不可恢复的状态。
这就是为什么 Windows 的第一选择不是“立刻改主文件”,而是:
先把脏数据和脏位图写进 log。
这样就算后面系统崩了,下次加载时仍然有机会把 hive 滚到正确状态。
4.2 这背后的设计思想是什么?
我觉得可以概括成一句话:
主 hive 不是第一落点,日志才是第一安全落点。
这和很多数据库、文件系统的恢复设计非常像:
- 先保证恢复信息可靠落地
- 再考虑主数据什么时候同步
5. base block 的两个 sequence number 为什么是可靠性的“红绿灯”?
这一节里最值得硬记的一个机制,就是:
base block contains two sequence numbers。
书里讲得很清楚:
- 在第一次 flush 后(注意不是后续每次)
- Configuration Manager 会更新其中一个 sequence number
- 于是两个序列号会出现不一致
- 如果系统在写主 hive 过程中崩溃
- 下次启动时,Configuration Manager 发现这两个 sequence number 不匹配
- 就知道上次有一轮写入没完整走完
- 此时可以利用日志文件里的 dirty sectors,把 hiveroll forward
- 最终恢复到一致状态。
5.1 这两个序列号到底起什么作用?
你可以把它理解成:
- 相等:说明主 hive 处于已验证一致状态
- 不相等:说明上次写入流程可能中途断了,加载时必须结合日志做恢复判断。
所以这两个 sequence number,就像一个特别简洁但非常有效的状态信号灯。
它们不是为了“记录版本号好看”,而是为了让系统在重启时快速判断:这份 hive 是不是需要带日志一起恢复。
6. 还有一个常被忽略的结构:unreconciled array 到底是干什么的?
这部分特别值得单独拿出来讲。
书里说:
- 当日志条目已经写入 hive log 之后
- lazy flusher 会清掉 dirty sector array 对应的有效位
- 但同时会把这些位放进另一个重要向量:
unreconciled array - 它的作用是帮助 Configuration Manager 知道:
哪些 log entries 还没有真正写回 primary hive file。
6.1 dirty 和 unreconciled 有什么区别?
我建议直接这样记:
dirty sector array
表示:
- 内存里的 hive 改了
- 但这些改动还没写进 log。
unreconciled array
表示:
- 日志已经有了
- 但主 hive 还没同步。
也就是说,它们分别对应两个阶段:
所以 unreconciled array 的价值,就是告诉系统:“这些改动已经安全进日志了,但主 hive 还没真正追上。”
7. Reconciler 为什么默认一小时才跑?这背后是“性能”和“可恢复性”的平衡
书里对 Reconciler 的描述非常关键:
- Reconciler 是另一类 lazy writer system thread
- 默认每小时唤醒一次
- 它会冻结 log
- 然后把 dirty log entries 写回 primary hive file
- 之所以做得这么“稀疏”,是因为这样有明显的性能收益。
7.1 为什么不是每次改完就同步主 hive?
因为那样代价太高。
书里明确指出,尤其是对随机访问数据来说,频繁 flush 很贵,尤其在传统机械盘上更明显。
所以 Windows 的思路是:
- 写入频繁发生时
- 先不断记 log
- 主 hive 稍后再批量 reconcile。
7.2 那这样会不会丢数据?
书里也讲得很实在:
- 每小时 reconcile 一次,或者当 log 空间快耗尽时触发
- 这样是很大的性能提升
- 唯一可能发生某些数据丢失的窗口,是 log flush 之间的时间窗口。
这说明 Windows 在这里做的是非常典型的工程平衡:
不是追求“零延迟全同步”,而是在“性能”和“崩溃后恢复能力”之间找最合理的平衡点。
8. 增量日志(incremental logging)到底改进了什么?为什么 Windows 8.1 是个分水岭?
书里明确指出:
Windows 8.1 introduced a big improvement on the performance of the hive sync algorithm thanks to incremental logging.
这一点特别重要,因为它说明旧算法和新算法之间有明显差异。
8.1 旧算法(Windows 8.1 之前)怎么干?
书里给出了一个非常清楚的 4 步流程:
- 把 dirty vector 标记的所有修改单元写成一个 log entry
- 通过只更新一个 sequence number 的方式,invalidate主 hive 的 base block
- 把所有修改数据写到 primary hive file
- 再做 validation,把两个 sequence number 设成新的相同值。
这套流程的问题在于:
- 每轮同步都比较重
- 每个阶段都要 flush
- 对性能尤其是随机 I/O 性能不友好。
8.2 新算法(incremental logging)怎么优化?
书里说得很清楚:
- 旧模型里,多次 validation 之间的所有 dirty data 往往堆成一个大 log entry
- 新模型打破了这个假设
- 每次 lazy flusher 运行时,都写一个新的独立 log entry
- 只有第一次会让 primary hive 的 base block 进入 invalidated 状态
- 后续 flush 继续写新 log entries,但不碰 primary hive file
- 每小时或 log 空间耗尽时,Reconciler 再统一把 log entries 写回主 hive,而且不执行 validation。
这意味着:
增量日志的核心,不是“多写日志”,而是“把主 hive 的重写频率显著降下来”。
9. Cell 的四种状态为什么特别值得记?它其实是理解恢复流程的钥匙
书里在 incremental logging 小节里,专门列出了 hive file 里 cell 可能处于的四种状态:
- Clean:数据在 primary hive file 中,且没再被修改
- Dirty:数据被修改过,但只在内存中
- Unreconciled:数据已写入 log,但还没进 primary file
- Dirty and Unreconciled:写入 log 之后又被再次修改,旧版本只在 log 里,新版本又只在内存里。
9.1 这四种状态为什么重要?
因为它们几乎就是“当前恢复点在哪里”的完整状态图。
比如:
- Dirty说明你还没安全落到日志
- Unreconciled说明日志已经能帮你恢复
- Dirty and Unreconciled说明日志里和内存里已经分叉,需要更精细处理。
这也解释了为什么 Windows 恢复逻辑不是简单的“有日志就直接覆盖”,而是要结合状态判断。
9.2 这对理解 crash case 有什么帮助?
书里后面直接分析了多种 crash case:
- Case A:新数据在内存里,log 已写,但还没 reconcile;重启时就把所有 log entries 应用到 primary hive 并再次 validate。
- Case B:Reconciler 已经把 log 内容写进 primary hive,但还没 validation;重启时会重放已有 log,不过不再真正改主 hive。
- Case C:先 reconcile 了,又新增了一条 log entry;重启时只补那条主文件还没有的新修改。
所以 Windows 的恢复不是“无脑回滚”,而是更接近“按当前已完成阶段做最小必要补写”。
10. 为什么说大多数时候 main hive 其实都处于“dirty state”?这点特别反直觉
书里有一句特别值得记住的话:
Reconciliation并不会更新主 hive 文件里的第二个 sequence number
两个 sequence number 只有在validation phase才会重新变成相同
而 validation 只会在少数场景发生:
- hive unload
- 系统关机
- hive 初次加载
这意味着,在操作系统大部分运行时间里,主 hive 文件其实都处于 dirty state,需要依赖其 log file 才能被正确读取。
10.1 这为什么反直觉?
因为很多人会天然觉得:
系统一直在运行,那主 hive 应该一直是“干净、完整、自洽”的吧?
但 Windows 这里的设计不是这样。
它更像是在说:
主 hive 不需要时时刻刻自己就完美无缺,只要“主 hive + log”这对组合在一起始终可恢复即可。
这其实是一个很典型的现代持久化系统思路。
也就是说,Windows 保证的不是“主文件永远单独完美”,而是“整体状态永远可恢复”。
11. 启动后的恢复与自修复为什么这么重要?因为真正的最坏场景永远是“系统已经崩过一次”
这一节最后还有一块特别硬核的内容,就是boot-time recovery和self-healing。
书里明确说:
- Windows Boot Loader本身就带有与 registry reliability 相关的代码
- 它甚至可以在 kernel 加载前解析
System.log - 并做修复来恢复一致性。
书里还继续说:
在某些 hive corruption 场景下
例如:- base block
- bin
- cell
存在未通过一致性检查的数据
Configuration Manager 甚至可以重新初始化损坏的数据结构
在这个过程中,可能会删除 subkeys
然后继续正常运行
如果系统不得不做这种self-healing
会弹出一个 system error dialog 提示用户。
11.1 这说明什么?
说明 Windows 对注册表的目标不是:
- 一旦有损坏就立即彻底报废
而是:
尽量把系统带回一个还能继续运行的状态,即便代价是丢掉部分损坏分支。
这就是非常典型的“先活下来,再谈完美”的系统可靠性哲学。
注册表可靠性设计的底线,不是“绝不损失任何字节”,而是“尽量让系统还能启动、还能继续工作”。
12. 从桌面支持和排障视角,这一节到底有什么现实价值?
很多人会觉得 Reliability 太偏内核。
但我觉得它对桌面支持和系统学习特别有价值,至少有下面 5 点。
12.1 它能帮我理解为什么 System.log1 / System.log2 这些文件不是“无用残留”
它们就是 hive 恢复链条的一部分。
12.2 它能帮我理解为什么“注册表写入完成”不等于“主 hive 已经同步完成”
中间还有:
- dirty sector array
- log write
- unreconciled array
- Reconciler
- validation
这些阶段。
12.3 它能帮我理解为什么某些注册表修改在崩溃后“还能找回来”
因为 log 里早就记下来了,系统下次会尝试 roll forward。
12.4 它能帮我理解为什么系统有时会自修复后继续启动,但配置看起来少了一块
因为 self-healing 在某些损坏场景下可能会删 subkeys,以换取整体可用。
12.5 它能帮我建立更成熟的“注册表不是文本文件”的认识
注册表的持久化语义,已经非常接近小型日志型存储系统,而不是“写一个 ini 文件”。
13. 最容易误解的 7 个点,我帮你一次理顺
13.1 误区一:改注册表就是直接重写主 hive
不对。
Windows 首先写的是 log,不是 primary hive file。
13.2 误区二:一个 hive 只要一个日志文件就够了
不对。
Windows 使用.log1 / .log2双日志方案,目的是保证 forward progress。
13.3 误区三:两个 sequence number 只是版本计数
不对。
它们更关键的作用是帮助系统判断上次写入流程是否完整结束。
13.4 误区四:Reconciler 一跑完,主 hive 就一定是完全干净状态
也不对。
书里明确说 reconciliation 仍不会更新第二个 sequence number;只有 validation 阶段才会让两个序列号重新相等。
13.5 误区五:incremental logging 只是“多写几次日志”
不准确。
它真正改变的是:减少对主 hive 文件的频繁重写,把更多同步压力转移到增量日志和后续 reconcile。
13.6 误区六:系统崩溃后,只要有日志就一定完全无损恢复
也不绝对。
书里已经说了,log flush 之间仍可能存在某些数据丢失窗口。
13.7 误区七:一旦 hive 损坏,Windows 只能放弃启动
不对。
Boot Loader 和 Configuration Manager 都有修复与 self-healing 逻辑,哪怕有时要牺牲部分 subkeys。
14. 我的学习理解:这一节真正教会我的,不是“怎么写注册表”,而是“怎么让系统在最差场景下还活着”
我觉得10.1.25 Reliability最有价值的地方,不是多教了几个术语,而是让我真正意识到:
注册表的核心挑战从来不只是“怎么存配置”,而是“系统在最差的时机崩了以后,还能不能回来”。
以前如果只看 Regedit,很容易把注册表理解成:
- 一个配置树
- 一组值
- 改了就算生效
但这一节告诉我,Windows 真正在意的是另一层东西:
- 写到哪一步了
- 日志落没落
- 主 hive 同步没同步
- 序列号是不是匹配
- 下次启动时能不能 roll forward
- 如果结构坏了,能不能自修复后继续跑。
所以我觉得这节最值得记成自己一句话的理解就是:
Windows 对注册表的设计,不是“写进去就完了”,而是“写进去之后,就算崩了也尽量能救回来”。
15. 总结提升
如果让我用一句话总结《Windows Internals》10.1.25 Reliability,我会这样说:
注册表可靠性的本质,是通过 log hives、dirty sector array、dual logging、base block 双序列号、incremental logging、Reconciler、validation phase 以及 boot-time recovery/self-healing,把“主 hive 可能来不及完整写回”这件事,转化成“即便在崩溃后也尽量能 roll forward 到一致状态”的系统能力。
这篇最值得记住的 10 个结论是:
- 每个 nonvolatile hive 都有关联的 log hive,用于确保 recoverable state。
- Windows 为 forward progress 采用
.log1 / .log2双日志方案。 - dirty sector array 用位图记录主 hive 中哪些 512-byte sectors 已被修改。
- lazy writer 默认在 log sync 请求后约 1 分钟唤醒,把 dirty data 先写到 log,而不是直接写主 hive。
- base block 的两个 sequence numbers 用来判断上次写入是否完整结束;不匹配时,系统会结合日志 roll the hive forward。
- unreconciled array 用来标记“已进入 log 但尚未写回主 hive”的区域。
- Reconciler 默认每小时执行一次,把 log entries 写回 primary hive,以换取更好的性能和可恢复性平衡。
- Windows 8.1 引入 incremental logging,大幅改善了 hive sync 的性能模型。
- 大多数时间主 hive 其实都处于 dirty state,真正让两个 sequence numbers 重新相等的是少见的 validation phase。
- Boot Loader 和 Configuration Manager 都有修复逻辑;严重损坏时甚至会做 self-healing,并可能删除 subkeys 以换取系统继续运行。
我觉得这一节真正沉淀下来的一句话,就是:
注册表的可靠性设计,本质上不是“主文件永远立刻完美”,而是“主文件 + 日志 + 序列号 + 恢复逻辑”这整套组合永远尽量可救。
下一篇预告
《Windows Internals》10.1.26 Registry performance and optimization:为什么注册表后面的优化重点,已经从“能不能存”变成了“怎样在大 hive、碎片、热键分布和缓存命中之间把启动和运行时性能再往上推”?》
这一篇可以继续把:
- hive reorganization
- hot/cold keys
- Defrag 信息
- contiguous bins
- 访问模式统计
- 为什么这会直接影响启动和运行期性能
全部串起来。
返回顶部