个人主页杨利杰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 hive2.1 为什么不是只要一个日志文件2.2 这些日志文件长什么样3. 第二层基础dirty sector array 到底是什么为什么它是“谁被改过”的总账本3.1 为什么不是直接记“改了哪个 key/value”3.2 它什么时候更新4. 第三层基础lazy writer 为什么不是“立刻重写主 hive”而是“先写日志”4.1 为什么不直接写主 hive4.2 这背后的设计思想是什么5. base block 的两个 sequence number 为什么是可靠性的“红绿灯”5.1 这两个序列号到底起什么作用6. 还有一个常被忽略的结构unreconciled array 到底是干什么的6.1 dirty 和 unreconciled 有什么区别dirty sector arrayunreconciled array7. Reconciler 为什么默认一小时才跑这背后是“性能”和“可恢复性”的平衡7.1 为什么不是每次改完就同步主 hive7.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 误区一改注册表就是直接重写主 hive13.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 stateConfiguration 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 synclazy 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 写入 logdirty 位清除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 步流程把 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 fileDirty and Unreconciled写入 log 之后又被再次修改旧版本只在 log 里新版本又只在内存里。9.1 这四种状态为什么重要因为它们几乎就是“当前恢复点在哪里”的完整状态图。比如Dirty说明你还没安全落到日志Unreconciled说明日志已经能帮你恢复Dirty and Unreconciled说明日志里和内存里已经分叉需要更精细处理。这也解释了为什么 Windows 恢复逻辑不是简单的“有日志就直接覆盖”而是要结合状态判断。9.2 这对理解 crash case 有什么帮助书里后面直接分析了多种 crash caseCase A新数据在内存里log 已写但还没 reconcile重启时就把所有 log entries 应用到 primary hive 并再次 validate。Case BReconciler 已经把 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 blockbincell存在未通过一致性检查的数据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 arraylog writeunreconciled arrayReconcilervalidation这些阶段。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 reorganizationhot/cold keysDefrag 信息contiguous bins访问模式统计为什么这会直接影响启动和运行期性能全部串起来。返回顶部