Skip to content

导引:病灶与治法

这一章咱们还得把两种病灶分清:014 讲的是「静默别名」——那是数据结构 race(inode_cache_ 里活引用被驱逐覆盖),治法是引用计数;这一章讲的是「共享 scratch clobber」——那是中间 buffer 被并发覆盖,加锁/引用计数都治不了,治法只能是消除共享。两类 race 长得像,病根正交,别混。

这章咱们要点亮什么

  1. 能跑的 ext2 ≠ SMP-safe 的 ext2:block_buf_ 在单核下是合理的单所有者用法,可它挂在实例上——同一个 ext2 实例的所有块 I/O 共享它。SMP 一压就互踩,而且症状(wild 块号)离病灶(buffer 被覆盖)隔了好几层,单靠 QEMU forensics 难抓。搬家到 host、上 TSAN,是为了有「第一秒就抓到」的确定性工具。
  2. 共享 scratch clobber 是单独一类 race,治法是消除共享不是加锁:这是和 014「静默别名」最关键的区别。引用计数治「对象被释放」,加锁治「读改写 RMW 的并发改」,两者都治不了「两个调用方往同一块中间 buffer 写」。只有给每条 SMP 路径一块自己的 buffer 才行。
  3. 4KB 的 buffer 不能放栈,不是偏好,是约束:#PF 跑在受限栈上(它可能嵌在中断上下文、甚至嵌在别的 ISR 里触发,可用栈深本来就宝贵;文件读写调用链还会深递归进 demand-page,把 16KB 任务栈也吃紧),在它里面再撑一个 4KB 栈数组就贴着栈底跑了。所以选堆——KmBuf 一个极简 RAII 类,构造 kmalloc、析构 kfreeoperator bool 查 OOM。约束选容器,不是偏好选容器。
  4. 大调用面的 race 修复,标准姿势是加重载不是改签名:read_block(blk) / write_block(blk) 这些单参版保留——它们挂在 init 期(挂载时单线程读 superblock/bgdt,那时还没起并发,加注释证安全);新增 read_block(blk, dst) / write_block(blk, src) 双参版,SMP 路径全走这组。不删旧签名、给新签名明确标注,比一刀切干净更诚实也更安全。

035_multi_terminal-45-gf25de18 · f25de18 · 2026-08-04