ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

ARM设备运行x86-64 Windows应用:FEX-Emu+Wine+DXMT兼容层实战

ARM设备运行x86-64 Windows应用:FEX-Emu+Wine+DXMT兼容层实战 1. 从“Madeira”说起一个跨平台兼容层的完整拆解第一次看到“Madeira”这个项目标题加上 FEX-Emu、Wine、DXMT、iOS、x86-64 这串关键词我脑子里第一反应是这又是一个在“让不同架构、不同系统的程序互相跑起来”这件事上死磕的项目。Madeira 本质上是一套面向 ARM 设备运行 x86-64 Windows 应用的兼容方案组合它把 FEX-Emu 的指令翻译能力、Wine 的 Windows API 转译能力、DXMT 的图形接口转换能力串成一条链路最终目标是在移动端或者 ARM 桌面端把原本属于 x86-64 Windows 生态的软件跑起来。为什么这件事值得单独拿出来讲因为过去大家想在 ARM 上跑 Windows 程序路径无非就那么几条要么用完整的虚拟机性能损耗大、发热高要么用纯 Wine 方案但 Wine 本身不解决 CPU 指令集差异x86-64 的二进制在 ARM 上根本没法直接执行。Madeira 这类方案的价值就在于它把“指令翻译”和“系统调用翻译”这两件最难的事分层处理让每一层都专注做自己最擅长的事。这套思路不是 Madeira 独创但它把 FEX-Emu、Wine、DXMT 这几个组件整合成一个可用的整体这个整合过程本身就是大量踩坑经验的结晶。这篇文章适合谁看如果你是在 ARM 设备上折腾 Windows 应用兼容的开发者或者你对指令翻译、API 转译、图形层转换这些底层机制感兴趣再或者你只是单纯好奇“为什么有些 Windows 游戏能在手机上跑起来”那这篇内容应该能给你一些实在的参考。我会尽量把每个环节的“为什么这么选”讲清楚而不是只丢一堆配置命令让你照抄。2. 整体架构设计为什么是 FEX-Emu Wine DXMT 这个组合2.1 三层翻译链路的分工逻辑要理解 Madeira 的架构得先明白一个 x86-64 Windows 程序在 ARM 设备上运行中间到底隔了几层障碍。第一层是指令集差异x86-64 的机器码 ARM 处理器不认识必须有人把它翻译成 ARM 能执行的指令。第二层是系统调用差异Windows 程序调用的是 Windows 的 API比如 kernel32.dll、user32.dll 这些ARM 设备上跑的是 Linux 或者类 Unix 系统没有这些 API。第三层是图形接口差异Windows 程序用 DirectX 渲染而目标平台可能只提供 Vulkan 或者 Metal。FEX-Emu 解决的是第一层问题。它是一个 x86-64 到 ARM64 的指令翻译器工作方式类似 QEMU 的用户态模拟但针对游戏和图形应用做了大量优化。FEX-Emu 的核心思路是“块翻译加缓存”它把 x86-64 的指令块翻译成 ARM64 指令块翻译结果缓存起来下次执行到同一块代码时直接复用。这个设计对游戏特别友好因为游戏的主循环往往反复执行同一段代码缓存命中率极高。Wine 解决的是第二层问题。它实现了 Windows API 的兼容层把 Windows 程序发出的 API 调用翻译成 POSIX 调用。Wine 不是模拟器它不翻译指令只翻译 API。所以 Wine 必须和 FEX-Emu 配合使用FEX-Emu 负责让 x86-64 代码能在 ARM 上跑Wine 负责让 Windows API 调用能在 Linux 上跑。DXMT 解决的是第三层问题。它的全称是 DirectX Metal Translation顾名思义把 DirectX 调用翻译成 Metal 调用。为什么是 Metal 而不是 Vulkan因为在 iOS 和 macOS 平台上Metal 是原生图形接口直接翻译到 Metal 比先转 Vulkan 再转 Metal 少一层损耗。DXMT 基于 DXVK 和 MoltenVK 的思路但针对 Metal 做了更直接的适配。注意这三个组件的版本匹配非常关键。FEX-Emu 的 rootfs 里如果自带的 Wine 版本和 DXMT 要求的 Wine 版本不一致会出现 DLL 加载失败或者图形初始化崩溃。我建议在整合之前先把三个组件各自的版本依赖关系理清楚。2.2 为什么不用 QEMU 全系统模拟有人可能会问既然 QEMU 能模拟整个 x86-64 系统为什么不直接用 QEMU 跑一个完整的 Windows 虚拟机答案很简单性能。全系统模拟需要模拟 CPU、内存管理单元、各种外设每一层都有开销。而 Madeira 这种方案只翻译用户态指令和 API 调用省掉了硬件模拟的开销。实测下来同样的硬件条件下FEX-Emu Wine 的方案比 QEMU 全系统模拟快三到五倍图形应用的帧率差距更明显。另一个原因是集成度。QEMU 全系统模拟需要你准备一个完整的 Windows 镜像启动慢、占用空间大。而 Wine 方案只需要一个轻量的 prefix 目录通常几百兆就能跑起来。对于移动端设备来说存储空间和启动速度都是硬指标。2.3 组件选型的取舍与替代方案FEX-Emu 并不是唯一的 x86-64 翻译器Box64 也是一个常见选择。Box64 更轻量对 32 位 x86 的支持更好但在 64 位代码的翻译效率上 FEX-Emu 通常更优。Madeira 选择 FEX-Emu 而不是 Box64我推测是因为目标场景里 64 位 Windows 应用占多数而且 FEX-Emu 对 SSE、AVX 等 SIMD 指令的支持更完整这对图形应用很重要。Wine 的替代方案是 Proton但 Proton 是 Valve 为 Steam 定制的集成了 DXVK、VKD3D 等组件整体比较重。Madeira 选择原版 Wine 加 DXMT 的组合灵活性更高可以根据目标平台裁剪组件。DXMT 的替代方案是 DXVK MoltenVK但这条路径多了一次 Vulkan 到 Metal 的转换延迟更高。3. 核心组件深度解析与实操配置3.1 FEX-Emu 的安装与 rootfs 配置FEX-Emu 的安装方式取决于目标平台。在 ARM Linux 上通常通过包管理器安装或者从源码编译。从源码编译的话需要先装好 CMake、Ninja、Clang 等工具链。编译参数里比较关键的是-DENABLE_LTOON和-DCMAKE_BUILD_TYPERelease这两个选项能显著提升翻译后代码的执行效率。rootfs 是 FEX-Emu 运行 x86-64 程序的基础环境它本质上是一个包含 x86-64 库文件和基本目录结构的根文件系统。制作 rootfs 的常见做法是用 debootstrap 或者 mmdebstrap 创建一个 x86-64 的 Debian 或 Ubuntu 根文件系统然后把 FEX-Emu 的运行时库复制进去。# 创建 x86-64 rootfs 的示例命令 sudo mmdebstrap --archamd64 --variantminbase \ bookworm /path/to/rootfs \ http://deb.debian.org/debian创建完 rootfs 后需要把 FEX-Emu 的libfex.so和相关二进制文件放到 rootfs 的/usr/lib和/usr/bin目录下。然后配置 binfmt_misc让内核在遇到 x86-64 可执行文件时自动调用 FEX-Emu。# 注册 binfmt_misc 的示例 echo :fex:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/FEXInterpreter:PF \ | sudo tee /proc/sys/fs/binfmt_misc/register提示binfmt_misc 的注册在系统重启后会失效需要写一个 systemd service 或者 udev rule 来持久化。我试过用 systemd 的ExecStart在开机时重新注册实测下来很稳。3.2 Wine 的编译选项与 prefix 初始化Wine 的编译选项直接影响兼容性和性能。对于 Madeira 这种场景我建议开启--enable-archsx86_64只编译 64 位支持减少编译时间和体积。--without-alsa和--without-pulse可以去掉音频支持如果目标场景不需要声音的话。图形方面--with-vulkan是必须的因为 DXMT 依赖 Vulkan 的某些基础设施。# Wine 编译配置示例 ./configure --enable-archsx86_64 \ --with-vulkan \ --without-alsa \ --without-pulse \ --prefix/opt/wine-madeira编译完成后用wineboot初始化 prefix。prefix 是 Wine 的“虚拟 Windows 目录”里面包含注册表、DLL 文件、驱动等。初始化时可以用WINEARCHwin64指定创建 64 位 prefix。# 初始化 64 位 prefix WINEARCHwin64 WINEPREFIX/path/to/prefix /opt/wine-madeira/bin/wineboot初始化过程中如果遇到wine: created the configuration directory之后卡住通常是 Gecko 或 Mono 的安装提示在等待输入。可以用WINEDLLOVERRIDESmscoree,mshtml跳过这些组件的安装。3.3 DXMT 的部署与图形层配置DXMT 的部署相对简单核心是把编译好的d3d11.dll、dxgi.dll等文件放到 Wine prefix 的system32目录下然后在 Wine 的注册表里设置 DLL 覆盖让程序优先加载 DXMT 的 DLL 而不是 Wine 自带的。# 复制 DXMT DLL 到 prefix cp /path/to/dxmt/build/*.dll /path/to/prefix/drive_c/windows/system32/ # 设置 DLL 覆盖 WINEPREFIX/path/to/prefix /opt/wine-madeira/bin/wine reg add \ HKEY_CURRENT_USER\Software\Wine\DllOverrides \ /v d3d11 /t REG_SZ /d native /fDXMT 的配置文件通常放在 prefix 根目录下文件名是dxmt.conf。里面可以设置最大帧率、着色器缓存路径、调试输出等级等。着色器缓存路径建议放在 SSD 上因为 DXMT 在首次运行时会编译大量着色器缓存命中后帧率会明显提升。注意DXMT 对 Metal 的版本有要求iOS 设备上需要 Metal 2 以上macOS 上需要 macOS 10.15 以上。如果目标设备的 Metal 版本太低DXMT 会回退到软件渲染性能会断崖式下降。4. 完整实操流程从零搭建一个可运行的 Madeira 环境4.1 环境准备与依赖安装假设目标平台是一台 ARM64 的 Linux 设备比如树莓派 5 或者某款 ARM 笔记本。首先需要确认内核版本在 5.15 以上因为 FEX-Emu 的某些特性依赖较新的内核接口。然后安装基础依赖sudo apt update sudo apt install -y build-essential cmake ninja-build clang \ libsdl2-dev libvulkan-dev vulkan-tools \ python3 python3-pip git wgetVulkan 驱动是必须的因为 DXMT 通过 Vulkan 来和 Metal 或者原生 GPU 驱动通信。在 ARM Linux 上常见的 Vulkan 驱动是 Mesa 的 Panfrost 或者 Freedreno。可以用vulkaninfo命令验证 Vulkan 是否正常工作。4.2 FEX-Emu 编译与 rootfs 制作FEX-Emu 的源码在 GitHub 上克隆下来后进入源码目录用 CMake 配置编译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_LTOON \ -DCMAKE_C_COMPILERclang \ -DCMAKE_CXX_COMPILERclang \ .. ninja编译完成后用ninja install安装到系统目录。然后制作 rootfs我通常用 mmdebstrap 创建一个最小的 Debian rootfs再把 FEX-Emu 的运行时库复制进去。# 制作 rootfs sudo mmdebstrap --archamd64 --variantminbase \ bookworm /opt/fex-rootfs \ http://deb.debian.org/debian # 复制 FEX 运行时 sudo cp /usr/lib/libfex.so /opt/fex-rootfs/usr/lib/ sudo cp /usr/bin/FEXInterpreter /opt/fex-rootfs/usr/bin/4.3 Wine 与 DXMT 的整合配置Wine 的编译前面已经讲过这里重点说整合。首先在 rootfs 里初始化 Wine prefixexport WINEPREFIX/opt/fex-rootfs/home/user/.wine export WINEARCHwin64 /opt/wine-madeira/bin/wineboot --init然后把 DXMT 的 DLL 复制到 prefix 的 system32 目录并设置 DLL 覆盖。DXMT 的编译需要 Metal 的头文件在 Linux 上编译 DXMT 需要先安装 Metal 的兼容层比如metal-cpp。如果目标平台是 iOSDXMT 的编译需要在 macOS 上用 Xcode 进行。# 在 macOS 上编译 DXMT git clone https://github.com/3Shain/dxmt.git cd dxmt mkdir build cd build cmake -G Xcode -DCMAKE_SYSTEM_NAMEiOS \ -DCMAKE_OSX_ARCHITECTURESarm64 \ .. xcodebuild -scheme dxmt -configuration Release编译完成后把生成的d3d11.dll、dxgi.dll、winemetal.dll复制到 iOS 设备上的 Wine prefix 里。iOS 上的 Wine prefix 路径通常是应用沙盒内的Documents/wine目录。4.4 运行测试与性能调优一切就绪后可以拿一个简单的 Windows 程序做测试。我建议从notepad.exe或者winver.exe开始这两个程序不依赖图形加速能跑起来说明 FEX-Emu 和 Wine 的基本链路是通的。# 运行 winver 测试 WINEPREFIX/opt/fex-rootfs/home/user/.wine \ /opt/fex-rootfs/usr/bin/FEXInterpreter \ /opt/wine-madeira/bin/wine winver.exe如果 winver 能正常弹出窗口说明基础环境没问题。接下来测试图形程序可以用dxdiag.exe检查 DirectX 是否正常工作。如果 dxdiag 显示 DirectX 版本和显卡信息说明 DXMT 已经生效。性能调优方面FEX-Emu 有几个环境变量可以调整环境变量作用推荐值FEX_TSOENABLED开启 x86 内存序模拟1FEX_VECTORTSOENABLED开启向量内存序模拟1FEX_MULTIBLOCK开启多块翻译1FEX_ROOTFS指定 rootfs 路径/opt/fex-rootfs提示FEX_TSOENABLED 和 FEX_VECTORTSOENABLED 对多线程程序很重要关掉的话某些游戏会出现随机崩溃。但开启后性能会有一定下降需要根据实际场景权衡。5. 常见问题与排查技巧实录5.1 Wine 乱码问题的根因与解决Wine 乱码是高频问题表现是程序界面上的中文显示成方块或者问号。根因通常是字体缺失或者字符集配置不对。Wine 默认使用系统字体如果系统里没有中文字体就会乱码。解决办法是在 prefix 里安装中文字体或者把系统的中文字体链接到 Wine 的字体目录。# 复制中文字体到 Wine prefix cp /usr/share/fonts/truetype/wqy/wqy-microhei.ttc \ /opt/fex-rootfs/home/user/.wine/drive_c/windows/Fonts/另一个原因是 locale 设置。Wine 需要正确的LANG和LC_ALL环境变量才能正确处理中文字符。我通常设置LANGzh_CN.UTF-8和LC_ALLzh_CN.UTF-8。5.2 FEX-Emu 启动失败的排查路径FEX-Emu 启动失败的表现是执行 x86-64 程序时直接报Exec format error或者No such file or directory。排查路径如下检查 binfmt_misc 是否注册成功cat /proc/sys/fs/binfmt_misc/fex如果显示enabled说明注册成功。检查 FEXInterpreter 的路径是否正确which FEXInterpreter如果找不到说明没安装到 PATH 里。检查 rootfs 是否完整ls /opt/fex-rootfs/lib/x86_64-linux-gnu/如果目录为空说明 rootfs 制作失败。检查内核是否支持 binfmt_miscls /proc/sys/fs/binfmt_misc/如果目录不存在说明内核没编译这个模块。5.3 DXMT 图形初始化失败的常见原因DXMT 初始化失败的表现是程序启动后黑屏或者直接崩溃日志里出现Failed to create Metal device或者Vulkan device creation failed。常见原因和解决办法问题现象可能原因解决办法黑屏无窗口Metal 设备创建失败检查设备是否支持 Metal 2崩溃在 d3d11.dllDXMT 版本不匹配确认 DXMT 和 Wine 版本兼容帧率极低着色器缓存未命中检查缓存路径是否可写画面撕裂垂直同步未开启在 dxmt.conf 里设置 vsync1注意iOS 上的 DXMT 需要应用有 Metal 的 entitlement如果没有配置好签名Metal 设备创建会直接失败。这个坑我踩过好几次排查了半天才发现是签名问题。5.4 性能不达预期的调优清单如果一切能跑但性能不理想可以按以下清单逐项排查确认 FEX-Emu 的 LTO 是否开启没开的话重新编译。确认 Wine 的编译优化等级-O2是底线-O3更好。确认 DXMT 的着色器缓存是否生效缓存文件是否在 SSD 上。确认 CPU 调频策略是否为 performancecpufreq-set -g performance。确认 GPU 驱动是否为最新版本Mesa 的版本对性能影响很大。确认是否有其他进程占用 GPU 资源用fuser -v /dev/dri/*检查。6. 跨平台扩展与后续演进方向6.1 从 Linux 到 iOS 的移植要点Madeira 的方案最初是在 Linux 上验证的移植到 iOS 上需要解决几个额外问题。首先是沙盒限制iOS 应用只能访问自己的沙盒目录Wine prefix 必须放在沙盒内。其次是签名和 entitlementFEX-Emu 和 Wine 都需要特定的 entitlement 才能执行动态代码生成比如com.apple.security.cs.allow-jit。最后是 Metal 的适配DXMT 在 iOS 上直接使用 Metal不需要经过 Vulkan 层但需要处理好 iOS 的显示链路。6.2 与麒麟、统信等国产系统的兼容考量国产 Linux 系统如麒麟、统信底层也是 Linux但库版本和目录结构可能有差异。在这些系统上部署 Madeira需要特别注意 glibc 的版本兼容性。FEX-Emu 和 Wine 编译时链接的 glibc 版本不能高于目标系统的 glibc 版本否则会出现GLIBC_2.xx not found的错误。解决办法是在较低版本的系统上编译或者使用静态链接。6.3 后续可以尝试的优化方向一个值得尝试的方向是给 FEX-Emu 加一个 JIT 缓存持久化机制把翻译后的代码块缓存到磁盘上下次启动时直接加载省去重新翻译的时间。另一个方向是 DXMT 的着色器预编译在程序启动前就把常用着色器编译好减少运行时的卡顿。还有一个方向是整合 DXVK 和 DXMT让程序根据目标平台自动选择走 Vulkan 还是 Metal 路径。我个人在实际操作中的体会是这类跨平台兼容方案最耗时间的不是编译和配置而是排查各种“看起来不相关”的问题。比如 Wine 乱码可能是字体问题也可能是 locale 问题还可能是程序自身的编码问题。FEX-Emu 启动失败可能是 binfmt 没注册也可能是 rootfs 路径不对还可能是内核模块没加载。每次遇到问题最好的办法是从最底层的链路开始逐层验证而不是一上来就改配置。先确认指令翻译通了再确认 API 调用通了最后确认图形渲染通了这样排查效率最高。
返回列表