编程 Bun Rust 迁移全链路深度拆解:从 53 万行 Zig 到 100 万行 Rust,AI 重写基础设施的工程极限

2026-08-18 13:43:11 +0800 CST views 11

Bun Rust 迁移全链路深度拆解:从 53 万行 Zig 到 100 万行 Rust,AI 重写基础设施的工程极限

本文覆盖:内存泄漏根因分析、全量迁移 vs 增量迁移决策、三阶段流水线设计、实现者-对抗性审查者分离机制、64 Claude 并行工程力学、19 个回归 bug 根因剖解,以及 16.5 万美元成本重构背后的软件工程哲学。所有数据来自 Jarred Sumner 官方博文,未经二手加工。


一、背景:为什么 Bun 必须离开 Zig

2026 年 7 月 8 日,Bun 团队正式宣布完成从 Zig 到 Rust 的全面重写。从 5 月 3 日启动到 5 月 14 日合并入主干,总耗时 11 天,涉及 53.5 万行 Zig 代码 → 100.9 万行 Rust 代码,并行使用 64 个 Claude 实例,总计提交 6,778 次,API 成本 16.5 万美元

这不是一次普通的技术选型变更。这是一场关于"AI 能否承担生产级基础设施语言迁移"的极限验证。

1.1 问题的本质:混合内存管理地狱

Bun 是一个 JavaScript 全栈工具链,集成了 JavaScript runtime(基于 JavaScriptCore)、包管理器、打包器(Bundler)、测试框架。它的代码构成是:

  • 约 80% Zig:Bun 的核心业务逻辑
  • 约 20% C++:嵌入的 JavaScriptCore、uWebSockets、BoringSSL

Bun 面临的核心矛盾在于:JavaScriptCore 是有 GC 的,Zig 是手动内存管理的,两者之间的边界处理成了一个持续失血的伤口。

看 Bun v1.3.14 changelog 里修复的那批 bug:

node:zlib 异步 .write() 未完成时调用 .reset() → 堆释放后使用(use-after-free)
node:http2 JS 回调触发哈希表重哈希 → 内部流指针失效 → use-after-free
UDPSocket.send() 的 valueOf() 回调在载荷捕获与实际发送之间分离了 ArrayBuffer
crypto.scrypt 输出缓存分配失败时,回调和密码/盐缓存永不释放
tlsSocket.setSession() 每次调用泄漏一个 SSL_SESSION(~6.5KB/次)

全是 use-after-free、双重释放、忘记释放。Jarred 本人在博文中写道:

"我每天带着崩溃报告睡不着觉。"

这不是功能 bug,这是 Rust 的类型系统在编译期就能拒绝掉的 bug。安全 Rust 里,use-after-free 是编译错误,不是 CI 失败,更不是用户投诉。

1.2 为什么不用 C++?

Bun 已经有大量 C++ 代码,迁移到 C++ 看起来是最自然的选择。C++ 有构造/析构函数,可以把 Zig 里的 defer 手动清理逻辑收进 RAII。

但 Jarred 的判断是:C++ 的内存安全靠"人",Rust 的内存安全靠"编译器"

C++ 的 safety 工具链(ASAN、MSAN、RAII、禁用法则)本质上都是"最好努力"——你可以在 CI 里加 -Werror,但你无法让编译器强制执行。代码审查员会疲劳,fuzzer 跑不到所有路径。

Rust 的核心承诺是:清单里那一长串 use-after-free,在安全 Rust 里直接是编译错误。 这不是事后发现,而是事前预防。

1.3 Zig 社区的"AI 禁令":一条政策引发的蝴蝶效应

Bun 团队此前已经 fork 了 Zig,引入了 LLVM 并行代码生成,debug 编译速度提升了 4 倍。但这些优化始终无法 upstream 回 Zig 官方,核心原因是 Zig 社区严格的 "no-AI policy"——禁止 AI 生成 issue、PR 甚至评论。

而与此同时,Anthropic 收购了 Bun,Claude Code 深度依赖 Bun runtime。结果就是:一边是 Zig 社区全面封禁 AI 生成代码,另一边是 Bun 团队开始用 Claude 大规模把 Zig 本身迁移出去。

这不是巧合,这是一次哲学碰撞:人类编写的"正确代码" vs AI 生成的"够用代码"。


二、关键架构决策:为什么选择一次性全量迁移

这是整个迁移工程中信息量最大的决策节点。

2.1 增量迁移 vs 全量迁移

Bun 早期,Jarred 曾手工把 esbuild 的转译器从 Go 迁移到 Zig,这个经验深刻影响了他对迁移策略的判断:

增量迁移会遗留大量"过渡代码",短期和中期都会非常痛苦。每次改动都要同时维护两套逻辑,开发者体验极差,而且过渡代码会沉淀成长期技术债务。

全量迁移虽然看起来风险大,但实际上:

  1. 有 Zig 版本作为"参考实现",新 Rust 代码的行为可以逐一验证
  2. 避免了长期双轨维护
  3. 可以在迁移完成后立即删除所有 Zig 代码

2.2 机械翻译优先,而非理想化重构

第二个决策更反直觉:先做忠实的机械翻译,让新代码在结构上尽量贴近原 Zig 代码,把降低 unsafe 用量、往地道 Rust 风格靠拢的工作留到 v1.4 发布后再逐步重构。

这个决策的理由是:

  • 如果一开始就追求"完美的 Rust 代码",迁移周期会变成数月甚至数年
  • 原 Zig 代码的行为是已知的参照物,"行为一致"比"代码优雅"优先级更高
  • Rust 的借用检查器会在编译期告诉你哪些地方需要重新设计——迁移完成后再来处理这些"类型驱动的重构"更高效

2.3 Phase A / Phase B 双阶段流水线

Bun 官方博文把迁移拆成了两个明确阶段:

阶段目标约束
Phase A忠实保留 Zig 逻辑,哪怕 Rust 代码暂时不能编译不追求 Rust 风格,只追求语义等价
Phase B逐个 crate 解决编译、构建和运行问题按 crate 分组并行,每组找 1 implementer + 2 reviewers

这个约束体系非常重要——它把一个看似混乱的大规模迁移,变成了一个有严格边界的任务队列。


三、工程力学:64 个 Claude 并行 × 11 天

3.1 动态工作流(Dynamic Workflow)的核心架构

Jarred 搭建了一套以 Dynamic Workflow 为核心的 AI 驱动流水线。峰值时运行了约 50 个并发工作流,每个工作流持续循环:

# 伪代码:每个 Workflow 的核心循环
while task = todo_list.pop():
    result = implementer(task)           # Claude 实现者:写代码
    feedback = adversarial_reviewer(result) # Claude 审查者A:找问题
    feedback2 = adversarial_reviewer(result) # Claude 审查者B:找更多问题
    apply_feedback(feedback, result)       # 应用审查意见
    apply_feedback(feedback2, result)

峰值配置:4 个 git worktree × 每个 worktree 16 个 Claude = 64 个并行实例

峰值产出:每分钟 58 次提交、1,300 行代码。

总提交数:6,778 次。

3.2 PORTING.md:576 行的 AI 迁移宪法

Jarred 编写了一份长达 576 行的 PORTING.md,这是整个迁移的"宪法文档"。它规定了:

  • 迁移分 Phase A(忠实翻译)和 Phase B(编译/构建/运行)
  • 文件命名规范、crate 引用规则
  • 禁止使用 tokio/rayon/hyper/futures(保持与 Zig 版一致的同步执行模型)
  • 禁止 async fn(避免引入 Rust 异步生态,保持行为一致性)
  • unsafe 必须写明 SAFETY 注释
  • 遇到不确定逻辑时宁可留下 TODO,也不要让 AI 自行猜测

这个文档的约束比大多数工程团队的代码规范还要细致。它本质上是一个 prompt engineering 的产物——Jarred 发现问题后,不去手动修代码,而是修改 workflow prompt("告诉 Claude 不要做什么"),问题自动消失。

3.3 LIFETIMES.tsv:全代码库结构体生命周期分析

除了 PORTING.md,还有一份 LIFETIMES.tsv(Tab-Separated Values),逐文件分析每个结构体字段的生命周期。这个文件是 AI 翻译结构体时的关键参考,确保 Zig 的资源管理模式(手动释放)在 Rust 里找到对应的 RAII 替身。

3.4 踩坑实录:Claude 并行时遇到的问题

Bun 官方博文和 Jarred 在 X 上的跟进透露了几个实际遇到的坑:

问题 1:git stash 踩踏
多个 Claude 实例同时工作时,会互相覆盖对方的改动,触发 git stash 踩踏。
解决:在 workflow prompt 里禁止 git stash,强制每个 Claude 在本地修改文件而不依赖暂存区。

问题 2:长注释 workaround
Claude 在遇到编译错误时,会写很长的注释来"justify"一个 workaround,而不是真正修复问题。
解决:prompt 里禁止写长注释

问题 3:stub out 而非修复
Claude 在编译错误阶段倾向于 stub out(有 TODO)而不是真正实现。
解决:Phase B 的 workflow 针对"编译错误"专门配置了修复流程,每个 crate 分配独立的 implementer + reviewers + fixer。

问题 4:循环依赖
Zig 代码库是单编译单元(1 个大文件),迁移到 Rust 时拆成了 ~100 个 crates,产生了海量的循环依赖错误。
解决:额外跑了一个 workflow 来分析代码的模块归属——告诉每个 Claude"这段代码应该放在哪个 crate",再做编译错误修复。


四、核心机制:实现者-对抗性审查者分离

这是整个迁移工程里最值得抄走的工程设计。

4.1 为什么分离是关键

Jarred 的洞察非常直接:

"写代码的人天然想让代码尽快合并。这个动机会让他对自己代码的问题视而不见。Claude 也一样——负责实现的 Claude 想让代码通过,负责审查的 Claude 的唯一任务就是挑毛病。"

这个设计在人类工程团队里也适用,但在 AI 驱动的流水线里尤为关键——因为 AI 没有疲劳、没有自尊心,审查者 Claude 可以无限"轴"下去。

4.2 审查者配置

  • 1 个实现者 Claude:拿到任务,执行代码生成
  • 至少 2 个对抗性审查者 Claude:只拿到代码 diff,看不到实现者的推理过程
  • 审查者被明确告知:默认这段代码是错的(default to wrong)

审查者的目标不是"理解代码想做什么",而是"找到这段代码在做什么层面的错误"。

4.3 三个真实拦截的 bug

Bun 博文公开了三个被对抗性审查机制拦截的真实 bug,每个都值得细细品味:

场景 1:异步关闭的悬空指针(use-after-free + double free)

// 实现者写的代码(有 bug)
fn close_async(pipe: Box<Pipe>) {
    uv_close(Box::into_raw(pipe) as *mut libc::c_void, Some(callback));
    // 函数返回时 Box 自动释放
    // 但 callback 里会再次释放同一块内存
}

// 审查者拦截后改成的代码
fn close_async(pipe: Box<Pipe>) {
    let leaked = Box::leak(pipe); // 泄漏 Box,uv_close 负责释放
    uv_close(leaked as *mut Pipe as *mut libc::c_void, Some(callback));
}

Zig 版本里,defer 可以确保释放顺序正确。Rust 版本里,如果让 Box 在函数返回时自动 drop,就会在 uv_close 回调触发时造成 double free。Box::leak 把释放权交给 uv_close 回调,是正确的生命周期管理。

场景 2:负数时间戳的 nsec 溢出

// 实现者写的代码(有 bug)
let seconds = trunc(timestamp);  // 截断浮点秒数
let nsec = ((timestamp - seconds) * 1_000_000_000.0) as i64;

// 问题:对 1970 年之前的文件(负数时间戳),
// 截断得到的 nsec 是负数(非法的 timespec)
// 例如:timestamp = -1.5 → seconds = -1(trunc 向零截断)
// nsec = (-1.5 - (-1)) * 1e9 = -0.5 * 1e9 = -500,000,000(非法)

// 审查者建议的修复
let seconds = floor(timestamp);
let nsec = ((timestamp - seconds) * 1_000_000_000.0) as i64;
// floor(-1.5) = -2.0 → nsec = (-1.5 - (-2.0)) * 1e9 = 500,000,000(合法)

这个 bug 非常隐蔽:测试用例几乎不会覆盖 1970 年之前的文件时间,但对合规性(POSIX timespec 要求 nsec ≥ 0)有影响。审查者 Claude 在语义层面找到了这个问题。

场景 3:unwrap_or 的立即求值陷阱

