6.6.110-42.4 loongarch64 hung_task / dio_sparse 分析记录

1. 问题背景

  • 缺陷描述来自 bugzilla.txt
  • 环境:TencentOS Server 4.4
  • 内核:6.6.110-42.4.tl4.loongarch64
  • 触发方式:ts_reliability 长时间压力测试
  • 最终现象:Kernel panic - not syncing: hung_task: blocked tasks

相关文件:

  • bugzilla.txt
  • tk5_6.6.110-42.4.tl4.loongarch64_hungtash_20251229_1766999502/vmcore
  • tk5_6.6.110-42.4.tl4.loongarch64_hungtash_20251229_1766999502/vmcore-dmesg.txt
  • tk5_6.6.110-42.4.tl4.loongarch64_hungtash_20251229_1766999502/vmlinux-6.6.110-42.4.tl4.loongarch64

2. 初始日志结论

2.1 panic 不是根因,只是结果

ts_reliability/stress.sh 中周期性开启:

  • kernel.hung_task_panic=1
  • kernel.softlockup_panic=1
  • kernel.hardlockup_panic=1
  • kernel.panic_on_oops=1
  • 以及其它 panic 开关

因此 hung task 一旦出现,会被放大成 panic。

ts_reliability/tst_frame/ts_reliability.cinit_os_env() 实际上尝试关闭 hung_task_panic。两者存在策略冲突,stress.sh 覆盖了框架初始化。

2.2 dmesg 中的直接症状

hung 任务为一批 dio_sparse 进程,典型栈:

  • rwsem_down_write_slowpath
  • down_write
  • unlink_anon_vmas
  • free_pgtables
  • exit_mmap
  • __mmput
  • exit_mm
  • do_exit

另有至少一个 dio_sparse 进程更深一层卡在:

  • __put_anon_vma
  • unlink_anon_vmas
  • free_pgtables
  • exit_mmap

说明问题发生在进程退出时的 mm/rmap 清理路径,而不是普通业务逻辑路径。

3. 测试用例关联

vmcore-dmesg.txt 的时间线可确认:

  • 多个 dio_sparse 系列用例在同一轮压力测试中反复运行
  • 最后触发现场的是 DS003 (dio_sparse) 启动后约 311 秒出现 hung
  • mem_process/memcg_control 的 OOM 是同轮压力中的并发事件,但从时间线看并不是 dio_sparse 卡死的直接原因

结论:

  • dio_sparse 是触发 exit_mmap -> unlink_anon_vmas 问题的直接场景
  • memcg OOM 更像背景噪声/压力放大因素

4. 代码树排查结论

4.1 OpenCloudOS vendor 树

代码服务器路径:

  • /home/robin/Gitee/opencloudos-kernel

已确认 6.6.110-42 版本已经包含较早的修复:

  • d8ab9f7b644a mm: lock VMA in dup_anon_vma() before setting ->anon_vma
  • 824135c46b00 mmap: fix error paths with dup_anon_vma()
  • ee8ab1903e3d mm: rmap: explicitly reset vma->anon_vma in unlink_anon_vmas()
  • 2555283eb40d mm/rmap: Fix anon_vma->degree ambiguity leading to double-reuse

因此,当前问题不是“缺少这几个老补丁”的简单情况。

4.2 主线内核树

主线代码服务器路径:

  • /home/robin/Kernel/linux

主线中发现了更晚、且更接近现场问题域的提交:

  • 61f67c230a5e mm/vma: fix anon_vma UAF on mremap() faulted, unfaulted merge
  • 91901a441fa1 mm/rmap: eliminate partial anon_vma tear-down in anon_vma_fork()
  • 69e945845585 mm/rmap: skip unfaulted VMAs on anon_vma clone, unlink
  • 535f6b8df17d mm/rmap: remove unnecessary root lock dance in anon_vma clone, unmap
  • 3a206a8649f8 mm/rmap: clear vma->anon_vma on error
  • a222439e1e27 mm/rmap: add anon_vma lifetime debug check

这些提交说明:

  • 上游到 2025-2026 仍在继续修 anon_vma 生命周期、unlink、error path、UAF 问题
  • 当前现场与上游持续修复的问题域高度一致

5. loongarch 机器上的 vmcore 现场分析

分析环境:

  • 机器:robin@10.180.13.141
  • 架构:loongarch64
  • crash 可用
  • vmcorevmlinux 匹配

