本文部分翻译自 fermyon 的一篇博客,原文地址: www.fermyon.com/blog/webass…
对于一种言语生态而言,规范可能并不是其间最令人激动的部分。但是,“组件模型” 这个看似无聊的名称背后,是Wasm为软件世界带来的最重要的创新。组件模型正在快速开展,而且现已有了参考完成,2023 年很可能是组件模型开端重新界说咱们怎么编写软件的一年。

WebAssembly 组件模型是一个提案,旨在经过界说模块在应用程序或库中怎么组合来构建中心 WebAssembly 规范。正在开发该模型的存储库包含非正式规范和相关文档,但是这种详细程度可能会一开端让人有些不知所措。

在本文中,咱们首先简要解说组件模型的动因。然后,咱们将建立对其作业办法的直觉,并将其与盛行操作系统(如Windows、macOS和Linux)上的本地代码链接和运转进行比较。本文的方针受众是现已了解本地编译言语(如C、Rust和Go)以及动态链接和进程间通讯等概念的开发人员。鄙人一个部分中,咱们评论一下 eunomia-bpf 社区在组件模型上的一些实践作业,并介绍一种名为WIT(Wasm Interface Type)的文档格局,用于描绘组件模型接口的办法。

动机

WebAssembly规范界说了一种体系结构、渠道和言语无关的可履行代码格局。WebAssembly模块能够从主机运转时导入函数、全局变量等,也能够将这些项导出到主机。但是,在运转时没有规范的办法来兼并模块,也没有规范的办法来跨模块鸿沟传递高档类型(例如字符串和记载)。

实践上,缺少模块组合意味着模块必须在构建时静态链接,而不能在运转时动态链接,而且它们不能以规范、便携的办法与其他模块通讯。组件模型经过在中心WebAssembly之上供给以下三个主要功用来处理这个问题:

  • 接口类型:一种言语无关的办法,用于根据高档类型(如字符串、记载、调集等)界说模块接口。
  • 规范ABI,它指定高档类型怎么以中心WebAssembly的初级类型来表明。
  • 模块和组件链接:一种动态组合模块成为组件的机制。这些组件本身能够组合在一起形成更高档的组件。

这些功用一起允许在没有静态链接的重复和安全漏洞的情况下打包和重用代码。这种重用特别适用于多租户云核算,其间成千上万个模块的代码重复会添加昂贵的存储和内存开支。

本地代码类比

如果您了解本地代码的链接和履行办法,那么您就会遇到“可履行文件”、“进程”和“同享库”等概念。组件模型具有类似的概念,但运用不同的术语。在这里,咱们将为每个本地代码概念匹配其WebAssembly对应物,并考虑它们的类似之处和不同之处。

组件 <-> 可履行文件

Wasm组件就像本地可履行文件相同:慵懒且无状况,等候运用。

模块 <-> 同享库

Wasm模块就像同享库(例如.dll.dylib.so),能够导出和/或导入符号,以便与其他代码链接在一起。与组件相同,模块是慵懒且无状况的,等候运用。

组件实例 <-> 进程

组件的_实例_就像进程;它是组件的加载和运转方法;它是有状况和动态的。像进程相同,几个组件实例能够安排成树,以便每个节点都能够与其直接子节点通讯。与进程相同,组件实例之间不同享内存或其他资源,必须经过某种主机介入办法进行通讯。

请注意,能够从同一可履行文件运转多个进程-有时一起运转-而这些进程不同享状况。相同,从同一组件产生的实例不同享状况。

模块实例 <-> 已加载的同享库

模块的_实例_就像已加载和链接到进程中的库相同。它是有状况和动态的,与组件的其他部分同享内存和其他资源。

请注意,一个给定的模块能够一起实例化到多个组件中,这些实例之间_不会_同享状况。

一些同享库也能够是可履行文件;即它们能够被链接到进程或作为独立进程履行。相同,Wasm模块实例能够链接到组件实例中或由主机运转时独立运转。

Wasm运转时 <-> 操作系统

Wasm运转时(例如wasmtime)类似于操作系统,它担任加载和链接组件(即进程)并在它们之间进行通讯。