// 实现者写的代码(有 bug)
fn parse_color_mix(input: &str) -> Color {
    let percent = input.strip_suffix('%')
        .and_then(|s| s.parse::<f32>().ok())
        .unwrap_or(100.0); // 括号外的 100.0 在函数调用时立即求值
    // 问题:如果 input 是 "50%" 但解析失败,
    // unwrap_or(100.0) 会被解释为 unwrap_or_else(|| 100.0) 的错误替代
    // 在某些上下文里,这会导致逻辑错误
    Color::mix(percent / 100.0)
}

// 审查者建议的修复
fn parse_color_mix(input: &str) -> Color {
    let percent = input.strip_suffix('%')
        .and_then(|s| s.parse::<f32>().ok())
        .unwrap_or_else(|| 100.0); // 惰性求值,解析失败时才返回 100.0
    Color::mix(percent / 100.0)
}

unwrap_or(expr) 里的 expr立即求值的。在 Rust 里这意味着即使不需要 fallback 值,100.0 这个表达式也会被求值。虽然在这个例子里影响不大,但这个模式在更复杂的语境下会导致性能问题或语义错误。

三个 bug 的共同特点:都能正常编译,代码看起来都合理——如果没有专门找茬的第二个 Claude,大概率被直接合入主干。


五、三阶段流水线全解

5.1 Phase A:机械翻译(1,448 个 .zig → .rs)

Phase A 阶段先做 3 个文件的试迁移来验证流程,然后扩展到全部 1,448 个 .zig 文件。由于 Bun 的 git 仓库太大(Zig 编译产物等),最终方案是 4 个 worktree,各跑 16 个 Claude,64 个并行实例同时工作。

这个阶段的核心约束:忠实保留 Zig 的语义,不追求 Rust 风格

典型的 Zig → Rust 机械翻译模式:

// Zig
pub fn read_file(path: []const u8) ![]u8 {
    var file = try std.fs.cwd().openFile(path, .{});
    defer file.close();
    return try file.readToEndAlloc(allocator, std.math.maxInt(usize));
}
// Phase A 机械翻译结果
pub fn read_file(path: &[u8]) -> Result<Vec<u8>, Error> {
    let mut file = std::fs::File::open(std::path::Path::new(
        std::str::from_utf8(path).unwrap()
    ))?;
    // defer file.close() → Rust Drop 自动处理
    let mut buffer = Vec::new();
    file.read_to_end(&mut buffer)?;
    Ok(buffer)
}

5.2 Phase B:修复 ~16,000 个编译错误

Phase A 完成后,产生了约 16,000 个编译错误,其中最难的是循环依赖

Zig 代码库是单编译单元(1 个大文件),迁移到 Rust 时必须拆成 ~100 个 crates。循环依赖产生了大量"找不到这个类型"的错误。

解决方案:按 crate 分组并行修复。每个 crate 分配:

  • 1 个 implementer Claude
  • 2 个 reviewer Claude(对抗性审查)
  • 1 个 fixer Claude(应用审查意见)
Phase B 工作队列:
16,000+ 编译器错误
  → 按 crate 分组(约 100 个 groups)
    → 每个 group: cargo check → 修复 → 2 个审查者 → 1 个应用者
      → 64 个 Claude 并行处理

5.3 Phase C:测试套件全绿

Bun 的测试套件包含三类测试:

  1. 内存泄漏测试:用 ASAN/MSAN 持续监控,验证重写后没有新增泄漏
  2. 集成测试:包括 next dev 热更新的长时间稳定性测试(数小时不重启)
  3. 压力测试:耗尽 TCP socket、读写 GB 级数据、spawn 10,000 个进程

测试隔离用了 systemd-run(cgroups 沙盒),确保每个测试分片都在独立环境中运行。

测试分片通过顺序:

  • Linux x64 60 个分片先全绿
  • macOS x64 + arm64
  • Windows x64 + arm64
  • Linux ARM64 glibc + musl

从第一个测试绿到全平台全绿,用了约 3 天。


六、核心数据与成本分析

6.1 全套数字

指标数值
Zig 代码(不含注释)535,496 行
Rust 代码1,009,272 行
迁移耗时11 天(5/3 → 5/14)
并行 Claude 实例64 个(4 worktrees × 16)
总提交次数6,778 次
峰值产出58 提交/分钟,1,300 行/分钟
API 成本$165,000
Token 消耗5.9B uncached input + 690M output
缓存读取72B cached input tokens
删除的测试用例0 个
等效人力~3 人年(全周期冻结功能开发)
二进制体积缩减~20%
已知回归19 个(全部已修复)
Rust unsafe 占比~4%(13,000 个 unsafe 关键字)