5.1 关键 backtrace

dio_sparse 进程 2501700:

  • unlink_anon_vmas -> down_write

dio_sparse 进程 2501708:

  • unlink_anon_vmas -> __put_anon_vma -> down_write

结论:

  • 不只是单点锁竞争
  • 至少有不同进程在 unlink_anon_vmas() 的不同阶段卡住

5.2 关键异常对象

通过 crash 反查 down_write() 的等待地址后发现:

  • 某个等待地址 0x900000014a1b4b40
  • 按预期应是 anon_vma->root->rwsem
  • 但对该地址附近做结构体解析后发现,它实际落在一个 anon_vma_chain 对象内部

原始内存显示:

- vma = 0x900000011f567780 - anon_vma = 0x9000000108ffd2b0

  • 0x900000014a1b4b50 可被合理解释为 anon_vma_chain.same_vma
  • 该对象中同时能解析出:

而如果强行把 0x900000014a1b4b38 当成 anon_vma 看:

  • root = 0x2
  • rwsem.wait_list.next = 0x900000011f5677c8

这明显不是合法的 anon_vma->root->rwsem 状态。

5.3 由现场推导出的核心结论

结论不是“正常 rwsem 竞争太久”,而是:

  • unlink_anon_vmas() 路径中使用的 anon_vma/root 相关对象已经损坏
  • 内核随后把错误地址当成 rwsemdown_write()
  • 最终导致多个 dio_sparse 进程永久 D 状态

也就是说,问题更像:

  • anon_vma
  • anon_vma_chain
  • vma->anon_vma
  • anon_vma->root

这些对象/指针/生命周期关系在更早阶段已经被破坏。

6. 当前技术判断

按概率排序:

  1. 内核公共 mm/rmap 路径问题
  2. LoongArch 架构侧放大/触发某个公共问题
  3. 硬件问题概率最低

目前没有证据表明这是:

  • 纯硬件随机比特翻转
  • 纯 LoongArch 专属代码 bug

当前最像的是:

  • anon_vma 生命周期
  • anon_vma_chain 链接关系
  • unlink_anon_vmas() 清理路径
  • 并发退出场景下的对象错连/UAF/错误恢复缺陷

7. 后续分析方向

下一步建议聚焦主线补丁短名单:

  • 61f67c230a5e
  • 91901a441fa1
  • 69e945845585
  • 535f6b8df17d
  • 3a206a8649f8
  • a222439e1e27

重点看这些补丁是否解释:

  • anon_vma 提前部分 tear-down
  • unlink_anon_vmas() 仍处理不该处理的 VMA
  • error path 未清空 vma->anon_vma
  • 某些 faulted/unfaulted VMA merge/fork/unmap 场景导致 anon_vma 引用关系错误

8. 主线补丁与现场的进一步对照

8.1 vendor 6.6 树当前仍是旧实现

/home/robin/Gitee/opencloudos-kernel 当前 mm/rmap.c 中:

- out_error: - unlink_anon_vmas(vma);

- root = lock_anon_vma_root(root, anon_vma);

  • anon_vma_fork() 在分配 anon_vma/anon_vma_chain 失败时仍走:
  • unlink_anon_vmas() 仍采用:

这与现场卡住栈完全一致:

  • unlink_anon_vmas()
  • down_write()
  • lock_anon_vma_root()

说明现场路径与旧实现高度吻合。

8.2 主线后续补丁的直接意义

91901a441fa1

该补丁的核心是:

  • 避免在 anon_vma_fork() 分配失败时
  • 把“部分建立完成但无效”的 VMA 再送入 unlink_anon_vmas()

补丁说明里明确写到:

  • 老实现会把 partially set up / invalid VMA 传给 unlink_anon_vmas()
  • 这样危险且容易引入 subtle bugs

这与当前现场非常接近,因为我们现场中:

  • unlink_anon_vmas() 路径里拿到的 anon_vma/root 已经不是可信对象
  • 进而对错误地址执行 down_write()
69e945845585

该补丁让以下场景直接返回 no-op:

  • anon_vma_clone() 面对 !vma->anon_vma
  • unlink_anon_vmas() 面对 !vma->anon_vma

也就是上游进一步明确:

  • unfaulted / 未正确接入 anon_vma 的 VMA,不应该继续走 unlink 清理逻辑