但是,与大多数供给少量初级进程间通讯办法的操作系统不同(例如管道、stdin/stdout等),启用组件的Wasm运转时供给了一种根据接口类型和规范ABI的单一高档通讯办法。此办法类似于比如gRPC的RPC协议,但它旨在(并优化)用于本地通讯,而不是经过网络。

对于跨模块鸿沟的组件内通讯,组件模型没有指定规范的ABI。这意味着要在组件内链接的模块必须就恰当的ABI达到一致,这能够是特定于言语的或与言语无关的。

与本地模型相反,其间进程间通讯一般比库调用更麻烦和杂乱,组件模型旨在使组件间和模块间通讯都便利和表达。定论是,您能够将Wasm应用程序分红多个独自的沙盒组件,而不需求比同享同一个沙盒的模块更多的作业。

注意事项

以上评论为简略起见忽略了一些细微差别和破例。例如,盛行操作系统上的进程_能够_同享内存、文件句柄等,虽然它们一般不这样做。相同,未来的 WASI API 可能会允许组件实例之间进行类似的同享。

相同,组件可能有除上述 ABI 之外的其他通讯办法:网络套接字、文件、JavaScript 环境等。详细细节取决于主机运转时所支撑的和授予每个组件的功用。

(借用的)可视化

以下图示托管在组件模型存储库,展现了 Wasm 模块和组件的静态和动态链接,右侧的图示表明加载到组件实例中的模块实例。不过,它相同能够展现本地的静态和动态链接,右侧的图示表明进程及其加载的库。

WebAssembly 组件模型

WebAssembly 组件模型

定论

虽然组件模型相对较新且仍在开发中,但它遵从了几十年来用于安排本地代码的相同形式。模块、组件及其实例化办法促进了代码重用和阻隔,类似于同享库、可履行文件和进程在您喜欢的操作系统中的运用办法。

此外,组件模型经过供给可跨组件鸿沟运用的表达力强、高档别的ABI,改进了本地模型。这使得在坚持代码阻隔的一起轻松重用代码,提高了安全性和可靠性。

在Fermyon,咱们对组件模型感到振奋,因为它对于使Spin微服务安全、高效和易于开发至关重要。Spin现已运用Wasmtime的强大沙盒确保来将服务相互阻隔,并运用wit-bindgen供给高档别、言语无关的API,用于常见功用,如HTTP恳求。未来,咱们将积极贡献于Wasmtime和其他项目,以帮助完成组件支撑,并在它们稳定后采用要害功用用于 Spin。

鄙人一个部分中,咱们希望评论一下 eunomia-bpf 社区在组件模型上面的一些实践作业。eunomia-bpf 社区正在探索 eBPF 和 WebAssembly 相互结合的东西链和运转时: github.com/eunomia-bpf… 。社区关注于简化 eBPF 程序的编写、分发和动态加载流程,以及探索 eBPF 和 Wasm 相结合的东西链、运转时和运用场景等技能。

组件模型的描绘文件 – WIT

由于Wasm组件供给了导入和导出“接口”的功用,因而咱们能够运用一种称为WIT(Wasm Interface Type)的文档格局来描绘这种接口。Wasm接口类型(WIT)格局是一种IDL(接口描绘言语),用于以两种主要办法为WebAssembly组件模型供给东西支撑:

  • WIT是一种开发者友好的格局,用于描绘组件的导入和导出。易于阅读和编写,并为从客户言语生成组件以及在主机言语中运用组件供给基础。
  • WIT包是在组件生态系统中同享类型和界说的基础。作者能够从其他WIT包导入类型,生成组件,发布代表主机嵌入的WIT包,或协作界说跨渠道同享API的WIT界说。

WIT包是WIT文档的调集。每个WIT文档都界说在一个运用文件扩展名wit的文件中,例如foo.wit,并编码为有效的UTF-8字节。每个WIT文档包含一个接口世界的调集。类型能够从包内的同级文档(文件)中导入,还能够经过URL从其他包中导入。

简略来说,WIT文件描绘了Wasm组件的导入和导出以及其他相关信息,包含:

  • 导入和导出的函数
  • 导入和导出的接口
  • 用于上述函数和接口的参数或返回值的类型,包含record(类似于结构体)、variant(类似于Rust的enum,一种tagged union)、union(类似于C的union,untagged union)、enum(类似于C的enum,映射到整数)等。

