2026年春夏开源操作系统训练营总结-陈玺州
本文最后更新于 2026年6月21日 下午
一种基于 AI 辅助的 Tokio 异步状态机动态跟踪分析
这次训练营后半段,我把主要精力放在一个 Rust 异步爬虫项目上:不是单纯让它“能跑”,而是想看清楚 async/await 在运行时到底怎样被推进。Tokio、FuturesUnordered、reqwest、文件系统 I/O 这些组件平时看起来都是比较高层的抽象,但它们真正执行时仍然会落到状态机、寄存器、栈帧、堆上捕获变量和 Waker 调度上。
因此,我这次的总结不只记录“我实现了什么”,也记录“我是如何得到结论的”。整体技术路径可以概括为三步:
- 获得原始数据;
- 用 AI 辅助分析原始数据;
- 人工总结、校验并提炼 AI 的分析,生成最后报告。
这个流程最大的优点是:它暴露了 AI 得出结论的完整路径。最终报告不是一段凭空生成的自然语言,而是可以沿着命令、断点、日志、寄存器、内存地址、调用栈逐级追溯回去的分析结果。换句话说,AI 在这里不是“黑箱裁判”,而更像一个帮我整理证据、生成假设、发现结构的研究助手。
项目背景
项目仓库是一个用 Rust 编写的并发网页下载器。同步版本比较直观:遍历任务列表,依次请求网页并写入文件。而异步版本中,核心路径变成了:
1 | |
表面上看,代码只是多了几个 .await。但真正的问题是:每一个 .await 暂停时,局部变量保存在哪里?Pending 后是谁把任务重新唤醒?多个并发任务同时存在时,它们的状态机是如何区分的?
这些问题很适合用训练营里学到的底层视角去拆。
第一步:获得原始数据
我先把程序编译为带调试信息的开发版本:
1 | |
然后用 rust-gdb 运行异步版本,并让 GDB 脚本自动设置断点、打印寄存器、局部变量和调用栈:
1 | |
为了降低一开始的状态空间,我先把并发数限制为 1。这样一次只推进一个下载任务,更容易观察第一个 future 从创建、Pending、被唤醒,到继续执行的完整过程。随后我又追加了并发数为 4 的实验,用来观察多个同类型 async 状态机同时存在时的内存布局。
断点主要设置在几个关键 .await 位置:
download_async.rs:16:外层create_dir_all(&output_dir).awaitdownload_async.rs:25:每个任务内部的task.run_async_to(output).awaitdownloader_async.rs:13-14:reqwest的send().awaitdownloader_async.rs:16-17:response.bytes().awaitdownloader_async.rs:20:内部create_dir_all(parent).awaitdownloader_async.rs:23:最终的write(output, bytes).await
这一步生成的材料包括两类:
- GDB 原始日志:保留每次断点命中的寄存器、局部变量、backtrace;
- 中文整理日志:把原始日志中反复出现的地址、变量、状态机层级和调度行为整理出来。
这一步很关键。没有原始日志,AI 很容易只复述 Rust async 的通用知识;有了原始数据,分析就必须回到具体证据上。
第二步:AI 辅助分析
原始 GDB 日志很长,直接人工读会非常慢。我的做法不是让 AI 一次性“总结全部”,而是分阶段给它任务。
第一类任务是结构化整理。比如让 AI 把每次断点命中按源码位置归类,区分这是外层调度 future、per-task async block,还是 downloader_async 内部 future。这样可以先建立一张“源码行 -> 状态机层级 -> 典型寄存器/变量”的表。
第二类任务是假设生成。比如在 x86_64 SysV ABI 下,函数前几个参数常通过 rdi、rsi、rdx 等寄存器传递;而 Future::poll 在理想情况下可以理解为接收 Pin<&mut Future> 和 &mut Context。AI 可以据此提出某个地址可能对应 future frame、poll context 或捕获变量区。
第三类任务是证据对照。我不会直接接受“某个寄存器一定是什么”的结论,而是要求它把判断依据列出来。例如:
rdx = 0xc,恰好对应"output/async"的字节长度 12;rcx = 0x636e7973612f7475按小端序可读出"ut/async"片段;rdi = 0x555556351ce0附近内存能读到"output/async";_task_context = 0x7fffffffaff8在 backtrace 中多次作为外层cx出现。
这样 AI 的作用就从“给结论”变成了“帮我建立可检查的推理链”。它可以快速发现日志中的重复模式,但每一个关键结论都要能回到 GDB 输出本身。
第三步:人工提炼与校验
AI 生成初步分析后,我再做人工筛选。这里最重要的是区分“能稳定对应的事实”和“只能作为临时现象观察的值”。
例如,下面这些对应关系比较可靠:
0x7fffffffaff8:外层 pollContext,在 backtrace 中多次出现;0x555556351ce0:外层output_dir/create_dir_all相关状态区,内存里能看到"output/async";0x555556355d10:per-task async block 的捕获变量区,包含task/url/output;0x55555642e850:bytes.ptr,即响应体数据缓冲区;110683:本次抓到的响应体长度;0x555556303788:bytes的 vtable,GDB 显示为bytes::bytes::PROMOTABLE_EVEN_VTABLE。
但也有一些值不能强行解释。例如某些断点处的 rdi = 0x517,或者恢复点上的 rdi/rsi/rdx = 0x0,更多只是编译器生成代码中的临时值。源码行断点并不等价于精确停在 Future::poll 函数入口,所以寄存器的语义会随着 codegen 位置变化。
这一点也是 AI 辅助分析里最容易出错的地方:模型会倾向于把所有值都解释得很完整,但底层调试恰恰需要承认“不知道”。最终报告里必须保留这种边界感,明确哪些结论是稳定证据支持的,哪些只是不能过度解读的临时现象。
核心技术结论
通过单并发跟踪,可以看到 Rust async/await 在这个程序中生成的是一组嵌套状态机:
1 | |
每个 .await 的本质过程可以概括为:
- 当前 future 把后续还要用的局部变量保存到自己的状态机 frame;
- poll 子 future;
- 子 future 如果返回
Poll::Pending,当前 future 也把控制权还给 runtime; - I/O ready 后,之前注册的
Waker被触发; - runtime 再次 poll 外层 future;
- 状态机根据内部状态跳回
.await后面的源码位置继续执行。
在日志中,这个过程有非常清楚的对应:
download_async.rs:16 -> 17:外层create_dir_all.await返回;download_async.rs:25反复命中:per-task future 在reqwest send/bytes等待期间被多次 poll;downloader_async.rs:13-14 -> 15:send().await返回,拿到Response;downloader_async.rs:16-17 -> 19:bytes().await返回,可以看到Bytes的ptr/len/vtable;downloader_async.rs:23:write(output, bytes).await使用此前保存在状态机中的output和bytes。
追加的并发 4 实验进一步说明:同一种 async block 状态机可以同时存在多个实例。前四个任务分别对应不同的捕获变量区:
1 | |
后续调度时,日志中可以看到 rsi 在这四个地址之间轮换。这说明 FuturesUnordered 同时保存了多个子 future 实例;当某个任务因为网络或文件 I/O 返回 Pending,它自己的状态留在对应的捕获区中,等 Waker 重新唤醒后再被 poll。
这比单纯阅读文档更直观:异步并发不是“凭空并行”,而是一组状态机实例在 runtime 调度下不断保存、恢复和切换。
如何更好地使用 AI
这次实验给我最大的启发,不是“AI 可以替我写报告”,而是“AI 可以把复杂技术分析变成一条可追踪的流水线”。
我总结出几条更适合底层系统分析的 AI 使用方式:
-
先有原始数据,再让 AI 说话。 让 AI 直接解释 async/await,它会给出通用答案;把 GDB 日志、断点位置、寄存器和 backtrace 给它,它才会被迫面向事实分析。
-
把问题拆成可验证的小块。 不问“帮我分析 Rust 异步状态机”,而是问“这个断点处
rdi/rsi/rdx分别可能对应什么,证据是什么,哪些不能确定”。 -
要求 AI 输出证据链,而不是只输出结论。 最有价值的回答不是“这是 future frame”,而是“这个地址反复出现在同一任务的 poll 中,并且附近内存能对应到
task.url/output,因此它很可能是捕获变量区”。 -
让 AI 主动标注不确定性。 底层调试中,错误的确定性很危险。对于源码行断点、编译器临时值、优化导致的寄存器复用,必须允许结论停在“不能稳定对应”。
-
最终报告由人来收束。 AI 擅长归纳模式和整理材料,但报告要讲清楚问题意识、实验设计、证据强度和结论边界,这些仍然需要人工判断。
这种方式的优点在于可解释性强:从最后一句结论往回追,可以追到 AI 的分析摘要,再追到某个断点命中,再追到原始 GDB 日志。AI 的价值不是替代推理,而是把推理过程显性化、结构化。
训练营后的反思
操作系统训练营让我越来越意识到,底层系统的学习不能只停留在“功能实现”。能写出代码是一层,能解释它为什么这样运行是另一层;能用工具观察它,则会再往前走一步。
这次 Tokio 异步状态机跟踪,本质上也是一次操作系统式的训练:从抽象接口一路向下追到 runtime、future frame、waker、寄存器和内存布局。它让我重新理解了 Rust 异步模型,也让我更清楚地看到 AI 在复杂工程学习中的位置。
我不希望 AI 只是生成一份看起来完整的文字,而是希望它参与到一个可复现、可校验的分析流程里。对于底层系统而言,这一点尤其重要。因为系统软件里真正有价值的结论,往往不是“听起来合理”,而是能够被日志、代码和机器状态共同支撑。