《Windows Internals》10.1.25 Reliability:为什么注册表不是“写进去就完了”,而是从 base block 序列号、增量日志到恢复流程都在围绕“崩溃后还能回来”做设计
2026/4/19 22:49:51 网站建设 项目流程


🔥个人主页:杨利杰YJlio
❄️个人专栏:《Sysinternals实战教程》 《Windows PowerShell 实战》 《WINDOWS教程》 《IOS教程》
《微信助手》 《锤子助手》 《Python》 《Kali Linux》
《那些年未解决的Windows疑难杂症》
🌟让复杂的事情更简单,让重复的工作自动化


《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 arraylazy writerbase block 双序列号incremental loggingReconciler、以及启动时的恢复与自修复机制,共同保证可靠性。

所以这篇文章,我就围绕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.log1
  • Sam.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 还没同步。

也就是说,它们分别对应两个阶段:

内存中 hive 被修改

dirty sector array 记账

lazy writer 写入 log

dirty 位清除

unreconciled array 记账

以后由 Reconciler 写回主 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 步流程:

  1. 把 dirty vector 标记的所有修改单元写成一个 log entry
  2. 通过只更新一个 sequence number 的方式,invalidate主 hive 的 base block
  3. 把所有修改数据写到 primary hive file
  4. 再做 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 recoveryself-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 个结论是:

  1. 每个 nonvolatile hive 都有关联的 log hive,用于确保 recoverable state。
  2. Windows 为 forward progress 采用.log1 / .log2双日志方案。
  3. dirty sector array 用位图记录主 hive 中哪些 512-byte sectors 已被修改。
  4. lazy writer 默认在 log sync 请求后约 1 分钟唤醒,把 dirty data 先写到 log,而不是直接写主 hive。
  5. base block 的两个 sequence numbers 用来判断上次写入是否完整结束;不匹配时,系统会结合日志 roll the hive forward。
  6. unreconciled array 用来标记“已进入 log 但尚未写回主 hive”的区域。
  7. Reconciler 默认每小时执行一次,把 log entries 写回 primary hive,以换取更好的性能和可恢复性平衡。
  8. Windows 8.1 引入 incremental logging,大幅改善了 hive sync 的性能模型。
  9. 大多数时间主 hive 其实都处于 dirty state,真正让两个 sequence numbers 重新相等的是少见的 validation phase。
  10. 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
  • 访问模式统计
  • 为什么这会直接影响启动和运行期性能

全部串起来。

返回顶部

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询