有关类型的更详细信息,请参见github.com/WebAssembly…

以下是一个WIT文档的示例,其间描绘了:

  • 一个名为my-world的world。能够认为一个world对应一个Wasm组件。
  • 组件内导出了一个名为run的函数,该函数不接受任何参数,也没有返回值。
  • 组件需求导入一个名为host的接口,其需求供给一个名为log的函数,并接收一个名为param的字符串参数。
default world my-world {
  import host: interface {
    log: func(param: string)
  }
  export run: func()
}

怎么在实践项目中运用 WIT 开发 Wasm 组件

鉴于组件模型和 WIT 现已如此出色,那么咱们该怎么运用它们呢?

  • BytecodeAlliance 开发了 wit-bindgen 和 wasm-tools 两个东西,能够帮助咱们生成处理接口、字符串、数组、结构体等杂乱类型的代码,而咱们只需求关怀详细函数的完成即可。
  • 运用传统的 Wasm 东西链(如 clang、rustc)编译生成 Wasm 模块后,再将其转换成 Wasm 组件。

以本文供给的示例为例,将其保存为 test.wit 文件后,运用 wit-bindgen c test.wit -w test.my-world 命令即可生成面向 C 程序的 binding,即以下三个文件:

  • my-world.h,其间包含了咱们写的组件从外部导入的函数的声明(host 接口下的 log 函数),以及咱们需求自己编写的函数的声明(run 函数)。
  • my-world.c,其间包含了一些东西函数的完成,例如操作组件模型中的字符串类型的东西函数。
  • my_world_component_type.o,包含了几个东西 section,用于 wasm-tools 东西将模块转换为组件。咱们需求将此文件链接到咱们的方针文件中。

my-world.h 的内容请参考以下示例:

#ifndef __BINDINGS_MY_WORLD_H
#define __BINDINGS_MY_WORLD_H
#ifdef __cplusplus
extern "C" {
#endif#include <stdlib.h>#include <string.h>#include <stdint.h>#include <stdbool.h>typedef struct {
  char*ptr;
  size_t len;
} my_world_string_t;
// Imported Functions from `host`void host_log(my_world_string_t *param);
// Exported Functions from `my-world`void my_world_run(void);
// Helper Functionsvoid my_world_string_set(my_world_string_t *ret, const char*s);
void my_world_string_dup(my_world_string_t *ret, const char*s);
void my_world_string_free(my_world_string_t *ret);
#ifdef __cplusplus
}
#endif#endif

示例

不同言语编写的 Wasm 组件协同作业

咱们在这个比如中展现了经过wit-bindgen分别用C和Rust来编写组件,而且让他们相互调用,然后在wasmtime这个支撑组件模型的Wasm运转时上运转。

  • github.com/eunomia-bpf…

btf2wit

一个从BTF(BPF Type Format,一种让eBPF程序在内核中根据不同版别进行重定位的调试信息)生成 WIT 描绘文件的小东西,用于运用 Wasm 进行 eBPF 用户态程序的开发

  • github.com/eunomia-bpf…

Rust + wit-bingen

一个用 Rust 编写 eBPF 程序并编译为 Wasm 运转,并运用 btf2wit 生成内核结构体的WIT界说在用户态解析数据的比如:

  • github.com/eunomia-bpf…

关于WIT和组件模型的更多信息

  • 截止至 2023 年 3 月,组件模型现在还很不成熟,目前为止没有任何一个完好支撑组件模型的Wasm运转时。

  • 但wasmtime现在对组件模型供给开始支撑(虽然一堆bug),也因而上面的比如用的是wasmtime。

  • 关于WASI。现在现已有了组件模型运用WASI的规范(preview2),但目前为止没有任何运转时完成了这个规范。所以暂时组件模型里用不了WASI。

参考资料

  • github.com/WebAssembly… github.com/WebAssembly…
  • *2023 年 WebAssembly 技能五大趋势猜测* zhuanlan.zhihu.com/p/597705400
  • webassembly-component-model https://www.fermyon.com/blog/webassembly-component-model
  • *Wasm-bpf: 为云原生 Webassembly 供给通用的 eBPF 内核可编程才能*www.infoq.cn/article/sXr…

本文运用 文章同步助手 同步