在 macOS 上运行 Linux 容器——无需虚拟机。
dd 在 Apple Silicon macOS 上原生运行 Linux 容器,无需虚拟机。它底层没有 Linux 内核,也没有虚拟机监控程序:一个 JIT 会转换容器的代码,并在用户空间内处理其 Linux 系统调用(gVisor / PRoot 一脉)。JIT 就是 客户机的 Linux 内核——命名空间、cgroup、overlay 镜像层和网络都以用户空间状态维护。它实现了 Docker Engine API,因此普通的 docker CLI 即可驱动它。
容器的计算以原生 Apple Silicon 指令运行;只有其系统调用是解释执行的。无需启动虚拟机,无需在虚拟机中运行守护进程,没有虚拟化开销。
make jit # 编译 + codesign JIT
DD_IMAGES=/path/to/images cargo run -p dd-daemon # 启动守护进程
export DOCKER_HOST=unix://$PWD/dd.sock
docker run -p 8080:80 -m 256m alpine sh -c 'echo hi from $(hostname)'
DOCKER_HOST 指向其套接字,你现有的 docker run / ps / images / build 命令即可无变化地工作。jit86) 运行,该 JIT 解码 x86 指令、合成其标志、并将 SSE/x87 降级到 NEON(glibc 二进制可运行);以及 macOS arm64 客户机 (ddcli mac)——其中任何一个都无需虚拟机。.wh. whiteout、合并 getdents)、无 TOCTOU 的路径狱 VFS、PID / UTS / USER 命名空间、带有 -p 端口映射的私有回环 netns,以及 cgroup 内存 + pid 限制(达到上限时 OOM)。dd CLI 会安装一个每用户后台守护进程和一个 docker context——所有内容都在 $HOME 下,无需 sudo。在 Mac 上运行 Linux 容器的所有其他方式——Docker Desktop、Colima、Rancher、OrbStack——都在虚拟机监控程序下启动一个 Linux 虚拟机,并在其内部运行守护进程。这个虚拟机是你全天都要付出的代价。dd 将其删除:容器是一个普通的 macOS 进程,其系统调用恰好由用户空间的 Linux 内核处理。
| dd — 用户空间内核(JIT) | 基于虚拟机的 Docker(Desktop / Colima / …) | |
|---|---|---|
| 底层模型 | JIT 在用户空间处理 Linux 系统调用(gVisor 一脉) | 虚拟机监控程序下运行完整 Linux 内核的虚拟机 |
| 空闲时驻留内存 | 无 — 每个容器一个,退出时释放 | 虚拟机预留数 GB 内存,始终运行 |
| 启动方式 | 进程启动 — 无需启动虚拟机 | 先启动 Linux 虚拟机 + 虚拟机内守护进程 |
| 绑定挂载 / 文件 I/O | 直接主机文件系统,通过路径狱实现 | 跨虚拟机边界的 virtiofs/gRPC-FUSE 桥接 |
| 端口映射 | 直接到主机套接字 | 通过虚拟机的 NAT/转发层 |
| 电池 / 后台开销 | 无容器运行时无任何开销 | 虚拟机空闲并消耗电池 |
| 交付和修补体积 | 无 Linux 内核 — 无需追踪 CVE | 交付、修补和追踪整个 Linux 内核 |
| 可观测性 | 一个普通的 macOS 进程 — 可使用 sample、debug、Activity Monitor | 一个不透明的虚拟机;工作负载对主机工具不可见 |
其优势是结构性的:客户机的计算以原生 Apple Silicon 指令运行(热路径上没有硬件虚拟化层),而臭名昭著的 Docker Desktop 文件共享瓶颈——macOS 与虚拟机之间的 virtiofs/FUSE 桥接——根本不存在,因为 dd 的 VFS 就是 主机文件系统,背后是一个路径狱。
诚实的权衡: 用户空间内核只在其实现的系统调用范围内完整,今天默认情况下客户机在一个进程中运行——速度很快,对于你信任的代码(你的开发环境、CI、你自己的工具)是正确的选择。对于不可信的代码,现在有了一个可选的 sentry 拆分 (
DDJIT_UNTRUSTED):客户机在一个默认拒绝的 Seatbelt 沙箱中运行,没有主机文件系统/网络权限,而一个可信的 sentry 进程拥有实际资源,并通过共享内存环提供服务——即 gVisor 形态。这还处于早期阶段(核心文件系统调用——read/write/open/close/lseek——现已转发;套接字/exec/fork 正在实现),因此对于完全恶意的代码,虚拟机仍然提供更小的攻击面。
同一静态 Linux 二进制文件,在 Apple M5 Pro(macOS 26.3)上以两种方式运行:在 Linux 虚拟机内部(基于虚拟机的 Docker 运行容器的方式) vs. 通过 dd 的 JIT 在宿主机上运行且无虚拟机。7 次运行的中位数(make bench)。时间越短越好;“dd vs VM” > 1× 表示 dd 更快。dd 一行甚至还要支付一个小的跨进程桥接开销,而真实应用则不需要——因此这些结果是保守的。
x86-64 容器 — dd vs 虚拟机模拟(qemu-user;在 Apple Silicon 上运行 x86 意味着双向都要转换)。dd 的 JIT 在 10 个工作负载中的 9 个上优于 qemu,尤其在浮点运算上表现显著:
| 工作负载 | 虚拟机 (qemu) | dd (无 VM) | dd vs VM |
|---|---|---|---|
| float n-body | 5.39s | 0.23s | 快 24 倍 |
| mandelbrot | 7.81s | 0.83s | 快 9.4 倍 |
| matmul | 8.21s | 1.37s | 快 6.0 倍 |
| SQLite(60 万行) | 2.99s | 1.01s | 快 3.0 倍 |
| qsort | 3.91s | 1.68s | 快 2.3 倍 |
| memcpy | 2.40s | 1.10s | 快 2.2 倍 |
| text-scan (wc/grep) | 1.42s | 1.11s | 快 1.3 倍 |
| int sieve | 1.31s | 1.04s | 快 1.25 倍 |
| SHA-256 | 2.72s | 2.44s | 快 1.1 倍 |
| base64 | 4.28s | 5.39s | 0.79×(慢 1.26 倍) |
aarch64 容器 — dd vs 原生虚拟机(虚拟机以完全原生速度运行 arm64——最难的标杆):
| 工作负载 | 虚拟机 (原生) | dd (无 VM) | dd vs VM |
|---|---|---|---|
| int sieve | 0.75s | 0.48s | 快 1.58 倍 |
| mandelbrot | 0.79s | 0.77s | 快 1.03 倍 |
| matmul | 0.66s | 0.66s | ~持平 |
| memcpy | 0.55s | 0.56s | ~持平 |
| base64 | 0.68s | 0.68s | ~持平 |
| float n-body | 0.17s | 0.17s | ~持平 |
| SHA-256 | 0.80s | 0.82s | ~持平 |
| qsort | 0.83s | 1.10s | 慢 1.33 倍 |
| text-scan (wc/grep) | 0.51s | 0.68s | 慢 1.35 倍 |
| SQLite(60 万行) | 0.36s | 0.62s | 慢 1.71 倍 |
dd 以原生速度运行 arm64 计算——在 int sieve 和 mandelbrot 上领先,在 SHA-256、matmul、memcpy、n-body 和 base64 上与原生持平。剩余的差距在于间接分支 / 系统调用密集型工作负载——qsort(约 1.3 倍)、text-scan(约 1.35 倍)和 SQLite(约 1.5 倍)——最近的优化(§B-off + 偷用 x16/x17 已使 SQLite 从约 1.9 倍降至约 1.5 倍)大幅缩小了差距。缩小其余差距(VDBE 调度)是当前的活跃前沿;请参阅 docs/design/arm-sqlite-parity.md。(每个工作负载的大小都确保运行时间 ≥0.45s,因此测试框架小的每次运行桥接开销可忽略不计。)
这些是计算微基准测试——它们甚至没有体现 dd 的结构性优势(无需启动虚拟机、无驻留内存、直接主机文件系统 I/O)。所有数字均已测量,7 次运行中位数。可重现:make bench。
目标是在每个基准测试上都超越虚拟机。 dd 已经赢得了上述所有 x86-64 工作负载,并在原生 arm64 上持平或更优;在仍有差距的地方——系统调用/分配密集型的 arm64 SQLite,以及进一步挖掘 x86 翻译器的潜力——正是优化的前沿(tier-2 跟踪优化器和 jit86 性能工作)。在所有地方达到持平或更优是标杆。
dd 通过在用户空间作为客户机的内核来运行 Linux 容器。一个 JIT 会转换客户机的机器代码并捕获每条系统调用指令;捕获处理程序——dd-jit/src/runtime/os/linux/ 中的 service()——就是 Linux 系统调用 ABI,针对 macOS 主机实现。
ld.so 动态加载)并构建初始栈。# 1. 启动守护进程,将 docker 指向它
make jit
DD_IMAGES=/path/to/images cargo run -p dd-daemon
export DOCKER_HOST=unix://$PWD/dd.sock
# 2. 这就是 Docker
docker run -p 8080:80 -m 256m alpine sh -c 'echo hi from $(hostname)'
docker ps
docker images
docker run --rm -it ubuntu bash
# 3. 或者通过已安装的桌面应用(每用户,无需 root)
dd install # LaunchAgent + docker context
dd app # 打开 GUI
docker --context dd run alpine echo hi
dd 针对 Apple Silicon macOS(arm64,macOS 12+)。JIT 需要 Xcode 命令行工具(clang + codesign)。
从 发布页面 获取最新的 .dmg 文件,打开它,将 dd 拖到 Applications 文件夹。然后在终端中:
dd install # 创建 ~/.dd 目录 + 每用户 LaunchAgent + `docker context create dd`
dd app # 打开 GUI
dd doctor # 检查套接字 / 代理 / 上下文 / 应用隔离
Gatekeeper: DMG 未签名(临时签名)。首次启动时,右键点击应用 → 打开,或运行
xattr -dr com.apple.quarantine /Applications/dd-app.app(dd doctor会检测到这一点并打印修复方法)。
xcode-select --install # clang + codesign
# 安装 Rust(稳定版)和 Nix(用于 GTK4 开发 shell)
git clone https://github.com/ricccrd/dd && cd dd
make app # 构建 + 组装 & 临时签名 target/dd-app.app
make dmg # 生成 -> target/dist/dd-<ver>-<arch>.dmg
make install # 复制到 /Applications 并运行 `dd install`