这同样在收紧“无效 VMA 被送入 unlink_anon_vmas()”的问题面。

535f6b8df17d

该补丁把 unlink_anon_vmas() 从旧逻辑:

  • 逐个 lock_anon_vma_root(root, anon_vma)

改成新逻辑:

  • 直接取 active_anon_vma = vma->anon_vma
  • anon_vma_lock_write(active_anon_vma)

也就是不再在 unlink 过程中反复依据链上对象切换/比较 root 锁。

这点和现场尤其贴近,因为现场最强证据就是:

  • 某个 down_write() 等待地址并不落在合法 anon_vma->root->rwsem
  • 而是落进了一个 anon_vma_chain 对象内部

因此,这个补丁虽然未必是唯一修复,但它确实绕开了现场当前最可疑的坏指针使用点。

3a206a8649f8

该补丁在 cleanup_partial_anon_vmas() 结束后明确增加:

  • vma->anon_vma = NULL;

目的就是避免:

  • VMA 部分清理后仍残留一个无效 vma->anon_vma
  • 从而导致后续 unlink / reuse / num_active_vmas 统计进入不一致状态

这与我们当前“对象关系先坏、后续 unlink 再踩中”的模型也是一致的。

8.3 当前最像的根因模型

结合现场与上游补丁,当前最像的模型是:

  1. 某个 VMA 在 anon_vma_fork() / clone / error unwind / cleanup_partial_anon_vmas()` 这类路径上进入了部分建立或部分清理状态。
  2. 该 VMA 的 anon_vmaanon_vma_chain、root 关系没有被正确清理或保持一致。
  3. 之后在进程退出的 unlink_anon_vmas() 中,旧实现仍继续依据这些不一致对象去 lock_anon_vma_root()
  4. 最终把错误内存当成 rwsem,造成永久阻塞。

8.4 现阶段最值得优先关注的上游补丁

按与现场贴近程度排序,当前最值得优先关注的是:

  1. 91901a441fa1 mm/rmap: eliminate partial anon_vma tear-down in anon_vma_fork()
  2. 69e945845585 mm/rmap: skip unfaulted VMAs on anon_vma clone, unlink
  3. 535f6b8df17d mm/rmap: remove unnecessary root lock dance in anon_vma clone, unmap
  4. 3a206a8649f8 mm/rmap: clear vma->anon_vma on error

补充判断:

  • 这几笔修复在主线可见,但在当前 vendor 树里没有检到。
  • 因此现场问题很可能属于主线后续已经识别并逐步修正的同类缺陷。

9. 按上游补丁序列回移到 vendor 树

根据用户要求,未再采用“直接魔改”的方式,而是在代码服务器上尽量按上游补丁序列回移。

远端分支信息:

  • 仓库:/home/robin/Gitee/opencloudos-kernel
  • 基线:devel 分支 b1f001b202a1
  • 测试分支:codex/anon_vma_backport_test

已回移的上游补丁主题:

  1. 542eda1a8329 mm/rmap: improve anon_vma_clone(), unlink_anon_vmas() comments, add asserts
  2. 91901a441fa1 mm/rmap: eliminate partial anon_vma tear-down in anon_vma_fork()
  3. 69e945845585 mm/rmap: skip unfaulted VMAs on anon_vma clone, unlink
  4. 535f6b8df17d mm/rmap: remove unnecessary root lock dance in anon_vma clone, unmap
  5. bfc2b13b05a1 mm/rmap: allocate anon_vma_chain objects unlocked when possible
  6. 3a206a8649f8 mm/rmap: clear vma->anon_vma on error

回移方式说明:

  • 先将主线补丁导出为 patch。
  • 在 vendor 测试分支上优先尝试 git am -3
  • 542eda1a832991901a441fa169e945845585 可直接应用。
  • 535f6b8df17dbfc2b13b05a1 因上下文差异无法自动构造 fake ancestor,采用最小冲突解决后继续 git am --continue
  • 3a206a8649f8 在前置语义补齐后可直接应用。

最终状态:

  • 测试分支最近 6 个提交均为上述 backport 结果。
  • 当前改动集中在 mm/rmap.c 单文件。
  • 相对基线 b1f001b202a1mm/rmap.c 统计为 163 insertions(+), 102 deletions(-)

当前可用于测试的分支:

  • codex/anon_vma_backport_test