比原生更快:在 Linux 内核中运行 WebAssembly

时间:2022-06-25
本文章向大家介绍比原生更快:在 Linux 内核中运行 WebAssembly,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

过去的几个月间,我们尝试了各种方法来给 Wasmer WebAssembly 运行环境提速。这些方法包括缓存编译后的代码、实现不同等级的编译后端(Singlepass/Cranelift/LLVM)等,也都取得了不错的效果。

这些优化性能的尝试使我们开始考虑一个更加“基础”的问题:基于 VM(虚拟机)的程序与原生程序相比,有哪些优势?我们是否可以让 WASM 运行得比原生代码更快?

这篇文章将介绍我们在 Linux 内核中实现的 WebAssembly 安全运行环境。我们在 Linux 内核中成功运行了一个 TCP Echo 服务端程序,并取得了相对原生代码 10% 的性能提升。

背景

  • “第二个操作系统“

许多语言和运行环境,包括 WebAssembly(支持 WASI 的实现)和 JavaScript (Node.js 和浏览器)等,都在尝试于真实的操作系统之上构建第二个沙箱化的“操作系统”。然而,这多出来的一层会带来不小的性能损耗。

如上图所示,在这样的传统架构中,来自 VM 上应用的系统服务请求(系统调用),在到达内核前,需要经过两层边界。

这两层边界的性能损耗很大。一个普通的函数调用所需时间一般小于 5 ns,然而一次来源于 VM 内部的系统调用可能会消耗上百纳秒。

  • Cervus 的后继者

我大约一年之前写过另一个内核中的 WebAssembly “用户模式”子系统 - Cervus 。那时候 WASI 和“生产级别”的 WebAssembly 运行时都还不存在,但 Cervus 项目已经证明这个想法是可行且有巨大潜力的。

现在 WebAssembly 生态正在快速成长,是时候做一个完整的、面向真实应用的内核模式 WebAssembly 运行环境了。

为什么要在内核中运行 WebAssembly ?

主要原因是性能和灵活性。

WASM 是由虚拟机保护的虚拟指令集。我们不需要依靠外部的软件/硬件保护来确保安全性。

在内核中运行 WASM 避免了这些外部保护引入的性能损耗,如系统调用(上下文切换)、用户态/内核态数据复制等。

同时,有了对底层的控制,我们可以实现很多在用户模式中低效或难以实现的特性,例如直接访问硬件、处理密集的内核事件(如网络包过滤)等。

安全性

在内核模式运行用户代码是件危险的事情。虽然我们用了很多技巧来保护系统免受恶意代码的攻击,我们仍然建议短期之内,在我们没有完整 Review 运行环境代码前,只通过这个模块执行可信的代码。

这里是一些已知的安全风险和我们的应对措施:

  • 栈溢出:在代码生成环节插入边界检查代码
  • 内存访问越界:为每个 WASM 任务分配 6GB 的虚拟地址空间,使越界访问无法表达。
  • 信号无法终止处于内核态的进程:接收到终止信号后,将 WASM 代码页面设置为禁止执行(NX)以强制终止执行。
  • 内核态进程浮点状态丢失:用 kernel_fpu_{begin,end} 与 preempt_notifier 手动保存和恢复浮点状态。
  • 内核不支持 Red Zone :在代码生成器中避免使用之。

例子和性能测试

我们提供了两个例子:echo-server 和 http-server 。它们位于 Wasmer 主仓库的 examples 目录下。

当使用 singlepass 后端编译(无优化直接生成 x86-64 代码),并在本地使用 tcpkali/wrk 测试时,echo-server 比它的用户模式等价实现快约 10% (25210 Mbps / 22820 Mbps) ,http-server 快约 6% (53293 Rps / 50083 Rps) 。

这两个例子使用了 WASI (文件抽象、控制台输出)和我们的异步网络扩展(通过 kernel-net 库)。

可以阅读这两个例子的代码,学习怎样编写在 kernel-wasm 中运行的高性能网络程序。

编译、运行

加载内核模块前,请确保:

  • 你的内核版本大于等于 4.15
  • 你的内核启用了抢占执行(preemption)。尝试在未启用抢占的内核上执行 WASM 用户代码会锁死你的系统。
  • 内核头文件和构建环境已安装

首先,clone 仓库:https://github.com/wasmerio/kernel-wasm

然后在仓库的根目录和 networkingwasi 目录下执行 make :

make
cd networking && make
cd ../wasi && make
cd ..

加载模块:

sudo insmod kernel-wasm.ko
sudo insmod wasi/kwasm-wasi.ko
sudo insmod networking/kwasm-networking.ko

运行 Wasmer 时选择 singlepass 后端和 kernel 加载器:

sudo wasmer run --backend singlepass --loader kernel the_file.wasm

(在 kernel-wasm 上运行的 cowsay )