6.2 成本拆解:$16.5 万值不值

按 API 定价计算,这次迁移花了约 $165,000。但等效工作量是 ~3 人年(按 6 位工程师全周期专注迁移、冻结功能开发估算)。

换算一下:

  • 传统人工迁移(3 人年 × $15万/年薪 × 2.5 倍成本系数)≈ $112.5 万
  • AI 驱动迁移($16.5 万 API 成本)≈ 节省 85%

但这只是数字。更重要的是:11 天 vs 几个月。在快速迭代的基础设施项目里,迁移周期缩短到原来的 1/20,意味着市场窗口、竞争压力、团队士气都有截然不同的走向。

6.3 unsafe 分析:13,000 个 vs 73 个(uv 的对比)

Theo(t3.gg 创始人)曾提出质疑:uv 包含 35 万行 Rust 代码,只有 73 个 unsafe 调用;而 Bun Rust 移植版有 68.1 万行代码,超过 13,000 个 unsafe 调用,相差约 180 倍。

Jarred 的回应:

"今天已经下降了大约 2,000 个。我预计最终会稳定在 1 万左右,因为 Bun 的大部分内容都是用 C 和 C++ 编写的——这种情况不会改变。"

这个对比其实不完全公平:uv 是一个相对纯粹的 Rust 项目,而 Bun 需要与 JavaScriptCore(WebKit 的 JS 引擎)、uWebSockets、BoringSSL 等大量 C/C++ 代码交互,文件系统、网络、JS 引擎集成这些操作在 Rust 里都绕不开 unsafe。

13,000 个 unsafe 里,真正危险的只是少数——审查者 Claude 会重点关注 unsafe 块的 SAFETY 注释,而 Phase B 之后的安全重构会逐步降低这个数字。


七、19 个回归 bug 根因剖解

合并入主干后,Bun 团队发现了 19 个回归 bug,全部已修复。根因分析非常有价值——它们几乎全是"语法相同但语义不同"的陷阱。

7.1 debug_assert! 的宏语义陷阱

这是最具代表性的回归:

// Zig: assert 是函数,参数在 release 构建中也会执行
assert(try dev.client_graph.insertStale(...));

// Phase A 机械翻译:Rust 里最接近的是 debug_assert!
// debug_assert! 是宏,release 构建中整行被擦除
debug_assert!(dev.client_graph.insert_stale(...)? == ...);

// → insert_stale 在 release 构建中根本不执行!
// → Hot Module Replacement(HMR)在 production 模式下完全损坏

修复方案:全部改回 assert!debug_assert_ne! 并保留实际执行逻辑。

7.2 bytemuck::cast_slice 对奇数长度 slice 直接 panic

// Zig: reinterpretSlice 忽略尾字节
let bytes: []u8 = @as(*const [N]u8, ptr).*[0..N];
// 对 [1, 2, 3, 4] reinterpret 后取 3 字节 → [1, 2, 3](忽略尾字节)

// Rust: bytemuck::cast_slice 要求对齐的字节数
let bytes = bytemuck::cast_slice::<u8, i32>(&[1u8, 2, 3, 4]);
// 对 4 字节 slice 合法 → [0x04030201]
// 对 3 字节 slice → panic!(必须 4 字节对齐)

这个 bug 在测试覆盖率不足的代码路径上触发了 crash。

7.3 其他回归

  • Zig 的 @intFromPtr 和 Rust 的 ptr as usize 在某些边界情况下的行为差异
  • 不同平台的 size_of::<T>() 在跨语言边界上的对齐问题
  • async/await 在 Node.js 兼容层的 event loop 交互语义差异

八、迁移后的成果

8.1 内存泄漏彻底消除

这是最直接的结果:所有之前靠 ASAN 和模糊测试"事后发现"的内存安全问题,在安全 Rust 里直接是编译错误。

8.2 二进制体积缩减 20%

Rust 的编译器优化 + LLVM LTO 跨语言生效(Zig 和 C++ 边界通过 bitcode 做了链接时优化),使得:

  • Windows x64 减少 17.66 MB
  • Linux x64 减少 8.58 MB
  • Linux aarch64 减少 9.07 MB

8.3 性能提升

