一张桌面级旗舰显卡,一根 Thunderbolt 线,一台 ARM 架构的轻薄笔记本——这三样东西拼在一起,听起来像是硬件发烧友的周末恶作剧。但开发者 ScottJG 真的把 NVIDIA RTX 5090 通过 Thunderbolt 掘到了 M4 MacBook Air 上,而且不只是"点亮屏幕",他一路从 PCI passthrough、BAR 映射、DART DMA 到内核 kprobes 打补丁,把 macOS 上跑外部 NVIDIA GPU 的每一道锁都撬了一遍。
结果怎样?值得每个对 eGPU 或 Apple Silicon 外设扩展感兴趣的人细看。
为什么这件事难到几乎没人做
macOS 从 2021 年起就不再为第三方 GPU 提供官方驱动支持。Apple Silicon 更是自带一套封闭的 IOMMU 体系——DART(Device Address Resolution Table),它对外设 DMA 访问内存的地址映射做了严格限制。换句话说,即使你物理上把 RTX 5090 接到了 Thunderbolt 端口,GPU 发出的内存读写请求也会被 DART 拦在门外。
再加上 NVIDIA 的 macOS 驱动早已停更,现有驱动对 Apple Silicon 的 PCI 架构一无所知。这不是插上就能跑的场景,而是每一层都需要手动打通:
- PCI 架构层:macOS 的 PCI 子系统对外部 GPU 没有 passthrough 路径
- 地址映射层:DART 不允许外设直接用物理地址访问系统内存
- 驱动层:NVIDIA 驱动在初始化阶段就会因 BAR 大小、地址范围等问题崩溃
ScottJG 的工作就是逐层拆解这些障碍。
关键工程突破:从底层一个个啃
PCI Passthrough 与 BAR 映射
RTX 5090 的 BAR(Base Address Register)空间远超 Thunderbolt 常规映射窗口。标准 Thunderbolt eGPU enclosure 只暴露 32-bit PCI BAR 窗口,而现代 NVIDIA GPU 需要 Resizable BAR 支持,否则性能会大幅折损。
ScottJG 在 macOS 上手动实现了 PCI passthrough,重新映射 BAR 空间,让 GPU 的完整地址范围能被主机端看到。这涉及直接操作 macOS IOKit 的 PCI 设备注册流程——绕过系统默认的"发现即忽略"策略。
DART 限制与自定义 DMA
Apple Silicon 的 DART 对每个外设端点的 DMA 地址翻译做了隔离。NVIDIA GPU 驱动发出的 DMA 请求用的是自己理解的地址,但 DART 要求所有地址必须经过其翻译表才允许访问物理内存。
解决方案是构建一套自定义 DMA 映射层:在 GPU 驱动发出 DMA 请求之前,将目标地址通过 DART 翻译表预映射,确保每次内存访问都能通过 DART 校验。这本质上是在用户态/内核态之间插入了一个地址翻译中间层。
kprobes 打补丁 NVIDIA 驱动
NVIDIA 的闭源驱动在初始化时会执行一系列 Apple Silicon 不支持的检查路径。ScottJG 用 Linux 风格的 kprobes 技术在 macOS 内核中动态拦截驱动的关键函数调用,跳过或替换那些会导致 panic 的分支。
这意味着他没有修改驱动二进制本身,而是在运行时动态修改驱动的执行流——一种非常硬核的内核级 hotpatch。
实测结果:能跑,但代价不小
测试结果显示,RTX 5090 在 M4 MacBook Air 上确实可以运行游戏,但性能远低于同样显卡在原生 x86 平台上的表现。瓶颈来自多个层面:
- Thunderbolt 带宽瓶颈:Thunderbolt 4 的最大带宽约 40 Gbps(约 4 GB/s 双向),而 RTX 5090 在 x16 PCIe 5.0 下的理论带宽是 64 GB/s。带宽缩水到约 1/16,对高帧率游戏是致命限制
- DMA 翻译开销:每次 GPU 与系统内存的数据交换都要经过自定义 DMA 映射层,增加了延迟
- 驱动兼容性残缺:kprobes 补丁只能跳过问题路径,无法补全 Apple Silicon 上缺失的完整 GPU 管线支持
实际游戏帧率大约只有同显卡在 Windows 桌面平台上的 30%-50%,且部分游戏会出现纹理加载异常或崩溃。
动手环节:在 macOS 上探测 Thunderbolt PCI 设备
如果你也想在 Apple Silicon Mac 上探索外部 PCI 设备的可见性,不需要 RTX 5090,任何 Thunderbolt 外设都可以用来验证 PCI 架构的底层状态。以下命令可以直接在终端运行:
# 1. 查看所有已注册的 PCI 设备,确认 Thunderbolt 外设是否被系统识别
ioreg -l -w0 | grep -i "class IOPCIDevice"
# 2. 列出当前 Thunderbolt 拓扑,查看连接的设备及其域/端口信息
system_profiler SPThunderboltDataType
# 3. 更底层:直接读取 PCI 配置空间(需要 sudo)
# 先找到设备的 PCI 位置码,格式为 bus:device:function
# 例如 0000:04:00.0,替换为你从 ioreg 中找到的实际值
sudo pciconf -l -v # FreeBSD 风格,macOS 上可用
# 4. 用 ioreg 提取某个 PCI 设备的详细属性(替换 <device-name> 为实际名)
ioreg -r -n <device-name> -w0 | grep -E "(BAR|assigned-addresses|reg)"
# 5. 检查 DART 映射状态——查看 IOMMU 相关的 IOKit 类
ioreg -l -w0 | grep -i "class AppleDART"
如果你有 eGPU enclosure 和一张支持的 AMD 显卡(macOS 对 AMD eGPU 有残留支持),可以进一步尝试:
# 查看系统是否为外部 GPU 加载了驱动
kextstat | grep -i -E "(amd|radeon|nvidia)"
# 监控 GPU 相关的内核消息
log show --predicate 'subsystem == "com.apple.gpu"' --last 5m --style compact
注意:macOS 从 Ventura 开始大量移除第三方 GPU 驱动扩展(kext),上述
kextstat命令在新系统上可能返回空结果。Apple Silicon 上的 GPU 驱动已全面转向 DriverKit 体系,传统 kext 路径基本关闭。这正是 ScottJG 不得不使用 kprobes 做运行时补丁的原因。
一个最小化的 kprobe 补丁思路(概念示例)
以下伪代码展示了 kprobes 在 macOS 内核中拦截函数的基本思路。这不是可直接运行的代码——macOS 内核不支持标准 Linux kprobes API,ScottJG 实际用的是基于 XNU 内核调试设施的定制实现——但逻辑结构值得理解:
/*
* 概念性示例:kprobe 风格的函数拦截
* 实际实现需要基于 XNU 内核的 fbt (Function Boundary Tracing)
* 或 DTrace probe 机制,此处仅展示逻辑结构
*
* 假设目标:拦截 NVIDIA 驱动中的 nv_init_hardware()
* 当检测到 Apple Silicon (ARM64) 时,跳过不兼容的初始化分支
*/
#include <sys/systm.h>
#include <mach/mach_types.h>
/* 目标函数符号——需要从驱动二进制中提取 */
#define TARGET_FUNC_SYM "_nv_init_hardware"
/* probe handler:在目标函数入口处执行 */
static int
kprobe_nv_init_handler(struct kprobe *kp, struct pt_regs *regs)
{
/* 检查当前架构 */
#ifdef __arm64__
/*
* 在 ARM64 上,NVIDIA 驱动的某些初始化路径
* 会尝试访问不存在的 x86 I/O 穗口,导致 panic。
* 此处修改返回值,让驱动跳过该分支。
*/
regs->retval = 0; /* 强制返回成功,跳过后续不兼容检查 */
printf("[kprobe] nv_init_hardware: ARM64 bypass applied\n");
#endif
return 0;
}
/* 注册 probe(概念性,实际需用 XNU fbt/DTrace) */
static int
register_kprobe_patch(void)
{
struct kprobe kp = {
.symbol_name = TARGET_FUNC_SYM,
.pre_handler = kprobe_nv_init_handler,
};
/* 实际注册机制取决于 XNU 内核版本和调试接口 */
return register_kprobe(&kp); /* 概念调用 */
}
这段代码的核心思想是:在驱动函数的入口点插入检查逻辑,根据运行平台修改执行流。真实实现中,ScottJG 需要处理 XNU 内核的符号解析、内存保护绕过、以及补丁的持久化——每一项都是独立的内核工程难题。
评估与取舍:这条路值得走吗?
从实验角度看,这项工作价值极高——它揭示了 Apple Silicon 外设扩展的真实边界,也为未来可能的开放 GPU 支持提供了技术参考。但从实用角度看,有几条明确的取舍线:
| 维度 | 判断 |
|---|---|
| 游戏性能 | 不值得。Thunderbolt 带宽瓶颈是硬限制,即使未来带宽翻倍也远不及 PCIe x16 |
| 计算加速(CUDA/ML) | 理论可行但极度折腾。DART DMA 翻译开销 + 驱动残缺 = 不稳定且低效 |
| 学习内核工程 | 极高价值。PCI passthrough、DART 映射、kprobes 补丁每一步都是硬核知识点 |
| 长期可行性 | 低。每个 macOS 大版本更新都可能破坏补丁,NVIDIA 驱动不会回头支持 Apple Silicon |
如果你只是想让 Mac 跑 NVIDIA GPU 的计算任务,更务实的路径是:
- 用远程 Linux 服务器挂 RTX 5090,SSH + VS Code Remote 开发
- 用云 GPU(Lambda、Vast.ai)按需租用
- 如果必须本地,买一台 x86 小主机做 eGPU 宿主,Mac 通过网络调度
如果你对内核级外设打通感兴趣,ScottJG 的项目是一个绝佳起点。他的底层工作覆盖了从硬件拓扑到内核执行流的完整链路,每一步都有可复用的思路——尤其是 DART DMA 翆译和 kprobes 运行时补丁这两块,对任何想在 Apple Silicon 上做非标准外设接入的人都有参考价值。
Thunderbolt 的物理连接能力远大于 macOS 当前愿意暴露的软件接口。ScottJG 的工作证明:锁是存在的,但每一把都有对应的撬法——只是撬完之后,你得到的是一间通风良好但屋顶漏雨的房间。