Madeira总体架构:一个Mach进程如何承载整个Windows游戏栈
【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu + Wine + DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira
Madeira是一个 iPhone Windows 游戏模拟器,它把 FEX-Emu、Wine 和 DXMT 三大组件装进一个 iOS App 进程里,让未经修改的 x86/x86-64 Windows 游戏直接在手机上运行。本文将带你从"单进程"这个核心设计出发,看懂整个 Windows 游戏栈是如何被一个 Mach 进程层层承载的。
一、为什么是"单进程":iOS 的硬约束
iOS App 沙盒不允许启动独立的外部程序——你不能像桌面那样"拉起一个 wineserver.exe 进程"。于是 Madeira 做了一个关键取舍:
所有东西都活在同一个 Mach 任务里:Wine 的服务端不是进程,而是线程;游戏本身不是进程,而是跑在 pthread 上的"伪进程"。
这一条约束决定了下面所有架构决策。
二、四层软件栈:谁负责什么
Madeira 的栈分成四块,每块各管一件事(完整说明见 README.md):
| 层 | 组件 | 职责 |
|---|---|---|
| CPU 翻译 | FEX-Emu(FEX/子模块) | 运行时把游戏的 x86/x86-64 代码 JIT 成 ARM64 |
| Windows 系统 | Wine 11.4(wine/子模块) | 提供 Windows 运行时;按 ARM64EC 编译,Wine 自身原生运行,只有游戏代码被翻译 |
| 老图形 API | DXMT(dxmt/子模块) | 用 Metal 驱动 Direct3D 9/10/11 |
| 新图形 API | madeira-d3d12/ | 自研 D3D12 实现,运行时用 Apple Metal Shader Converter 把 DXIL 转成 Metal 着色器 |
32 位游戏则通过 WoW64 支持,另有 i386 编译的 Wine 模块农场(docs/WOW64.md)。
三、进程地图:线程代替进程 🧵
这是整个架构最有意思的部分,对应源码都在 app/Madeira/ 下:
3.1 wineserver 变成一条高优先级线程
在桌面上,wineserver是独立进程,客户端通过 Unix socket 找它。iOS 上,WineServerBridge.m 直接调用静态链接进来的wineserver_main(),把它跑在 pthread 里,并用pthread_attr_set_qos_class_np把 QoS 提到USER_INTERACTIVE——注释里记录了真实教训:低优先级 wineserver 曾造成典型的优先级反转,游戏线程每帧大部分时间都卡在等它的回复上(L156-L174)。
客户端与服务器之间也不再走 socket,而是通过socketpair+wineserver_inject_client_fd把文件描述符直接"注入"事件循环(见 WineProcessBridge.m)。
3.2 游戏是一条 pthread,__wine_main就是"启动游戏"
WineProcessBridge.m 中的wine_process_thread(L734)承载整个 Wine "进程":
- 准备前缀(Wine prefix,即虚拟的
C:\)与环境变量; - 确保 FEX JIT 池已初始化(
fex_initialize是启动前置条件); - 调用
extern void __wine_main(argc, argv)真正启动游戏——在桌面这是"进程创建",在这里只是函数调用。
线程局部变量(_Thread_local jmp_buf)模拟了每个"进程"的退出语义:游戏退出时 longjmp 回到自己的线程而不是杀死整个 App。
四、CPU 层:FEX-Emu JIT 与"双映射"JIT 池
iOS 有两道墙挡住了 JIT:页面大小是 16KB(不是 4KB),以及只有调试器附加时才允许写+执行内存。
FEXBridge.mm 的解法很精巧——同一个 64MB 内存池,两个视图:
- RX 视图:由调试器(StikDebug)分配的"读+执行"页;
- RW 视图:用 Mach
vm_remap把同一块物理页再映射一份"读+写"。
JIT 编译器往 RW 视图写 ARM64 机器码,CPU 从 RX 视图执行,同一物理页两边一致。mmap 钩子(fex_mmap_hook)拦截所有可执行内存申请,改从这个池子里分配;线程退出则用 longjmp 从系统调用里"逃"出来,绕开调试器会抢先拦截信号的陷阱(L208-L213)。
配套细节:__clear_cache通过sys_icache_invalidate做指令缓存失效;宿主特性按 Apple A15 描述(支持 LSE 原子、RCPC、FlagM,无 SVE)。
五、图形层:两条 Metal 路径
- D3D9/10/11:走 DXMT 的
winemetal.dll/d3d11.dll(ARM64EC 编译),unix 侧静态库libdxmt_combined.a由 App 链接。 - D3D12:madeira-d3d12/ 提供原生
d3d12.dll,运行时把 DXIL 字节码交给 Apple 的 Metal Shader Converter 转成 Metal 着色器(头文件与库在 madeira-d3d12/third_party/metal-shader-converter/)。
音频和视频则经 FFmpeg + VideoToolbox / AudioToolbox 打通(docs/MEDIA.md)。
六、"Windows 装在哪":前缀与 DLL 农场
虚拟 C 盘由打包的 prefix-template.tar.gz 在首次启动时解出(PrefixExtractor.c),wineserver 启动前会先播种注册表模板(17,000+ 键),避免生成空注册表。
所有 Windows 模块是预先编译好的"DLL 农场",按架构分目录:
| 目录 | 内容 |
|---|---|
| app/Madeira/arm64ec-windows/ | 64 位主战场:Wine/DXMT/D3D12 的 ARM64EC DLL(ntdll.dll、winemetal.dll、madeira_d3d12.dll…) |
| app/Madeira/aarch64-windows/ | 纯 ARM64 模块,含xtajit.dll(FEX 的 WoW64 翻译器) |
| app/Madeira/i386-windows/ | 32 位 Wine 模块(WoW64 路径) |
七、延伸阅读 📚
- 构建全景(各组件怎么编译、谁链接谁):docs/BUILDING.md
- 32 位游戏 WoW64 原理:docs/WOW64.md
- Steam 客户端(Madeira Dock):docs/MADEIRA_DOCK.md
- 测试程序(x86/x64/WoW64 探针):tests/
一句话总结
Madeira 的本质是用 Mach 原语把三个本应独立的世界压进一个任务:vm_remap双映射解决 JIT,pthread 替代 wineserver 进程,__wine_main函数调用替代"创建进程"。理解了这个单进程模型,就理解了整个项目在 iOS 上跑 Windows 游戏的全部地基。
【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu + Wine + DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考