Madeira 单进程模型深度解析:为什么所有 Windows 进程都是线程及其 6 大后果

发布时间:2026/10/3 17:00:10
Madeira 单进程模型深度解析:为什么所有 Windows 进程都是线程及其 6 大后果 Madeira 单进程模型深度解析为什么所有 Windows 进程都是线程及其 6 大后果【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu Wine DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/MadeiraMadeira 是让 iPhone 直接运行 x86-64 Windows PC 游戏的开源项目它通过 FEX-Emu Wine DXMT 把未经修改的 Windows 游戏装进单个 iOS App 里。这套玩法能成立的关键正是它的单进程模型iOS 应用无法启动其他程序于是 Madeira 把 Wine 的全部进程都变成了同一个 App 内的线程。本文带你从新手视角看懂这个模型以及它带来的 6 个真实后果。一、单进程模型是什么为什么非这么做不可先建立直觉。在普通 PC 上Wine 的架构是一主多从一个独立的wineserver守护进程负责窗口、文件、注册表等系统服务的仲裁每个 Windows 程序explorer.exe、游戏本体、Steam 客户端……各自是一个真正的操作系统进程它们通过 Unix 套接字互相通信崩溃时互不影响但 iOS 的沙箱有两条硬性限制应用不能 fork/exec 启动新程序—— 你看到的每个 App 只能是一个进程信号signals是进程级别的—— 无法用来精准击杀某一个 Windows 程序于是 Madeira 做了一个大胆的决定iOS 应用不能启动其他程序所以一切都跑在一个进程里连 Wine 的服务器都变成线程而不是独立程序。 —— 见 README.md也就是说打开 Madeira 后你的 iPhone 里只有一个 Mach 任务一个 iOS 进程里面住着角色在 PC 上在 Madeira 里应用界面SwiftUI不存在主线程组wineserver独立守护进程一个线程explorer.exe独立进程一个伪进程线程游戏本体独立进程一个伪进程线程 所谓伪进程pseudo-process就是一组共享同一线程局部身份TEB/PEB的线程对外表现得像一个 Windows 进程。二、它是怎么实现的启动进程 创建一个线程在标准 Wine 里spawn_process的核心动作是fork()两次再exec加载器。而 Madeira 在 iOS 上把这段整个换掉了原来的fork exec被替换成pthread_create—— 直接新建一个线程把命令行参数、工作目录、一个专用 socket 打包进去新线程进入wine_ios_child_main像真正的加载器一样把 Windows 可执行文件拉起来线程退出时用setjmp/longjmp模拟进程的结束码并清理该进程占用的资源实现就在 process_ios.cspawn_process的 iOS 分支和 process_ios.c子线程入口ios_child_thread_entry。wineserver 那边同样被收编。在 main_ios.c 中可以看到两处关键改造忽略了SIGTERM/SIGINT等信号——因为信号是进程级的任何一个 Wine 客户端线程触发信号都会杀死整个 App主套接字超时设为无限——不能像原版那样3 秒没客户端就exit(0)否则exit()会带走整个 App三、单进程模型的 6 大后果这是本文的重点。共享一个进程意味着共享一切文件描述符表、信号、内存、锁。每一条都直接塑造了 Madeira 的行为。后果 1一个进程崩溃可能带走整个 AppPC 上杀掉steam.exe不会伤到游戏但在 Madeira 里所有伪进程住在同一个 Mach 任务中任何未处理的崩溃如空指针访问都可能让 iOS 直接把整个 App 打下来。代码注释里就记录过一次真实事故Steam 的steamsysinfo.exe硬件探测工具触发一次故障后因为这里每个 Windows 进程都是同一个 Mach 进程内的伪进程这次故障带走了整个 App。 这也是为什么玩家偶尔会遇到登录窗口黑屏后 App 直接关闭——那是某个后台子进程崩溃的连带伤害。后果 2文件描述符表是全局共享的普通进程各自持有 fd 表单进程模型下wineserver 和所有游戏线程共用同一张 fd 表。一个进程误关了别的进程正在用的通信管道就会让其他所有人卡死。为此 Madeira 建了一套完整的fd 账本fdtrace谁注册了哪个 fd、谁在什么场景下关闭它都要登记核对一旦发现跨属主关闭立刻高亮告警见 server_ios.c。后果 3信号杀不了线程只能关管道 延迟退出Linux 上 wineserver 用SIGQUIT杀线程但 iOS 里信号到不了特定线程被杀的线程会一直跑着直到某次请求发现管道已关闭。更隐蔽的坑是被杀的线程可能正握着某把全局互斥锁。由于所有 Windows 进程共享这些锁它会让所有进程的后续请求永久阻塞真实案例msiexec 被杀后等待它的主程序永远挂在NtClose上。解决方案是延迟退出——被杀线程先失败掉当前请求、走到最外层代码块再退出见 server_ios.c。后果 432 位游戏要抢一块稀缺地址空间32 位游戏通过 WoW64 层运行必须占用 2GB 以下的客户地址窗口——这块空间在单进程里是全局唯一且不可重占的。因此当一个 32 位伪进程比如游戏的启动器退出时必须把它的地址窗口归还下一个 32 位伪进程才能接手这个槽位。这条释放窗口逻辑直接写在子线程退出路径里见 process_ios.c。32 位支持的更多细节可以看 docs/WOW64.md。后果 5内存压力会互相传染所有伪进程共享同一个地址空间和 JIT 编译池。一个不起眼的后台子进程比如硬件探测工具加载 Python 运行时和 COM 初始化就可能吃掉大几百 MB 的 JIT 池空间把整个游戏的性能基线拉低。这也是单进程模型下调试必须全局看内存的原因一个线程的分配就是所有线程的问题。后果 6必须维护一张进程黑名单既然不能阻止所有子进程又不能让危险子进程崩掉全家Madeira 选择在NtCreateUserProcess门口设了一道闸门直接拒绝启动几类可有可无但容易出事的 Windows 程序steamerrorreporter, gldriverquery, vulkandriverquery, steamsysinfo, hardwareupdater, unitycrashhandler64见 process_ios.c。这些大多是 Steam/Unity 的错误上报、驱动探测助手——启动失败它们自己也容忍日志里一句Failed spawning ... continues拒绝启动反而比放任它们崩溃更安全。 对玩家来说这意味着App 日志里出现某进程拒绝启动是正常设计不是错误。四、对新手玩家的实际意义把上面的技术细节翻译成日常体验游戏崩溃可能 整个 App 崩溃这是 iOS 沙箱 单进程的物理限制遇到时重开 App 即可游戏存档不受影响存档跨重装保留Steam 里有些进程不启动是正常的黑名单拦截是保护机制32 位老游戏可以玩但有窗口槽位机制启动器退出后主程序才会真正接棒所以 32 位游戏启动可能比 64 位更慢性能波动是全局的一个后台线程吃满 CPU所有游戏线程都会跟着掉帧五、想深入从这里继续架构总览与构建docs/BUILDING.md32 位游戏WoW64docs/WOW64.mdSteam 客户端Madeira Dockdocs/MADEIRA_DOCK.md进程启动线程化改造build/ntdll-unix/process_ios.cwineserver 线程化改造build/wineserver/main_ios.c客户端-服务器通信与伪进程身份管理build/ntdll-unix/server_ios.c一句话总结Madeira 用进程皆线程的单进程模型绕过了 iOS 无法启动新程序的沙箱限制——代价是崩溃、文件描述符、信号和内存全部变成全局共享而整个项目的很多奇怪设计其实都是对这几条代价的工程化回应。【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu Wine DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考