Madeira 跨架构运行方案:在 ARM 设备上跑 x86-64 Windows 程序
1. 从“Madeira”这个名字说起一个被低估的跨架构运行方案第一次看到“Madeira”这个词大多数人会联想到葡萄牙的那个海岛或者一种加强型葡萄酒。但在跨架构运行这个圈子里Madeira 指向的是另一件事让 x86-64 的二进制程序在 ARM 设备上跑起来而且不是靠虚拟机那种笨重的全系统模拟而是走用户态翻译的路子。配合 FEX-Emu 做指令翻译、Wine 做 Windows API 转译、DXMT 做图形层对接这套组合拳打下来能在 ARM 设备上跑起相当一部分 Windows 游戏和桌面应用。我接触这套方案是因为手头有一台 ARM 架构的设备想跑一些只有 Windows 版本的老游戏和工具软件。纯原生方案走不通虚拟机性能损耗又太大于是开始研究用户态翻译这条路。Madeira 本质上是一个整合层它把 FEX-Emu、Wine、DXMT 这几个组件串起来让整个链路能协同工作。关键词里出现的 FEX-Emu、Wine、DXMT、x86-64 这几个词基本勾勒出了这套方案的技术骨架。这篇文章适合谁看如果你手上有 ARM 设备想跑 x86-64 的 Windows 程序又不想忍受虚拟机的性能损失那这套方案值得研究。如果你只是好奇跨架构运行是怎么实现的文章里的原理拆解部分也能给你一个清晰的认知框架。我会从架构设计讲到实操配置再到踩坑经验尽量把每个环节的“为什么”说清楚。2. FEX-Emu 在整条链路里到底干了什么活2.1 用户态翻译和全系统模拟的本质区别要理解 FEX-Emu 的定位先得搞清楚用户态翻译和全系统模拟的区别。全系统模拟比如 QEMU 的 system mode是把整个硬件环境都虚拟出来包括 CPU 指令集、内存管理、设备接口 guest 操作系统完全感知不到自己跑在模拟器上。这种方式的代价是每条指令都要经过翻译层性能损耗通常在 5 到 10 倍甚至更多。用户态翻译比如 QEMU 的 user mode、FEX-Emu走的是另一条路只翻译用户态指令系统调用直接透传给宿主内核。这意味着内存管理、进程调度、文件系统这些重活都由宿主内核原生处理翻译层只需要关注指令集的转换。性能损耗能压到 2 到 3 倍某些场景下甚至更低。FEX-Emu 就是典型的用户态翻译器它专注于 x86-64 到 ARM64 的指令转换。它的核心是一个 JIT 编译器把 x86-64 指令块动态翻译成 ARM64 指令块然后缓存起来复用。第一次执行某段代码时有翻译开销后续命中缓存就快很多。2.2 FEX-Emu 的 JIT 缓存机制与性能特征FEX-Emu 的 JIT 缓存策略值得单独拿出来说。它采用的是块级翻译也就是以基本块为单位进行翻译和缓存。基本块是指一段没有分支跳转的连续指令序列翻译一次之后可以反复执行。缓存命中率越高性能越好。实际使用中我发现几个影响性能的关键因素。第一是缓存大小FEX-Emu 默认的缓存容量有限跑大型程序时容易频繁淘汰旧块导致重复翻译。可以通过配置文件调大缓存上限代价是占用更多内存。第二是翻译线程数多线程翻译能加快冷启动速度但线程太多会争抢 CPU 资源。第三是指令集扩展的支持程度某些 x86-64 的扩展指令比如 AVX 系列翻译开销较大如果程序重度依赖这些指令性能会明显下降。提示FEX-Emu 的配置文件通常位于~/.fex-emu/Config.json调整MaxInstCacheSize和NumTranslationThreads这两个参数对性能影响最直接。建议先从默认值开始用性能分析工具定位瓶颈后再针对性调整。2.3 为什么选 FEX-Emu 而不是其他翻译方案市面上做 x86-64 到 ARM64 翻译的方案不止 FEX-Emu 一家还有 Box64、QEMU user mode 等。我选 FEX-Emu 的理由有几个。第一是它对 x86-64 指令集的覆盖比较完整尤其是对较新指令扩展的支持这在跑现代游戏和生产力软件时很关键。第二是它的 JIT 优化做得比较激进热代码路径的执行效率明显优于纯解释执行的方案。第三是它和 Wine 的集成度较好社区里有现成的整合方案可以参考。Box64 的优势在于轻量启动快适合跑一些小工具。但遇到复杂程序时FEX-Emu 的翻译质量和缓存策略更占优势。QEMU user mode 的兼容性最广但性能通常不如 FEX-Emu。所以如果你的目标是跑游戏或者重型应用FEX-Emu 是更合适的选择。3. Wine 层把 Windows API 调用翻译成宿主系统调用3.1 Wine 不是模拟器是 API 转译层很多人第一次听到 Wine 会以为它是虚拟机或者模拟器其实都不是。Wine 的全称是 Wine Is Not an Emulator它做的事情是把 Windows 的 API 调用翻译成宿主系统的等价调用。比如 Windows 的CreateFile会被翻译成 Linux 的openReadFile翻译成read。这样 Windows 程序在运行时感知不到自己不在 Windows 上。在 Madeira 这套方案里Wine 的角色是承上启下。上面是 x86-64 的 Windows 程序下面是 ARM64 的宿主系统。FEX-Emu 负责指令集翻译Wine 负责 API 翻译两者配合才能让程序完整跑起来。Wine 的 API 覆盖度是决定兼容性的关键。经过这么多年发展Wine 对常用 Windows API 的支持已经相当完善但总有一些冷门 API 或者新版本 Windows 引入的接口没有实现。遇到这种情况程序可能启动失败或者某个功能不可用。社区的做法通常是提交 bug 报告或者用 winetricks 安装缺失的组件来绕过。3.2 Wine 前缀Prefix的隔离机制与配置要点Wine 用“前缀”来隔离不同程序的运行环境。一个前缀就是一个目录里面模拟了 Windows 的 C 盘结构包括windows、Program Files、users等子目录。每个前缀有独立的注册表和配置文件互不干扰。这个机制的好处是你可以为不同程序创建不同的前缀避免依赖冲突。比如某个游戏需要特定的 DirectX 版本另一个工具需要 .NET Framework把它们放在不同前缀里就不会打架。坏处是每个前缀都要单独配置占用的磁盘空间也不小。创建前缀的命令很简单WINEPREFIX~/.wine-madeira WINEARCHwin64 winecfg这行命令会创建一个 64 位的前缀并打开配置界面。WINEARCHwin64这个参数很重要因为现在大多数 Windows 程序都是 64 位的用 32 位前缀跑 64 位程序会直接报错。注意前缀一旦创建架构就固定了不能从 32 位改成 64 位。如果创建时忘了指定WINEARCH默认可能是 32 位后面遇到 64 位程序就得重建前缀。3.3 Wine 乱码问题的根因与修复路径热词里出现了“wine 乱码”和“wine 栏是乱码”这确实是 Wine 使用中最常见的问题之一。乱码的根因通常是字体缺失或者字符编码不匹配。Wine 默认自带的字体很少遇到中文、日文、韩文等非拉丁字符时如果没有对应的字体文件就会显示成方块或者乱码。修复思路分两步。第一步是安装字体把宿主系统的中文字体复制到 Wine 前缀的字体目录或者用 winetricks 安装corefonts、cjkfonts等字体包。第二步是配置字体替换在winecfg的“显示”选项卡里把默认字体替换成已安装的中文字体。具体操作# 安装 winetricks如果还没装 # 安装中文字体支持 winetricks cjkfonts # 或者手动复制字体 cp /usr/share/fonts/truetype/wqy/wqy-microhei.ttc ~/.wine-madeira/drive_c/windows/Fonts/复制完字体后还需要在注册表里配置字体替换规则。可以用wine regedit打开注册表编辑器定位到HKEY_CURRENT_USER\Software\Wine\Fonts\Replacements添加替换项。比如把MS Shell Dlg替换成WenQuanYi Micro Hei。界面栏乱码通常是另一个原因Wine 的界面控件使用了系统默认字体而默认字体不支持中文。这种情况下除了安装字体还需要在winecfg里把界面字体显式指定为中文字体。我实测下来把Tahoma、MS Sans Serif、System这几个字体都替换成中文字体后大部分乱码问题都能解决。4. DXMT把 DirectX 调用接到 Metal 上的桥梁4.1 DXMT 的定位与工作原理DXMT 是 DirectX Media Translation 的缩写它做的事情是把 Windows 的 DirectX 调用翻译成宿主系统的图形 API 调用。在 ARM 设备上宿主系统的图形 API 通常是 MetalApple 平台或者 VulkanLinux 平台。DXMT 的重点是 DirectX 到 Metal 的转换因为 Apple 设备在 ARM 生态里占了很大份额。它的工作流程大致是这样的Windows 程序调用 DirectX 接口创建纹理、着色器、渲染目标DXMT 把这些调用翻译成对应的 Metal 对象和命令。着色器代码需要从 HLSL 转换成 Metal Shading Language这个转换过程由 DXMT 内置的编译器完成。和 DXVK 的区别在于DXVK 是把 DirectX 翻译成 VulkanDXMT 是翻译成 Metal。在 Apple 平台上Metal 是原生 API性能通常比 Vulkan 转译层更好。所以如果你在 Apple 设备上跑 Windows 游戏DXMT 是更优的选择。4.2 图形层配置中的常见故障与排查顺序图形层出问题是最让人头疼的因为表现往往是黑屏、花屏、崩溃错误信息又很少。我总结了一个排查顺序按这个顺序走能覆盖大部分情况。第一步确认 DXMT 是否正确加载。可以在启动程序时加上调试环境变量看日志里有没有 DXMT 的初始化信息。如果 DXMT 根本没加载程序会回退到 Wine 内置的 DirectX 实现那个性能很差而且兼容性有限。第二步检查 Metal 设备是否可用。有些 ARM 设备或者虚拟化环境下Metal 可能不可用或者功能受限。可以用系统自带的图形诊断工具确认。第三步检查着色器编译是否成功。DXMT 在首次运行某个程序时需要编译着色器这个过程可能因为语法不兼容或者特性不支持而失败。日志里通常会有编译错误的详细信息。第四步检查纹理格式和渲染目标的兼容性。某些游戏使用了 Metal 不支持的纹理格式DXMT 需要做格式转换转换过程中可能出问题。提示DXMT 的日志可以通过设置DXMT_LOG_LEVELdebug环境变量来开启详细输出。排查问题时先把日志级别调高定位到具体环节后再针对性解决。4.3 性能调优从帧率波动到稳定输出图形层跑通之后下一步是调性能。我遇到的最典型问题是帧率波动大有时候能到 60 帧有时候掉到 20 帧以下。这种波动通常来自几个方面。第一是着色器编译卡顿。首次遇到新着色器时DXMT 需要实时编译这个编译过程会阻塞渲染管线导致帧率骤降。解决办法是启用着色器预编译或者异步编译。DXMT 支持后台编译着色器虽然首次运行时可能看到一些画面异常但整体流畅度会好很多。第二是内存带宽瓶颈。ARM 设备的统一内存架构虽然延迟低但带宽有限。高分辨率纹理和复杂的渲染目标会吃满带宽导致帧率下降。适当降低纹理质量或者渲染分辨率能缓解这个问题。第三是 CPU 翻译开销。FEX-Emu 翻译 x86-64 指令需要消耗 CPU 资源如果游戏逻辑本身就很吃 CPU翻译开销会进一步压缩可用算力。这种情况下可以尝试调整 FEX-Emu 的翻译线程优先级或者关闭一些不必要的后台进程。5. 从零搭建 Madeira 运行环境的完整流程5.1 基础依赖的安装与版本选择搭建环境的第一步是装依赖。不同 Linux 发行版的包管理命令不一样但核心依赖是相同的。以 Ubuntu 系为例# 安装基础编译工具和依赖库 sudo apt update sudo apt install -y build-essential cmake ninja-build git python3 pkg-config sudo apt install -y libsdl2-dev libvulkan-dev libgl1-mesa-dev sudo apt install -y libasound2-dev libpulse-dev libdbus-1-devFEX-Emu 和 DXMT 都需要从源码编译因为预编译的二进制包往往不包含最新的优化和修复。编译前确认 CMake 版本不低于 3.20GCC 版本不低于 11。版本太低会导致编译失败或者生成的二进制性能不佳。Wine 可以选择用发行版自带的包也可以自己编译。自带的包省事但版本可能偏旧。如果遇到兼容性问题建议从 Wine 的 Git 仓库拉最新代码编译。编译 Wine 需要额外的依赖sudo apt install -y libfreetype6-dev libgnutls28-dev libkrb5-dev sudo apt install -y libx11-dev libxext-dev libxrender-dev libxi-dev5.2 FEX-Emu 的编译与根文件系统配置FEX-Emu 的编译流程比较标准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编译完成后需要配置根文件系统。FEX-Emu 需要一个 x86-64 的根文件系统来提供库文件和运行时环境。可以用FEXRootFSFetcher工具自动下载也可以手动准备一个 Ubuntu 或 Debian 的 x86-64 rootfs。# 使用 FEXRootFSFetcher 自动下载 FEXRootFSFetcher # 选择 Ubuntu 22.04 或其他版本根文件系统准备好之后在 FEX 的配置文件里指定路径。配置文件通常在~/.fex-emu/Config.json把RootFS字段指向 rootfs 目录。5.3 Wine 与 DXMT 的整合配置Wine 和 DXMT 的整合需要把 DXMT 的库文件放到 Wine 能找到的位置。DXMT 编译完成后会生成d3d11.dll、dxgi.dll等文件这些需要复制到 Wine 前缀的system32目录。# 编译 DXMT git clone https://github.com/3Shain/dxmt.git cd dxmt mkdir build cd build cmake -DCMAKE_BUILD_TYPERelease .. make -j$(nproc) # 复制到 Wine 前缀 cp output/d3d11.dll ~/.wine-madeira/drive_c/windows/system32/ cp output/dxgi.dll ~/.wine-madeira/drive_c/windows/system32/然后在 Wine 的注册表里配置 DLL 覆盖让程序优先加载 DXMT 的 DLL 而不是 Wine 内置的版本。可以用winecfg的“函数库”选项卡来配置也可以用winetricks命令WINEPREFIX~/.wine-madeira winetricks d3d11native dxginative5.4 启动脚本的编写与环境变量调优把所有组件串起来需要一个启动脚本。脚本里要设置好 FEX-Emu 的路径、Wine 的前缀、DXMT 的环境变量然后启动目标程序。#!/bin/bash export FEX_ROOTFS~/.fex-emu/RootFS/Ubuntu_22_04 export FEX_APP_CONFIG~/.fex-emu/Config.json export WINEPREFIX~/.wine-madeira export DXMT_LOG_LEVELwarn export WINEESYNC1 export WINEFSYNC1 fex-emu -r $WINEPREFIX/drive_c/windows/system32/wine64 $WINEESYNC和WINEFSYNC这两个环境变量控制 Wine 的同步机制。ESYNC 是事件同步FSYNC 是 futex 同步。FSYNC 通常性能更好但需要内核支持。如果内核版本较新优先用 FSYNC。6. 实测中遇到的典型问题与解决思路6.1 程序启动即崩溃从日志定位翻译层错误程序启动就崩溃是最常见的问题。排查的第一步是看日志。FEX-Emu 和 Wine 都会输出日志关键是找到日志的位置和关键信息。FEX-Emu 的日志默认输出到 stderr可以在启动脚本里重定向到文件。Wine 的日志可以通过WINEDEBUG环境变量控制比如WINEDEBUGall会输出所有调试信息但信息量巨大通常只需要关注特定模块比如WINEDEBUGd3d11只看 DirectX 相关的日志。我遇到过一次启动崩溃日志里显示 FEX-Emu 在翻译某条 AVX2 指令时出错。原因是 FEX-Emu 的版本较旧不支持那条指令。升级到最新版本后问题解决。所以遇到翻译层错误第一反应应该是检查版本。另一种常见情况是 Wine 的 API 未实现。日志里会显示unimplemented function之类的信息。这种情况下可以查 Wine 的 bug 数据库看是否有已知的解决方案或者替代实现。6.2 画面异常纹理丢失、着色器编译失败的应对画面异常的表现形式很多纹理变成纯色、模型缺失、画面闪烁、颜色错乱。这些问题的根因通常在图形层。纹理丢失最常见的原因是纹理格式不支持。DXMT 需要把 DirectX 的纹理格式转换成 Metal 的格式如果遇到不支持的格式纹理就无法正确上传。解决办法是查看 DXMT 的日志确认是哪种格式出了问题然后在 DXMT 的配置里启用格式转换回退。着色器编译失败会导致模型渲染不出来或者渲染成默认材质。DXMT 的着色器编译器对 HLSL 的支持比较完整但某些游戏使用了非标准的语法或者依赖特定的编译器行为。遇到这种情况可以尝试更新 DXMT 到最新版本或者查找社区里针对该游戏的补丁。提示如果某个游戏在 DXMT 下画面异常可以尝试切换到 DXVK 作为对比。如果 DXVK 正常而 DXMT 异常说明问题出在 DXMT 的 Metal 转换层如果两者都异常问题可能在 Wine 或 FEX-Emu 层。6.3 性能不达预期CPU 翻译开销与 GPU 瓶颈的区分性能问题需要先定位瓶颈在 CPU 还是 GPU。用系统监控工具观察运行时的 CPU 和 GPU 占用率。如果 CPU 占用率接近 100% 而 GPU 占用率不高瓶颈在 CPU 翻译反之则是 GPU 渲染瓶颈。CPU 翻译瓶颈的优化手段有限主要是调整 FEX-Emu 的配置。可以尝试增大 JIT 缓存、增加翻译线程数、启用多线程编译。如果设备支持还可以尝试超频 CPU 或者关闭节能模式。GPU 瓶颈的优化空间更大。降低渲染分辨率、关闭抗锯齿、降低纹理质量、减少阴影和特效这些都能显著提升帧率。DXMT 本身也有一些性能相关的配置项比如是否启用异步着色器编译、是否启用管线缓存等。6.4 音频与输入设备的兼容性处理音频问题通常表现为无声、爆音、延迟。Wine 的音频后端有 PulseAudio、ALSA、JACK 等多种选择。默认情况下 Wine 会自动选择但自动选择不一定最优。可以在winecfg的“音频”选项卡里手动指定后端。我遇到过一次爆音问题原因是 Wine 的音频缓冲区设置太小。在winecfg里把缓冲区调大之后问题解决。延迟问题则相反缓冲区太大导致延迟增加需要根据实际场景权衡。输入设备方面手柄和键盘鼠标的兼容性通常没问题但某些特殊手柄可能需要额外的驱动或者映射配置。Wine 支持 XInput 和 DirectInput 两种手柄接口大部分现代游戏用 XInput老游戏用 DirectInput。如果手柄不工作先确认游戏用的是哪种接口然后在 Wine 的注册表里配置对应的映射。7. 这套方案适合跑什么、不适合跑什么7.1 游戏兼容性的实际边界经过大量测试我发现这套方案对游戏的兼容性有几个规律。DirectX 9 和 DirectX 11 的游戏兼容性最好因为 DXMT 对这两个版本的支持最成熟。DirectX 12 的游戏兼容性参差不齐部分游戏能跑但性能损失较大部分游戏直接无法启动。Unity 引擎和 Unreal 引擎的游戏通常兼容性不错因为这两个引擎的渲染管线比较标准。自定义引擎或者重度依赖特定 DirectX 特性的游戏兼容性风险较高。老游戏2005 年之前的兼容性反而不一定好因为它们可能依赖 16 位代码或者已经废弃的 DirectX 版本。这种情况下可能需要额外的兼容性配置比如用 dgVoodoo2 做一层转换。7.2 生产力工具的运行表现除了游戏这套方案也能跑一些 Windows 生产力工具。Office 系列基本能用但复杂宏和插件可能出问题。Adobe 系列中Photoshop 和 Illustrator 的旧版本能跑新版本对 GPU 加速依赖较强性能可能不理想。开发工具方面Visual Studio 的旧版本能跑新版本对 .NET 和系统 API 的依赖较深兼容性风险较高。一些轻量级编辑器和命令行工具通常没问题。需要强调的是生产力工具对稳定性的要求比游戏高。游戏崩溃了重开就行生产力工具崩溃可能导致数据丢失。所以如果用这套方案跑生产力工具务必做好数据备份重要工作还是建议在原生 Windows 环境下完成。7.3 哪些场景下不建议使用这套方案有几种情况我建议直接放弃这套方案。第一是对性能要求极高的场景比如竞技类游戏或者实时音视频处理翻译层的开销无法忽略。第二是对稳定性要求极高的场景比如生产环境的关键业务系统。第三是依赖特定硬件驱动的场景比如需要专用 GPU 加速或者特殊外设的软件。如果你的需求是偶尔跑一两个 Windows 程序而且对性能和稳定性要求不高这套方案是可行的。但如果你的日常工作重度依赖 Windows 生态还是建议准备一台原生 Windows 设备或者用云桌面方案。8. 一些实操中攒下来的经验编译 FEX-Emu 和 DXMT 时建议用 Release 模式并且开启 LTO链接时优化。LTO 能跨编译单元优化代码对翻译器的性能提升比较明显。代价是编译时间变长内存占用增加。如果机器内存不足 16GB编译时可能会因为 OOM 失败可以适当减少并行编译的任务数。Wine 前缀的备份很重要。配置好一个能跑特定程序的前缀后把整个前缀目录打包备份。以后遇到问题可以快速回滚不用从头配置。前缀目录通常不大压缩后几百 MB 到几 GB占不了多少空间。日志是排查问题的第一手资料。建议在启动脚本里默认开启日志输出但日志级别不要设太高否则日志文件会迅速膨胀。可以用logrotate或者类似的工具管理日志文件避免磁盘被写满。社区资源值得花时间研究。FEX-Emu、Wine、DXMT 都有自己的社区论坛和 issue 追踪系统。遇到问题时先搜索是否有人遇到过类似情况往往能找到现成的解决方案。提交 issue 时附上完整的日志和系统信息能大大提高被回复的概率。最后说一个容易被忽略的点散热。ARM 设备跑翻译层时 CPU 负载很高发热量比原生运行大得多。如果散热跟不上设备会降频性能进一步下降。确保设备通风良好必要时加装散热措施。我在长时间跑游戏时会给设备加一个散热底座帧率稳定性明显改善。