LinuxRunner 的 Asbestos 解释器内幕:把 ARM64 指令编译成 iPhone 上最快的执行路径

【免费下载链接】LinuxRunner

Run ARM64 Linux userspace and desktop applications on iPhone and iPad.

项目地址: https://gitcode.com/gh_mirrors/li/LinuxRunner
点击查看 免费下载

LinuxRunner 让 iPhone 和 iPad 直接运行 ARM64 Linux 用户态程序与桌面应用,它的核心是一台名为 Asbestos 的 ARM64 指令解释器。下面拆解 Asbestos 如何把一条条 ARM64 指令,编译成 iPhone 上最快的执行路径——从默认的 Gadget 线程代码,到可选的原生 JIT 与 AOT 预编译,并给出实测性能与源码导读。

LinuxRunner 在 iPhone 上运行的 Linux 桌面环境

一、Asbestos 是什么:一个不启动内核的 ARM64 引擎

先澄清一个常见误解:LinuxRunner 不启动 Linux 内核、不用硬件虚拟化、也不是整机模拟器。它只提供 Linux 用户态兼容层,而驱动这些程序跑起来的"心脏"就是 Asbestos 解释器。

用官方架构文档的原话说:

The Asbestos interpreter decodes supported instructions into cached sequences of precompiled host routines (gadgets). ——docs/ARCHITECTURE.md

翻译成大白话:Asbestos 把 Linux 程序的每条 ARM64 指令,解码成一段段预先编译好的 host 例程(叫 gadget)并缓存下来。因为 guest 和 host 都是 ARM64,gadget 用的就是本机原生指令,所以速度接近"跑自己",而不是模拟一个 CPU。

每个地址空间对应一个 struct asbestos,它维护:

  • 已翻译的基本块(fiber_block)哈希表;
  • 软件 TLB 的失效代际(invalidate_gen、code_seen 位图),保证内存拷贝-on-write 时缓存代码能及时作废;
  • jetsam 回收链表 + RCU 风格的原子计数(jit_active_threads),在没有锁的情况下安全地释放旧块。

💡 记住三个名词就够了:基本块(一段没有分支的连续指令)、gadget(一条/一小段指令的原生实现)、代码流(一串 gadget 指针,CPU 顺着它一路 br 跳过去执行)。

二、三步看懂执行路径:从 guest 指令到 host 代码

把整个流程压缩成三步,你就抓住了 Asbestos 的骨架:

阶段做什么关键文件
① 解码遇到新基本块时,把 guest 指令逐条解码成 gadget 指针流gen.c
② 缓存把这段"代码流"存进 fiber_block.code[] 并按地址入哈希表asbestos.c
③ 执行fiber_enter 进入代码流,gadget 用 br x8 一条跳一条,直到分支/系统调用才退出回主循环entry.S

① 解码发生在 asbestos/guest-arm64/gen.c(约 7800 行)。它读入 32 位定长的 ARM64 指令,边解码边做指令融合(比如把 CMP+B.cond、ADRP+ADD、LDR+CBZ 这种高频组合合成一条更快的 gadget),并做窥孔优化。一个典型的基本块平均只有 4 条左右指令,对应约 16 个 gadget 参数(见 asbestos.h 里的 FIBER_BLOCK_INITIAL_CAPACITY 16)。

③ 执行的关键在 entry.S。gadget 之间靠一个 gret 宏接力:

  • _cpu(x1)指向 CPU 状态,_tlb(x2)指向软件 TLB,_pc(x28)指向当前代码流位置;
  • gret 从 _pc 取出下一个 gadget 指针,然后 br x8 直接跳过去——不经过 C 主循环,这就是"线程代码"(threaded code)的精髓。

只有碰到分支、系统调用或中断,代码流才会 fiber_exit 退出,回到 C 主循环 cpu_run_to_interrupt 做调度、处理 INT_SYSCALL 等,再 fiber_enter 重新进入。

在 LinuxRunner 里运行的 Blender 3D 视口,验证引擎能扛住重型应用

三、块链接与 ret_cache:让热路径少绕远

光靠"一条跳一条"还不够快,Asbestos 在两点上把热路径进一步压短:

块链接(Block Chaining)。 当基本块 A 跳转的下一个块 B 恰好是它在 guest 内存里的紧邻块、且同页时,A 可以直接"改指针"指向 B 的代码流,省去回主循环。entry.S 里的 fiber_ret_chain 专门走这条快路:源码注释提到,相邻同页的前向跳转约占链式转移的 69%,这部分可以直接跳过内存一致性检查继续执行。

返回地址预测(ret_cache)。 函数返回是最高频的分支之一。frame.h 为每个线程维护一张 ret_cache[4096] 表,把"返回地址 → 调用点 gadget 参数"缓存起来,ret 时命中就一步到位,不用重新查表。

这两步都是纯 C/汇编、零额外内存映射,所以即使不开 JIT,解释器本体也已经相当能打。

四、可选 JIT:把最热的指令块翻译成原生机器码

在 gadget 流之上,Asbestos 还叠了一层可选的原生 JIT(jit.c,约 3700 行)。设计写在 jit.h 顶部注释里,一句话概括:

A layer on top of the threaded-code blocks: after a block's gadget stream is generated, runs of supported guest instructions are translated to host code in a MAP_JIT region, and the first gadget pointer of each run is replaced by a pointer to that code.

也就是说:

  • 仍然先按 gadget 流生成,再把连续的、受支持的指令段翻译成真正的 host 机器码,写进一块 120 MB 的 MAP_JIT 区域;
  • 翻译时把最热的 guest 寄存器 pin 到固定 host 寄存器上,跨原生代码保持不落地;
  • 访存指令内联 TLB 查找,miss 时才回退到该段的 gadget;
  • 块与块之间通过 jit_link / jit_unlink 打直接分支,让控制流尽量留在原生代码里。

JIT 是可选的。 默认构建(LINUXRUNNER_JIT=NO)只有解释器,不需要可执行内存权限或调试器;带 JIT 的 Build 900 IPA 里则包含 JIT 代码。在 iOS 真机上,JIT 能否激活取决于系统是否放行可执行内存(通常需调试器附加),状态可通过终端里的 /proc/ish/jit 查看,详情见 docs/engine/IOS_JIT.md。模拟器实测一次运行就翻译了 16571 个块并稳定跑在 mode: JIT。

🔧 想调参?环境变量如 ISH_JIT_STATS=1、ISH_JIT_DUMP=<file>、ISH_JIT_PIN=0 等都能在 jit.h 顶部查到。

LinuxRunner 中通过 Metal GPU 加速的 Hyprland 桌面会话

五、AOT 预编译:连可执行内存都不映射

JIT 还需要运行时生成机器码,AOT 则更"静态":把 JIT 的翻译结果提前录制成汇编,直接链进目标程序。

  • aot.h 定义了镜像结构:aot_module(带 build_id、sha256 校验)、aot_trans(每个块的原生码段 + 块间直接链接)、aot_loop(自环块的寄存器提升)。
  • 用 ISH_JIT_RECORD 录一次,tools/jit_aot 把它铺成 .S;一个 constructor 在加载时调用 ish_aot_register() 即可。
  • 关键点:一个 -Djit_emit=false 的构建只跑 AOT 镜像,从不映射可执行内存——对"完全不允许 JIT 的场景"尤其友好。

三条路径的取舍,可以这样记:

执行路径是否运行时生成机器码需要可执行内存适合场景
Gadget 解释器(默认)否(只生成 gadget 指针流)否通用、无权限依赖
原生 JIT(可选)是是(iOS 需系统放行)热代码块、追求极限
AOT 预编译否(离线录制)否不允许 JIT、冷启动最快

六、实测性能:解释器比原生慢多少?

性能数据来自 docs/engine/BENCHMARKS.md。需要强调:测量在 Apple M1 的 MacBook Air 上完成,不是 iPhone;但引擎代码在 iOS 上完全相同,相对差距可迁移。

interp_bench 微基准(相对原生的剩余减速倍数,越低越好):

工作负载相对原生说明
syscall1.3×系统调用开销已压得很小
alu4.5×整数运算
branch7.5×分支密集
call17.3×函数调用
string24.7×memcpy 主导的字符串

宏基准里,git hash-object、sha256sum、gzip、xz、sort、Python 启动等真实 Debian 程序都能跑,且新旧引擎输出校验和逐位一致(正确性护栏)。文档还诚实记录了被否决的优化——比如"融合 LDP 前/后索引"在调用密集型代码上反而慢了约 8%,没有采用。

在 LinuxRunner 中运行的 Firefox 浏览器页面

七、源码导读:从哪几个文件开始读

想深入 Asbestos,建议按下面这条"主线"读(文件均为仓库相对路径):

  1. asbestos/asbestos.h —— 先看 struct asbestos 和 struct fiber_block,理解"块"长什么样。
  2. asbestos/asbestos.c —— 主调度循环 cpu_run_to_interrupt 与编译入口 fiber_block_compile。
  3. asbestos/guest-arm64/gen.c —— 指令解码与 gadget 生成(融合/窥孔都在这)。
  4. asbestos/guest-arm64/gadgets-aarch64/entry.S —— fiber_enter / fiber_ret_chain / fiber_exit,host↔guest 的边界。
  5. asbestos/guest-arm64/jit.c —— 原生 JIT:代码区域、内联 TLB、块间直接链接。
  6. asbestos/guest-arm64/aot.h —— AOT 镜像的布局与注册。

配套文档:整体架构看 docs/ARCHITECTURE.md,iOS 上的 JIT 激活细节看 docs/engine/IOS_JIT.md,性能方法论与数据看 docs/engine/BENCHMARKS.md。

写在最后

Asbestos 的高明之处在于分层取舍:默认的 Gadget 线程代码零权限依赖、开箱即跑;可选的 JIT 在系统放行时把热块榨到极限;AOT 则给"绝对不能 JIT"的场景留了一条快路。三者共用同一套块缓存、块链接和软件 TLB,所以你看到的"iPhone 跑 Linux 桌面",本质是这台 ARM64 指令引擎把每一层开销都压到最小后的结果。

【免费下载链接】LinuxRunner

Run ARM64 Linux userspace and desktop applications on iPhone and iPad.

项目地址: https://gitcode.com/gh_mirrors/li/LinuxRunner
点击查看 免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值