虽然 Jarred 说"还没见过比 Zig 版慢的 benchmark",但实际上:

  • Linux 平台:Claude Code 启动速度提升 10%(6 月 17 日 Claude Code v2.1.181 已集成)
  • 跨语言 LTO 使 HTTP 吞吐提升 3.5%,Bun.escapeHTML 提升 6.5%
  • 多平台吞吐量均有提升

8.4 自动化安全基础设施

迁移完成后,Bun 团队上线了:

  • 24/7 引导模糊测试:覆盖所有解析器(JS/TS/JSX/CSS/JSON5/TOML/...),自动提交 PR 修复发现的问题
  • 11 轮 Claude Code Security 安全审查:用 Claude agent 做自动化代码审计

九、给软件工程师的启示

这次迁移的价值远超"Bun 换了语言"本身。它第一次用如此详尽的一手数据,回答了一个悬而未决的问题:AI Agent 到底能不能承担生产级基础设施项目的完整语言重写?

9.1 对抗性审查是质量的关键

不是"AI 写代码"厉害,是"AI 写 + AI 审"这个协作模式厉害。实现者和审查者分离、审查者分开上下文窗口、审查者被告知"默认这段代码是错的"——这三件事组合起来,才构成了大规模 AI 代码生产的质量护栏。

9.2 工作流的 prompt 比代码更重要

Jarred 几乎所有问题的解决方式都是 edit the workflow prompt,而不是手动修复输出。

问题:Claude 互相 git stash 踩踏
→ 解决:prompt 里禁止 git stash,问题自动消失

问题:Claude 写长注释 justify workaround
→ 解决:prompt 里禁止长注释,问题自动消失

"edit the loop, not the output" 本身就是一套工程方法论。

9.3 测试套件是 AI 代码生产的必要基础设施

0 个被删除的测试用例、138 万个 expect 断言——没有这套百万级测试体系,没有任何人敢合并 100 万行 AI 代码。

对于想用 AI 做大规模代码生产的团队来说,这是一条硬性前提:你的代码库有足够的测试覆盖率来兜底吗?如果没有,先补测试覆盖率。

9.4 成本已经不是拦路虎

$16.5 万做 3 人年的工作量,在商业决策层面已经不需要犹豫。真正的问题不是"贵不贵",而是:

"你的代码库有足够的测试覆盖率来兜底吗?"

9.5 语法相同 ≠ 语义相同

19 个回归几乎全是跨语言"看起来一样但行为不同"的陷阱:

  • assert vs debug_assert! 的构建期语义
  • bytemuck::cast_slice vs Zig 的 reinterpretSlice 的对齐要求
  • unwrap_or vs unwrap_or_else 的求值时机

这些陷阱没有任何 linter 能提前发现,只有测试套件能兜底。


十、展望:AI 重写软件的未来

Bun 的这次迁移证明了:AI 能够以人类无法企及的速度完成跨语言迁移——11 天 vs 几个月(甚至几年)

Jarred 本人早在 2026 年 5 月就预言过:

"我预计开源软件会走向完全相反的方向——未来甚至可能变成 '禁止人类贡献代码'。人类依然会负责讨论问题、决定优先级,但真正写代码、提交 PR、回复和处理反馈、完成实现的工作,最终都会由 LLM 来完成。"

Bun 的这次重写,正是这句话的第一次大规模公开演练。

速度上天的时代,信任只能自己想办法落地——而 Bun 给出答案是:对抗性审查 + 百万级测试断言 + 严格的 workflow 约束


标签:Bun|Rust|Zig|JavaScript运行时|AI代码生成|代码迁移|工程实践|性能优化|内存安全|AI Agent

Keywords:Bun|Rust|Zig|JavaScript runtime|AI code generation|code migration|engineering practice|performance optimization|memory safety|AI Agent


本文数据来源:Jarred Sumner 官方博文《Bun in Rust》(bun.sh/blog/bun-in-rust,2026-07-08);补充参考:CSDN 相关报道、36氪相关报道。

推荐文章

设置mysql支持emoji表情
2024-11-17 04:59:45 +0800 CST
介绍 Vue 3 中的新的 `emits` 选项
2024-11-17 04:45:50 +0800 CST
使用 `nohup` 命令的概述及案例
2024-11-18 08:18:36 +0800 CST
全新 Nginx 在线管理平台
2024-11-19 04:18:33 +0800 CST
程序员茄子在线接单