
1. 项目缘起为什么我要折腾 Madeira 这套跨平台兼容方案第一次看到 Madeira 这个词很多人会以为是那个葡萄牙的旅游海岛但在我们这行里它指的是一套围绕FEX-Emu、Wine、DXMT构建的跨架构兼容运行环境目标很明确让 x86-64 平台的 Windows 应用和游戏能在 ARM 设备、尤其是移动端和国产化桌面系统上跑起来。我最初接触这个方向是因为手头有一批老旧的 Windows 工具链和几款只在 x86 上验证过的游戏需要在 ARM 开发板、国产 Linux 发行版以及部分移动设备上做验证重写不现实虚拟机性能又拉胯于是就走上了这条翻译层叠翻译层的路子。这套方案解决的核心问题其实就一句话指令集不一样、系统 API 不一样、图形接口不一样怎么让一个为 Windows x86-64 编译的二进制文件在 ARM Linux甚至移动端上正常跑起来。它适合的人群也比较明确——做国产化适配的工程师、折腾 ARM 设备玩 Windows 游戏的玩家、以及需要在一台设备上同时维护多套异构应用的开发者。如果你只是想在普通 x86 电脑上跑 Windows 软件那 Wine 单独就够了没必要上 Madeira 这一整套但如果你面对的是 ARM 平台那这套组合基本是目前性价比最高的选择。我踩过的第一个坑就是把它想简单了。很多人以为装个 Wine 就完事了实际上在 ARM 上Wine 本身也是 x86-64 的二进制它自己都跑不起来必须先靠 FEX-Emu 把 x86-64 指令翻译成 ARM64 指令Wine 才能启动然后 Wine 再去加载 Windows 的 PE 文件最后图形部分交给 DXMT 把 DirectX 调用翻译成 Metal 或 Vulkan。这是一条四层链路任何一层出问题表现都是程序闪退或者黑屏排查起来非常折磨人。2. 整体架构拆解四层翻译链路到底谁在干什么2.1 FEX-Emu把 x86-64 指令现场翻译成 ARM64FEX-Emu 是整个链路的地基。它的工作方式是JIT即时编译程序运行时它把 x86-64 的机器码一块块翻译成 ARM64 的机器码翻译结果会缓存起来下次执行同一段代码就直接用缓存。这跟传统的模拟器比如 QEMU 的纯解释模式有本质区别——解释模式是一条指令一条指令地读、翻译、执行慢得离谱JIT 是成块翻译后直接跑原生指令性能能到原生的 60% 到 80%具体取决于负载类型。我实测下来纯计算密集型的任务比如编译、压缩性能损失大概在 30% 左右而图形密集型任务因为还要叠加后面几层的开销损失会更大。FEX-Emu 有个关键配置是TSOTotal Store Order模式x86 的内存模型比 ARM 强为了保证多线程程序的正确性FEX 需要模拟这种强内存序这会带来额外开销。如果你的程序是单线程的可以关掉 TSO 换性能但多线程程序千万别关否则会出现那种偶尔算错、偶尔崩溃的玄学 bug查到你怀疑人生。2.2 WineWindows API 的翻译官Wine 这层大家相对熟悉它把 Windows 的 API 调用比如CreateFile、RegOpenKey翻译成 Linux 的对应调用open、读配置文件。在 Madeira 这套方案里Wine 是以 x86-64 二进制形式存在的所以它自己也要被 FEX-Emu 翻译一遍。这就带来一个很有意思的现象Wine 内部的性能开销会被放大因为它本身就在被翻译执行。这里有个实操要点尽量用较新的 Wine 版本。老版本 Wine 在 ARM FEX 环境下有很多已知的兼容性问题尤其是涉及线程和内存管理的部分。我一般推荐 Wine 8.x 以上配合 FEX 的最新 release。另外Wine 的winecfg里那个Windows 版本设置别乱选选 Windows 10 兼容性最好选 Windows 7 有时候反而会触发一些老 API 的 bug。2.3 DXMTDirectX 到 Metal/Vulkan 的桥梁DXMT 是这套方案里相对年轻的一环它的作用是把 Windows 游戏常用的DirectX 11/12调用翻译成Metal苹果平台或VulkanLinux/安卓平台。为什么不用更成熟的 DXVK因为 DXVK 走的是 Vulkan 路线在苹果生态里 Metal 才是原生接口DXMT 在苹果设备上的效率明显更高。而在 Linux 和安卓上DXMT 也可以配置成走 Vulkan 后端。这一层的坑最多。首先是着色器编译卡顿游戏第一次运行某个场景时DXMT 要现场把 DX 的着色器翻译成目标平台的着色器这个过程可能造成几秒甚至十几秒的卡顿。解决办法是开启着色器缓存让翻译结果持久化。其次是特性支持不全不是所有 DX12 的高级特性比如光追、网格着色器都能完整翻译遇到不支持的游戏就是直接黑屏或者报错这个目前没有完美解法只能等上游更新。2.4 应用层Windows 程序与游戏最上面这层就是我们真正想跑的东西。这里要区分两类普通应用办公软件、工具类和游戏。普通应用对图形要求低主要考验 Wine 的 API 覆盖度游戏则对 DXMT 和 FEX 的性能都极其敏感。我的经验是普通应用的成功率能到 90% 以上而游戏大概只有 60% 到 70% 能跑起来能跑起来的里面又只有一半能达到可玩的帧率。3. 环境搭建实操从零把链路跑通3.1 基础依赖安装与版本选择搭建环境的第一步是确认你的系统架构和发行版。我主要在两类环境上做过国产 Linux 发行版基于 Debian 或 RPM 体系和ARM 开发板上的精简 Linux。两者的依赖安装方式略有不同但核心依赖是一致的。先装基础工具链sudo apt update sudo apt install -y build-essential cmake ninja-build git python3 pkg-config sudo apt install -y libsdl2-dev libepoxy-dev libdrm-dev libgbm-dev sudo apt install -y libasound2-dev libpulse-dev libudev-dev这些是 FEX-Emu 和 Wine 编译时的常见依赖。libsdl2-dev是给 FEX 做输入和窗口用的libepoxy-dev和libgbm-dev是图形相关libasound2-dev和libpulse-dev是音频。少装一个编译到一半报错回头补装再重编非常浪费时间所以建议一次性装齐。版本选择上我的建议是FEX-Emu 用最新的 release tagWine 用 8.0 以上的稳定版DXMT 用官方仓库的主分支。不要图省事用发行版自带的旧版本那些版本往往缺少 ARM 相关的关键补丁。特别是 FEX2023 年之后的版本在 ARM64 上的性能优化非常明显老版本跑起来会慢一大截。3.2 FEX-Emu 的编译与 RootFS 配置FEX-Emu 的编译不算复杂但 RootFS 的配置是新手最容易翻车的地方。RootFS 是一个包含 x86-64 基础库的根文件系统FEX 需要它来提供 x86-64 版本的libc、libstdc等基础库因为被翻译的程序会去链接这些库。编译步骤大致如下git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build cd build cmake -DCMAKE_BUILD_TYPERelease -DENABLE_ASSERTIONSOFF .. make -j$(nproc) sudo make install编译完成后需要生成 RootFS。FEX 官方提供了一个脚本会自动下载一个精简的 x86-64 根文件系统cd External/FEXRootFSFetcher ./FEXRootFSFetcher这个脚本会下载一个几百 MB 的压缩包并解压到~/.fex-emu/RootFS/下。注意下载过程对网络稳定性要求较高如果中断了删掉~/.fex-emu/RootFS/下的残留文件重新跑一遍不要试图续传容易损坏。RootFS 配好后可以用一个简单的 x86-64 程序测试FEXBash # 进入 FEX 环境后 uname -m # 应该输出 x86_64如果uname -m输出的是aarch64说明 RootFS 没生效检查~/.fex-emu/config.json里的RootFS路径是否正确。3.3 Wine 的交叉编译与配置在 ARM 上编译 x86-64 的 Wine需要用到交叉编译工具链。这一步是整个搭建过程中最耗时的我的一台 8 核 ARM 机器编译 Wine 花了将近两个小时。sudo apt install -y gcc-x86-64-linux-gnu g-x86-64-linux-gnu git clone https://github.com/wine-mirror/wine.git cd wine ./configure --enable-win64 --hostx86_64-linux-gnu \ --prefix/opt/wine-x86_64 \ --without-x --with-wayland make -j$(nproc) sudo make install--without-x --with-wayland这个组合是为了适配现代 Linux 桌面环境如果你用的是 X11把这两个参数去掉即可。编译完成后Wine 的可执行文件在/opt/wine-x86_64/bin/下但不能直接运行因为它是 x86-64 的必须通过 FEX 来启动FEXBash export WINEPREFIX~/.wine-madeira /opt/wine-x86_64/bin/wine64 winecfgwinecfg能弹出窗口说明 Wine 这层通了。如果报错说找不到libwine.so检查LD_LIBRARY_PATH是否包含了/opt/wine-x86_64/lib。3.4 DXMT 的集成与图形后端选择DXMT 的集成相对独立它本质上是一组 DLL需要放到 Wine 的system32目录下并在 Wine 的注册表里做相应配置。git clone https://github.com/3Shain/dxmt.git cd dxmt # 按照 README 编译产物是 d3d11.dll、dxgi.dll 等 cp build/bin/*.dll ~/.wine-madeira/drive_c/windows/system32/然后在 Wine 里设置 DLL 覆盖FEXBash export WINEPREFIX~/.wine-madeira /opt/wine-x86_64/bin/wine64 reg add \ HKEY_CURRENT_USER\Software\Wine\DllOverrides \ /v d3d11 /t REG_SZ /d native /f图形后端的选择上Linux 平台建议走 Vulkan苹果平台走 Metal。Vulkan 后端需要系统装好libvulkan1和对应的 ICD比如 Mesa 的lavapipe或者厂商驱动。我实测下来Mesa 的软件渲染后端lavapipe虽然能跑但帧率惨不忍睹只适合验证功能真要玩游戏还是得有硬件加速的 Vulkan 驱动。4. 性能调优与常见问题排查实录4.1 性能瓶颈定位先搞清楚慢在哪一层跨平台兼容方案最怕的就是整体很慢但不知道慢在哪。我的排查思路是逐层剥离先测 FEX 本身的翻译开销再测 Wine 的 API 开销最后测 DXMT 的图形开销。测 FEX 开销最简单的方法是找一个纯 x86-64 的命令行程序比如busybox的 x86-64 版本在 FEX 里跑一个计算密集的任务对比原生 ARM64 版本的耗时。如果这个比值在 1.3 到 1.5 之间说明 FEX 层正常如果超过 2那就要检查是不是 TSO 模式没配对或者 JIT 缓存没生效。Wine 层的开销比较难单独测我的土办法是跑一个只调用 Win32 API 但不涉及图形的程序看它的启动时间和运行耗时。如果启动就要好几秒那多半是 Wine 在初始化时做了太多文件系统操作可以尝试把WINEPREFIX放在 tmpfs 上减少磁盘 IO。DXMT 层的开销最直观直接看游戏帧率。如果帧率低但 CPU 占用不高说明瓶颈在 GPU 翻译层如果 CPU 占用很高那可能是 FEX 或 Wine 的问题。4.2 常见问题速查表现象可能原因排查方法解决方向程序启动即闪退RootFS 缺失或损坏检查~/.fex-emu/RootFS/是否完整重新运行 RootFSFetcherWine 报错找不到 DLLDLL 覆盖配置错误检查注册表 DllOverrides用winecfg图形界面重新配置游戏黑屏但有声音DXMT 着色器翻译失败查看 DXMT 日志换图形后端或降级 DX 版本中文显示为方块字体缺失检查 Wine 字体目录安装中文字体到 Wine 的 Fonts 目录帧率极低软件渲染检查 Vulkan ICD安装硬件加速驱动多线程程序随机崩溃TSO 模式被关闭检查 FEX 配置重新开启 TSO音频爆音或延迟音频后端不匹配检查 Wine 音频设置切换 PulseAudio/ALSA 后端这个表是我踩坑踩出来的每一条都对应至少一次深夜调试。特别是中文显示为方块这条很多人以为是编码问题其实是 Wine 的字体目录里没有中文字体把系统的中文字体复制过去就好了。4.3 几个反直觉的调优技巧第一个技巧是关掉不必要的调试输出。FEX 和 Wine 默认会输出大量日志这些日志在调试时有用但在正常运行时是纯开销。可以通过环境变量FEX_LOG_LEVELnone和WINEDEBUG-all来关闭实测能提升 5% 到 10% 的性能。第二个技巧是预编译着色器缓存。DXMT 支持把翻译好的着色器缓存到磁盘下次启动直接加载。这个缓存文件默认在~/.cache/dxmt/下记得定期备份换机器或者重装系统后直接拷过去能省掉大量首次运行的卡顿。第三个技巧是给 FEX 的 JIT 缓存单独分配大内存。FEX 的 JIT 缓存默认大小有限跑大型程序时容易频繁淘汰和重新翻译。可以在配置里把JITCacheSize调大我一般设成 512MB对内存充裕的设备来说这点开销完全值得。5. 应用场景延展这套方案还能怎么用5.1 国产化桌面环境的 Windows 应用兼容这是 Madeira 这套方案最实际的应用场景之一。很多国产 Linux 发行版需要运行一些只有 Windows 版本的行业软件比如特定的 CAD 工具、财务软件、老旧的 ERP 客户端。这些软件往往不涉及复杂的图形主要考验 Wine 的 API 覆盖度成功率很高。我在一个实际项目里用这套方案让一款十几年前的 Windows 财务软件在 ARM 国产平台上跑了起来。关键改动有两个一是把软件依赖的msvcr71.dll等老运行库手动放进 Wine 的system32二是把软件的配置文件路径从C:\Program Files映射到 Linux 的用户目录下避免权限问题。整个过程花了两天但比重新开发一套替代方案划算太多。5.2 ARM 设备上的 Windows 游戏体验游戏场景对性能要求最高但也是最有意思的。我试过在几款 ARM 开发板和移动设备上跑一些老游戏比如《魔兽争霸3》《红色警戒2》这类 DX8/DX9 时代的作品效果出乎意料地好基本能满帧运行。而 DX11 之后的游戏就比较吃力了帧率往往只有个位数到十几帧。这里有个经验优先选 DX9 及以下的老游戏。因为 DX9 的翻译链路更成熟DXMT 对 DX9 的支持也最完善。DX11 和 DX12 的游戏除非是特别简单的独立游戏否则不建议在这套方案上尝试投入产出比太低。5.3 移动端与嵌入式场景的适配思路移动端和嵌入式设备的资源更紧张内存和存储都有限所以配置上要做减法。我的做法是RootFS 用精简版只保留必要的库Wine 编译时关掉不需要的模块比如--without-cups、--without-ldapDXMT 只保留目标游戏需要的 DLL。这样一套下来整个环境的体积能控制在 1GB 以内对嵌入式设备比较友好。另外移动端的输入方式跟桌面不同触摸屏没有鼠标的精确指针很多 Windows 程序的操作逻辑会变得很难用。我的解决办法是配一个蓝牙鼠标或者用scrcpy之类的工具把画面投到电脑上操作。这个不是技术问题是使用习惯问题但确实影响体验。6. 我在这套方案上踩过的坑与个人体会说几个印象最深的坑。第一个是FEX 的 RootFS 和系统库版本冲突。有一次我在一个较新的发行版上跑 FEX结果 RootFS 里的libc版本比系统自带的还老导致一些新编译的 x86-64 程序链接失败。解决办法是手动更新 RootFS 里的基础库或者干脆用chroot把 RootFS 隔离起来。这个坑花了我整整一个下午。第二个是Wine 的注册表污染。Wine 的注册表是存在WINEPREFIX里的如果同一个 prefix 里装了太多程序注册表会变得很乱导致某些程序启动时读到错误的配置。我的建议是一个程序一个 prefix虽然占空间但能避免大量玄学问题。空间不够的话至少把游戏和办公软件分开。第三个是DXMT 的版本兼容性。DXMT 更新很快但新版本不一定兼容老游戏。我有一次升级 DXMT 后原本能跑的游戏直接黑屏了回退到旧版本才恢复。所以升级前一定要备份或者用 git 的 tag 锁定版本。最后分享一个实用的小技巧善用strace和ltrace。当程序闪退但没有任何日志时用strace跟踪系统调用往往能看到它在哪一步失败了。比如卡在openat上那就是文件找不到卡在mmap上可能是内存分配问题。这个工具虽然原始但在这种多层翻译的环境里比任何高级调试器都管用。这套 Madeira 方案本质上是在用软件的方式弥补硬件和生态的鸿沟它不完美性能有损失兼容性有盲区但在很多场景下它是唯一可行的选择。我的态度是能用就用用不了就换方案别跟它死磕。毕竟工具是为人服务的不是反过来。