查看原文
其他

WASI标准化:一种用在Web外部运行Wasm的系统接口

橙汁 Patract开放平台 2021-10-13
作者|Lin Clark,高级工程师,主要研究 Rust 和 Wasm。
原文|https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/


今天( 2019-03-27),我们宣布开始新的标准化工作——Wasm 系统接口 WASI。


原因:开发人员开始将 Wasm 推向浏览器之外,因为它提供了一种快速,可扩展,安全的方式来在所有计算机上运行相同的代码。

但是我们还没有坚实的基础可以建立。浏览器外部的代码需要一种与系统对话的方式——系统接口。而 Wasm 平台目前还没有该功能。

内容:Wasm 是用于概念计算机的汇编语言,而不是用于物理计算机的汇编语言。这就是为什么它可以在各种不同的机器体系结构上运行的原因。

就像 Wasm 是概念计算机的汇编语言一样,Wasm 需要概念操作系统(而不是任何单一操作系统)的系统接口。这样,它可以在所有不同的操作系统上运行。

这就是 WASI——Wasm 平台的系统接口。

我们的目标是创建一个系统接口,该接口将成为 Wasm 的真正伴侣,并持续时间的考验。这意味着要坚持 Wasm 的关键原则-可移植性和安全性。

我们是:我们正在组织一个 Wasm 分组来专注于标准化WASI(https://wasi.dev/)。我们已经收集了有兴趣的合作伙伴,并正在寻找更多加入的伙伴。

我们,合作伙伴和支持者认为这很重要的一些原因如下:


Mozilla 首席研发官 Sean White:


“ Wasm 已经在改变网络为人们带来新型、引人入胜的内容的方式,并使开发人员和创作者能够在网络上尽力而为。到目前为止,这一直是通过浏览器实现的,但是通过 WASI,我们可以将 Wasm 和 Web 的优势交付给更多的用户,更多的位置,更多的设备,并作为更多体验的一部分。”

Fastly 首席技术官 Tyler McMullen:


“我们将 Wasm 扩展到浏览器之外,作为在边缘云里快速,安全地执行代码的平台。尽管我们的边缘和浏览器之间的环境存在差异,但 WASI 意味着 Wasm 开发人员不必将其代码移植到每个不同的平台上。”

Myles Borins,Node 技术指导委员会主任:


“ Wasm 可以解决 Node 中最大的问题之一——如何获得接近本机的速度并像使用本机模块一样重用 C 和 C ++ 等其他语言编写的代码,同时仍然保持可移植性和安全性。标准化此系统接口是实现这一目标的第一步。”


npm 的联合创始人 Laurie Voss:


“ npm 对潜在的 Wasm 能够扩展 npm 生态系统的功能而感到极大的兴奋,同时极大地简化了使本机代码在服务器端 JavaScript 应用程序中运行的过程。我们期待这一过程的结果。”

WASI 当前有3种实现:

  • Moszilla 的 WasmRuntime wasmtime

(https://github.com/bytecodealliance/wasmtime)。
  • Lucet(https://www.fastly.com/blog/announcing-lucet-fastly-native-webassembly-compiler-runtime),Fastly 的 Wasm runtime。

  • 浏览器 polyfill(https://wasi.dev/polyfill/)。

你可以在此视频中看到 WASI 的实际效果:


如果你想了解有关该系统接口应如何工作的建议的更多信息,请继续阅读。



1


什么是系统接口?


许多人谈论例如 C 之类的语言,这些语言让你可以直接访问系统资源。但是,这不是很真实。

这些语言无权直接在大多数系统上执行打开或创建文件之类的操作。为什么呢?因为这些系统资源(例如文件,内存和网络连接)对于稳定性和安全性来说非常重要。

如果一个程序无意间弄乱了另一个程序的资源,则可能使该程序崩溃。更糟糕的是,如果某个程序(或用户)故意弄乱了另一个程序的资源,它可能会窃取敏感数据。

因此,我们需要一种方法来控制哪些程序和用户可以访问哪些资源。人们很早就意识到了这一点,并想出了一种提供这种控制的方法:保护环安全性

有了保护环安全性,操作系统基本上可以在系统资源周围设置保护屏障,这是内核。内核是唯一要做的比如创建新文件,打开文件或打开网络连接之类的操作的东西。

用户程序在内核之外的用户模式下运行。如果程序想要执行任何操作,例如打开文件,则必须要求内核为其打开文件。


这就是系统调用概念出现的地方。当程序需要让内核执行这些操作之一时,它将要求使用系统调用。这使内核有机会弄清楚哪个用户在访问,然后,它可以查看该用户在打开文件之前是否有权访问该文件。

在大多数设备上,这是代码可以通过系统调用访问系统资源的唯一方法。


操作系统使系统调用可用。但是,如果每个操作系统都有自己的系统调用,那么你是否需要为每个操作系统使用相同版本的代码吗?幸运的是,你不需要。

大多数语言都提供标准库。进行编码时,程序员无需知道他们要针对的系统,他们只是使用界面。

然后,在编译时,你的工具链会根据你要定位的系统来选择要使用的接口实现。此实现使用操作系统 API 中的功能,因此它特定于系统。

这是系统接口的 printf 用处。例如,为 Windows 机器编译时可以使用Windows API 与该机器进行交互。如果要针对 Mac 或 Linux 进行编译,它将使用 POSIX。


但是,这给 Wasm 带来了一个问题。

使用 Wasm,即使在编译时,你也不知道要针对哪种操作系统。因此,你不能在标准库的 Wasm 实现中使用任何单个 OS 的系统接口。


我之前已经讨论过 Wasm 是如何用于概念计算机而不是真实计算机的汇编语言。以同样的方式,Wasm 需要用于概念性操作系统的系统接口,而不是真正的操作系统。

但是,即使没有适当的系统接口,也已经有 runtime 可以在浏览器外部运行 Wasm。他们是怎么做到的呢?让我们来看看。



2


如今,Wasm 如何在浏览器之外运行?


产生 Wasm 的第一个工具是 Emscripten。它在网上模拟了特定的 OS 系统接口 POSIX。这意味着程序员可以使用 C 标准库(libc)中的函数。

为此,Emscripten 创建了自己的 libc 实现。该实现分为两部分——一部分被编译到 Wasm 模块中,另一部分用 JS 粘合代码实现。然后,此 JS 粘合将调用浏览器,然后浏览器将与 OS 进行通信。


早期的 Wasm 大部分代码都是使用 Emscripten 编译的。因此,当人们开始希望在没有浏览器的情况下运行 Wasm 时,他们首先通过运行 Emscripten 编译的代码开始。

因此,这些 runtime 需要为 JS 粘合代码中的所有这些功能创建自己的实现。不过这里有个问题。该 JS 粘合代码提供的接口不是设计为标准的,甚至不是面向公众的接口,那不是它要解决的问题。

例如,例如,对于在 API 中被设计为公共接口的函数,该函数被称为 read,而 JS 粘合代码改为使用 _system3(which, varargs)

第一个参数,which 是一个整数,始终与名称中的数字相同(本例中为3)。

第二个参数,varargs 是要使用的参数。varargs 之所以这样称呼,是因为你可以拥有不定数量的它们。但是 Wasm 没有提供将可变数量的参数传递给函数的方法。因此,参数是通过线性内存传递的。这不是类型安全的,并且比如果可以使用寄存器传递参数的情况要慢。

这对于运行在浏览器中的 Emscripten 很好。但是现在,runtime 将其视为事实上的标准,实现了自己的 JS 粘合代码版本。他们正在效仿 POSIX 模仿层的内部细节。

这意味着他们正在重新实现基于 Emscripten 约束有意义的选择(例如将参数作为堆值传递),即使这些约束不适用于他们的环境。


如果我们要建立一个可持续数十年的 Wasm 生态系统,我们需要坚实的基础。这意味着我们事实上的标准不能是模仿的模仿。

但是,我们应该采用什么原则?


3


Wasm系统接口需要遵循哪些原则?


Wasm 中包含两个重要的原则:

  • 可移植性
  • 安全性

在转向浏览器外用例时,我们需要保持这些关键原则。

实际上,POSIX 和 Unix 的安全性访问控制方法还不能完全解决问题。让我们看看它们的不足之处。


可移植性


POSIX 提供源代码可移植性。你可以使用不同版本的 libc 编译相同的源代码,以针对不同的计算机。


但是 Wasm 需要超越这一步骤。我们需要能够编译一次并跨一大堆不同的机器运行。我们需要可移植的二进制文件。


这种可移植性使向用户分发代码变得更加容易。

例如,如果 Node 的本机模块是用 Wasm 编写的,那么当用户使用本机模块安装应用程序时,用户将无需运行 node-gyp,开发人员也无需配置和分发数十个二进制文件。

安全性


当一行代码要求操作系统执行某些输入或输出时,操作系统需要确定执行代码要求的操作是否安全。

操作系统通常使用基于所有权和组的访问控制来处理此问题。

例如,程序可能要求操作系统打开文件,用户具有他们有权访问的一组文件。
当用户启动程序时,该程序代表该用户运行。如果用户有权访问文件(是因为他们是所有者,还是因为他们在具有访问权限的组中),那么该程序也具有相同的访问权限。


这样可以保护用户彼此之间,当开发早期的操作系统时,这很有意义。系统通常是多用户的,并且管理员控制安装了什么软件。因此,最主要的威胁是其他用户偷看你的文件。

那已经改变了。系统现在通常是单用户,但是它们正在运行的代码会引入许多其他可信度未知的第三方代码。现在最大的威胁是你自己正在运行的代码将对你不利。

例如,假设你在应用程序中使用的库获得了一个新的维护程序(这在开放源代码中经常发生)。那个维护者可能会引起你的兴趣……或者他们可能是坏人之一。而且,如果他们有权在你的系统上执行任何操作(例如,打开你的任何文件并通过网络发送它们),那么他们的代码可能会造成很大的损失。


这就是为什么使用可以直接与系统对话的第三方库很危险的原因。Wasm 的安全性方法不同,Wasm 被沙盒化。

这意味着代码无法直接与操作系统对话。但是,它如何处理系统资源呢?主机(可能是浏览器,也可能是 wasm runtime)将函数放在代码可以使用的沙盒中。

这意味着主机可以限制程序在逐个程序的基础上可以执行的操作,它不仅让程序代表用户执行操作,还可以使用用户的完全权限来调用任何系统调用。

仅仅拥有一种沙盒机制并不能保证系统本身的安全性——主机仍然可以将所有功能都放到沙盒中,在这种情况下我们的状况不会更好,但它至少为主机提供了以下选择:创建一个更安全的系统。

在我们设计的任何系统接口中,我们都需要坚持这两个原则。可移植性使开发和分发软件变得更加容易,并且必须为主机提供工具以保护自己或他们的用户。

4


该系统接口应该是什么样?


给定这两个关键原则,Wasm 系统接口的设计应该是什么?这就是我们在标准化过程中要解决的问题。不过,我们确实有一个建议:

  • 创建一组模块化的标准接口;
  • 从标准化最基本的模块 wasi-core 开始。


什么是 wasi-core?

wasi-core 将包含所有程序需要的基础知识。它将覆盖与 POSIX 相同的大部分内容,包括文件,网络连接,时钟和随机数之类的内容。

对于许多事情,它将采用与 POSIX 非常相似的方法。例如,它将使用 POSIX 的面向文件的方法,在这种方法中,你可以进行诸如 open,close,read 和 write 之类的系统调用,而所有其他操作基本上都可以在顶部进行增强。

但是 wasi-core 不会涵盖 POSIX 所做的一切。例如,流程概念不能清楚地映射到 Wasm 上。除此之外,说每个 Wasm 引擎都需要支持 fork 之类的流程操作是没有意义的,但是我们也想使 fork 标准化成为可能

这就是模块化方法的用武之地。这样,我们可以获得良好的标准化覆盖率,同时仍然允许细分平台仅使用 WASI 有意义的部分。


像 Rust 这样的语言将在其标准库中直接使用 wasi-core。例如,Rust 的 open 实现是通过 __wasi_path_open 在将其编译为 Wasm 时进行调用来实现的。

对于 C 和 C ++,我们创建了一个 wasi-sysroot(https://github.com/CraneStation/wasi-sysroot),它根据 wasi-core 函数实现了 libc。


我们希望像 Clang 这样的编译器准备好与 WASI API 交互,并且像 Rust 编译器和 Emscripten 这样的完整工具链也可以将 WASI 用作其系统实现的一部分。

用户代码如何调用这些 WASI 函数?运行代码的 runtime 将 wasi-core 函数作为导入传递。


这给我们带来了可移植性,因为每个主机都可以有自己的 wasi-core 实现,该实现是专门为其平台编写的——从 Mozilla 的 wasmtime 和 Fastly 的 Lucet 之类的 Wasm runtime,到 Node 甚至是浏览器。

这也给我们提供了沙盒管理功能,因为主机可以逐个程序选择要传入的 wasi-core 函数(即,允许哪个系统调用),这样可以保持安全性。

WASI 为我们提供了进一步扩展此安全性的方法。它从基于功能的安全性中引入了更多概念。

传统上,如果代码需要打开文件,则会使用字符串(即路径名)进行调用。然后,操作系统进行检查以查看代码是否具有权限(基于启动程序的用户)。

传统上,如果代码需要打开文件,它将使用字符串(路径名)调用 open, 然后,操作系统进行检查以查看代码是否具有权限(基于启动程序的用户)。

使用 WASI,如果要调用需要访问文件的函数,则必须传入一个文件描述符,该描述符具有附加的权限。这可以用于文件本身,也可以用于包含文件的目录。

这样,你就不会有随机要求打开的代码 /etc/passwd。相反,该代码只能在传递给它的目录上操作。

这样就可以安全地给沙盒代码更多地访问不同的系统调用的权限,因为这些系统调用的功能可能会受到限制。

而且这是逐模块进行的。默认情况下,模块无权访问文件描述符。但是,如果一个模块中的代码具有文件描述符,则可以选择将该文件描述符传递给它在其他模块中调用的函数。或者,它可以创建文件描述符的更多受限版本以传递给其他功能。

因此,runtime 将应用程序可以使用的文件描述符传递给顶级代码,然后根据需要在系统的其余部分传播文件描述符。

这使 Wasm 更接近最小特权原则,在该原则下,模块只能访问执行其工作所需的确切资源。

这些概念来自面向能力的系统,例如 CloudABI 和 Capsicum。面向功能的系统的一个问题是,通常很难向其移植代码。但是我们认为这个问题可以解决。如果代码已经使用带有相对文件路径的  openat ,则编译代码就可以了。

如果代码使用 open 并迁移到 openat 样式是过多的前期投资,则 WASI 可以提供增量解决方案。使用libpreopen(https://github.com/musec/libpreopen),你可以创建应用程序合法需要访问的文件路径的列表。然后,您可以使用 open,但只能与这些路径一起使用。


5


下一步是什么?


我们认为 wasi-core 是一个好的开始。它保留了 Wasm 的可移植性和安全性,为生态系统提供了坚实的基础。

但是,在 wasi-core 完全标准化之后,我们仍然需要解决一些问题。这些问题包括:

  • 异步I / O
  • 看文件
  • 文件锁定 


About Patract


Patract 为波卡 Wasm 合约生态的平行链和 DApp 开发提供解决方案。我们帮助社区平行链设计和开发链上合约模块和 Runtime 支持,并且为 DApp 开发者提供覆盖开发、测试、调试、部署、监控、数据提供和前端开发等阶段的全栈工具和服务支持。


How to join Patract



1.对于合约开发者,可以访问官网 (https://patract.io),熟悉测试链和工具套件。欢迎加入官方开发群:
Element(https://app.element.io/#/room/#PatractLabsDev:matrix.org)
Discord(https://discord.gg/wJ8TnTfjcq)
 
2.对于将要集成 Wasm 合约功能的平行链项目方,或者使用 Wasm 合约开发的 DApp 项目方,欢迎加入 Patract 开放平台: https://open.patract.io
 
3.对于用户,欢迎加入:
Telegram(https://t.me/patract)
Twitter(https://twitter.com/PatractLabs)
 
4.对于求职者,我们在招聘区块链开发工程师、前端/全栈开发工程师、产品经理、开发者运营等岗位,可以联系 sean@patract.io

扫码加入Patract 微信开发群


往期精彩:
// 与Solidity相比,用ink!写智能合约的体验更丝滑
// Patract走进Substrate Seminar,介绍Wasm合约工具套件
// Wasm技术要达到什么程度才能打破生态板结?


: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存