国产指令集架构处理器要想获得足量的软件资源,通过二进制翻译挖 Wintel 联盟软件生态的墙角不失为一条可行之策。
目前二进制翻译有 2 种方案:以完整的硬件体系系统为目标的模拟,以某种指令架构操作系统应用接口为目标的模拟。前者的代表有 Qemu 完全系统模式、Bochs 等,后者的代表有 exagear、Qemu 用户模式等。由于前者主要用于操作系统内核验证等操作,且性能损耗相对较大,因此不在本文讨论之列。本文主要就后者的软件架构进行讨论。目前业界的主流做法是,先通过二进制翻译模拟目标指令架构 x86 的 Linux 系统,再通过 Wine 的方式兼容 Wintel 应用,然而这种架构虽然简单明了,但并非最优。
(资料图)
在讨论此问题之前,要首先确定目标是什么,以目标为指引高屋建瓴的看待此问题,而非单纯的逐个击破各个模块所遇到的问题。以目标导向而非任务导向去解决此问题。问题是什么?模拟 Wintel 软件运行的环境!既然如此,为什么要先模拟目标指令架构 x86 的 Linux 系统,而不直接一步到位模拟 x86 的 Win32 系统呢?
对于软件架构而言,越是底层的架构层级,对性能损耗的要求越是严格,比如处于最底层的 Linux 内核,就需要通过汇编等手段优化性能到极致。对于整个软件的单位任务运行时间,可用下列公式描述。
其中 t 是某一层执行单位任务所需时间。k 是某一层的执行时间损耗比,是一个大于 1 的常数。
目前主流的二进制翻译架构,从下到上依次是
Linux (Host) -> ISA Translator -> API Translator -> Emulated App
然而这样的架构存在不足,就是指令翻译层(ISA Translator)的执行时间损耗比太大,导致在其上的各层需要承担其带来的时间损耗。因此,以下架构要明显优于上述架构。
Linux (Host) -> API Translator -> ISA Translator -> Emulated App
也就是通过透传将被模拟应用的 API 调用透传到本机代码执行。
若针对二进制翻译进行业务侧性能分析,其所耗费的时间可以用以下公式表示
其中 k 表示时间损耗比,是模拟速率的倒数。λ 是模拟比,表示被模拟应用单位任务中,需要通过指令翻译层的执行代码的比例。
比如,若采用全模拟,则 λ 为 0,指令翻译模拟速率若为 0.5,单位任务耗费的单位时间数为 2。若采用透传将模拟比降至 0.3,哪怕模拟速率只有前者的一半 0.25,单位耗费的单位时间数为 1.9,明显优于前者,这也是为什么选择合适的接口进行透传可以达到事半功倍的效果。。
模拟比在具体实现上,与透传调用的 API 相关。倘若按照常规架构,那么能透传的有 libc 等 Linux 相对底层的 API,而由于 Linux 的 API 设计以追求开发者自由为原则,导致多为细粒度接口,针对细粒度接口的透传带来的收益与透传所带来的损耗相比,收益并不明显。
那么如何降低模拟比?还是回到最初的问题,要高屋建瓴的看待整个任务目标,而非针对独立任务各个击破。既然任务目标是高性能模拟 Wintel 应用,那么可以选择合适的 Win32 接口进行透传。由于 Win32 接口的实现粒度相比 Linux 的明显粗,因此透传 Win32 接口带来的收益对模拟比的降低明显高于透传 Linux 接口。
尽管如此,要想大幅降低模拟比,还是要对被模拟的系统做深度分析。由于 Win32 应用大多为有图形界面的应用,因此从 Win32 图形界面入手,可以快速寻求出解决方案。Win32 图形界面为消息回调机制,看似是应用端通过循环轮询消息,实际则是 Nt 内核将消息反馈给应用端进行处理,应用端图形界面相关绘制操作,会根据特定数据结构序列化为数据,交由 Nt 内核异步执行。同样在 Linux 平台,X11 协议也是将消息序列化为数据,传递给下一层异步执行。也就是说,完全可以做到应用端与底层的解耦,从而降低模拟比。不同于 API 直接调用,消息序列化数据与指令无关,也就是说,被模拟的 x86 应用序列化的消息,交由 RISC-V 指令实现的内核依然可以正确绘制。同理,针对图形底层渲染接口亦是如此。
由此举一反三,在设计二进制翻译软件的时候,被模拟应用、二进制翻译器、本机库之间也可通过消息序列化传递交互。并且 3 个部分位于同一线程的 3 个不同协程内,通过协程切换与消息传递交互。这便是二进制翻译领域革命性的架构 —— 奇美拉架构。
标签: