开放流程的生命周期

为了从端到端地全面了解如何访问 Fuchsia,本文档详细介绍了在执行打开文件等简单操作时使用的每个层。请务必注意,所有这些层都存在于用户空间中;即使在与文件系统服务器和驱动程序交互时,内核也仅用于将消息从一个组件传递到另一个组件。

对以下对象进行调用:

open(“foobar”);

该请求会去哪里?

标准库:定义了“open”

“open”调用是由标准库提供的函数。对于 C/C++ 程序,这通常在 unistd.h 中声明,后者在 libfdio 中有后备定义。对于 Go 程序,Go 标准库中有一个等效(但各不相同)的实现。对于每种语言和运行时,开发者可以选择自己的“开放”定义。

在单体内核中,open 是系统调用周围的轻量级 shim,内核可以处理路径解析、重定向等。在该模型中,内核需要根据调用方的外部了解来调解对资源的访问。不过,Zircon 内核有意没有此类系统调用。相反,客户端通过通道访问文件系统;进程初始化时,为其提供命名空间,这是“绝对路径”->“句柄”映射的表。通过此命名空间映射定向请求,即可打开从进程内部访问的所有路径。

不过,在此示例中,如果请求打开“foobar”时使用了相对路径,因此来电可以通过表示当前工作目录的路径(其本身表示为绝对路径和句柄)发送。

标准库负责获取一个(或多个句柄)并使其看起来像文件描述符。因此,“文件描述符表”是客户端进程中存在的概念(如果客户端选择使用自定义运行时,它们可以仅以句柄的形式查看其资源 -“文件描述符”封装是可选的)。

但是,这会产生一个问题:如果给定一个对文件、套接字、管道等的文件描述符,标准库如何才能让所有这些资源在功能上保持一致?该客户端如何知道要通过这些句柄发送哪些消息?

Fdio 银行

名为 fdio 的库负责为各种资源(文件、套接字、服务、管道等)提供统一接口。该层定义了一组可用于各种协议支持的文件描述符的函数,例如读取、写入、打开、关闭、查找等。每个受支持的协议都负责提供客户端代码,以解读其互动的细节。例如,套接字为客户端提供多个句柄;一个用于处理数据流,另一个则用作控制平面。相比之下,文件通常仅使用一个通道进行控制和数据(除非为了请求内存映射而执行了额外的工作)。虽然套接字和文件都会收到对 openwrite 的调用,但它们需要以不同的方式解释这些命令。

在本文档中,我们将重点介绍文件系统客户端使用的主要协议:FIDL

FIDL

调用 open("foo") 的程序会调用标准库,找到与当前工作目录对应的“fdio”对象,并且需要向远程服务器发送请求以“please open foo”。如何做到这一点?该程序包含以下工具:

  • 一个或多个表示与 CWD 的连接的句柄
  • zx_channel_write:可以(通过通道)发送字节和句柄的系统调用
  • zx_channel_read:可以(通过通道)接收字节和句柄的系统调用
  • zx_object_wait_one:可以等待句柄可读取 / 写入的系统调用

使用这些基元,客户端可以将一条消息写入 CWD 句柄上的文件系统服务器,服务器可以读取该消息,然后在写回客户端时以“成功”或“失败消息”进行响应。当服务器处理过程中,要弄清楚要实际打开的内容时,客户端不一定要先选择等待时间,然后再尝试读取状态消息。

请务必让客户端和服务器在传输或接收消息时就对这 N 个字节和 N 个句柄的解释达成一致:如果二者之间存在差异,则消息可能会被丢弃(甚至更糟,变成意外行为)。此外,如果此协议允许客户端对服务器进行任意控制,则这个通信层很容易被利用。

FIDL IO 协议介绍了这些字节和句柄在两个实体之间传输时的实际含义。该协议描述了“预期句柄数”“枚举操作”和“数据”等内容。在本例中,open("foo") 会创建一条 Open 消息,并将 FIDL 消息的“data”字段设置为字符串“foo”。此外,如果有任何标志被传递到 open 函数(例如 O_RDONLY, O_RDWR, O_CREAT 等),这些标志将放在 FIDL 结构的“arg”字段中。但是,如果操作发生更改(例如更改为 write),则此消息的解释将被更改。

该层的字节协议完全相同,因为它允许在截然不同的运行时之间进行通信:理解 FIDL 的进程可以在 C、C++、Go、Rust、Dart 程序(及其他)之间轻松透明地通信

libfidl 包含用于 FIDL 的 C/C++ 实现的客户端和服务器端代码,并负责自动验证两端的输入和输出。

对于 open 操作,FIDL 协议要求客户端创建一个通道,并将一端(作为句柄)传递给服务器。事务完成后,此渠道可以用作与打开的文件通信的机制,就像以前可以与“CWD”句柄通信一样。

通过设计协议,确保 FIDL 客户端提供句柄而不是服务器,因此通信更适合使用流水线。对 FIDL 对象的访问可以是异步的;对 FIDL 对象的请求可以在对象实际打开之前传输。 capabilities这种模型允许客户端立即开始发送请求,然后在组件就绪后对其进行响应,而不是阻止组件完成其启动过程并开始处理请求。如需详细了解此行为如何应用于功能路由,请参阅开放协议的生命周期

概括来说,一个“open”调用经过标准库,对“CWD”fdio 对象执行操作,该对象将请求转换为 FIDL 消息,后者使用 zx_channel_write 系统调用发送到服务器。客户端可以选择使用 zx_object_wait_one 等待服务器响应,也可以继续异步处理。无论采用哪种方式,系统都会创建一个通道。该通道的一端与客户端存在,另一端则传输到“服务器”。

文件系统:服务器端

调度

从通道的客户端端传输消息后,消息会位于通道的服务器端,等待读取。服务器由“将句柄保存在通道另一端的任何人”标识 - 它可以与客户端位于同一(或不同的)进程中,使用与客户端相同(或不同)的运行时,并且使用与客户端相同(或不同的语言)编写。通过使用商定的传输格式,进程间依赖项会在信道上发生的瘦通信层遭遇瓶颈。

在未来的某个时间点,CWD 句柄的服务器端端将需要读取客户端传输的消息。此过程不是自动进行的,服务器需要有意识地等待接收句柄上的传入消息,在本例中是“当前工作目录”句柄。当服务器对象(文件、目录、服务等)打开时,其句柄向服务器端 Zircon 端口注册,该端口会等待其底层句柄变为可读取状态(表示消息已到达)或关闭状态(意味着它们永远不再接收更多消息)。此对象称为调度程序,用于将传入请求分派给适当的句柄。它负责将传入消息以及先前提供的一些表示打开连接的“iostate”重定向到回调函数。

对于使用 libfs 的 C++ 文件系统,此回调函数称为 vfs_handler,它会接收一些关键信息:

  • FIDL 消息,由客户端提供(或由服务器人为构建,如果句柄已关闭,则显示为“关闭”消息)
  • 表示与句柄的当前连接的 I/O 状态(作为“iostate”字段传递,如前所述)。

vfs_handler 可以解释 I/O 状态以推断更多信息:

  • 文件(如果使用了 readdir 则为目录内)中的搜寻指针
  • 用于打开底层资源的标志
  • Vnode,表示底层对象(可在多个客户端或多个文件描述符之间共享)

这个处理程序函数包含这些信息,相当于一个大型“切换/用例”表,根据客户端提供的“操作”字段,将 FIDL 消息重定向到相应的函数。在 Open 案例中,Open 序数被注意到为操作,因此 (1) 需要一个句柄,并且 (2)“data”字段(“foo”)被解释为路径。

VFS 层

在 Fuchsia 中,“VFS 层”是一个独立于文件系统的代码库,可以分派和解释服务器端消息,并在适当情况下调用底层文件系统中的操作。值得注意的是,该层完全是可选的;如果文件系统服务器不想链接到此库,就没有义务使用该库。若要成为文件系统服务器,进程必须仅理解 FIDL 传输格式。因此,某种语言中可能会有任意数量的“VFS”实现。目前有以下实现:

  • 树内 C++ VFS:供 Fuchsia 的“主”文件系统 minfs 和 blobfs 使用。它目前拥有所有 VFS 实现中最多的功能,但也可能是最难使用的功能。
  • 树内 Rust VFS:某些 Rust 文件系统(包括 fat32 实现)使用它。该版本较新,目前包含的功能比 C++ 实现少。
  • SDK C++ VFS:面向 SDK 用户的“树内”C++ 版本的简化版本。这最常用于服务发现等较为简单的用途。

VFS 层定义了可以路由到底层文件系统的操作接口,包括:

  • 读取/写入 Vnode
  • 查找/创建 Vnode(按名称)/从父 Vnode 解除关联
  • 按名称重命名/关联 Vnode
  • 以及更多其他功能

如需实现文件系统(假设开发者希望使用共享 VFS 层),只需定义实现此接口的 Vnode 并链接到 VFS 层即可。这样一来,只需极少的工作量即可实现“路径遍历”和“文件系统装载”等功能,并且几乎没有重复的代码。为了不与文件系统有关,VFS 层对文件系统使用的底层存储空间没有预先设想的概念:文件系统可能需要访问块存储设备、网络,或者只是存储数据的内存,但 VFS 层只处理对路径、数据字节数组和 vnode 执行操作的接口。

步道散步

为了打开服务器端资源,需要为服务器提供一些起点(由调用的句柄表示)和一个字符串路径。此路径按“/”字符拆分为多个段,系统会通过回调底层文件系统“查找”每个组件。如果查询成功返回一个 vnode,并且检测到另一个“/”区段,则此过程会持续到 (1) lookup 找不到组件;(2) 路径处理到达路径中的最后一个组件;或 (3) lookup 找到装载点 vnode,即具有附加“远程”句柄的 vnode。目前,我们将忽略装载点虚拟节点,但有关文件系统装载的部分对它们进行了讨论。

假设 lookup 成功找到了“foo” Vnode。文件系统服务器将继续调用 VFS 接口“Open”,以验证是否可以使用提供的标志访问请求的资源,然后调用“GetHandles”之前,询问底层文件系统是否还需要额外的句柄才能与 Vnode 交互。假设客户端同步请求“foo”对象(通过默认的 POSIX 打开调用隐含其中),与“foo”交互所需的任何其他句柄都会打包到一个小型 FIDL 说明对象中,并传回客户端。或者,如果“foo”无法打开,系统仍会返回 FIDL 说明对象,但“status”字段会设为一个错误代码(表示失败)。我们假设“foo”打开操作成功服务器将继续为“foo”创建“iostate”对象,并向调度程序注册该对象。这样,服务器今后便可处理对“foo”的调用。“Foo”已打开,客户端现在已准备好发送其他请求。

从客户端的角度来看,在“Open”调用开始时,路径和句柄组合会通过 CWD 句柄传输到远程文件系统服务器。由于调用是同步的,因此客户端继续等待句柄上的响应。服务器在正确找到、打开并初始化此文件的 I/O 状态后,会发回“成功”FIDL 说明对象。客户端将读取此对象,确定调用已成功完成。此时,客户端可以创建一个表示“foo”句柄的 fdio 对象,通过文件描述符表中的条目来引用它,然后将 fd 返回给调用原始“open”函数的人员。此外,如果客户端想要向“foo”发送任何其他请求(例如“read”或“write”),则可以使用与已打开文件的连接直接与文件系统服务器通信 - 在以后的请求中无需通过“CWD”进行路由。

Open 过程:图表

+----------------+
| Client Program |
+----------------+
|   fd: x    |   fd: y    |
| Fdio (FIDL)| Fdio (FIDL)|
+-------------------------+
| '/' Handle | CWD Handle |
+-------------------------+
      ^            ^
      |            |
Zircon Channels, speaking FIDL                   State BEFORE open(‘foo’)
      |            |
      v            v
+-------------------------+
| '/' Handle | CWD Handle |
+-------------------------+
|  I/O State |  I/O State |
+-------------------------+
|   Vnode A  |   Vnode B  |
+-------------------------+
| Filesystem Server |
+-------------------+


+----------------+
| Client Program |
+-------------------------+
|   fd: x    |   fd: y    |
| Fdio (FIDL)| Fdio (FIDL)|
+-------------------------+
| '/' Handle | CWD Handle |   **foo Handle x2**
+-------------------------+
      ^            ^
      |            |
Zircon Channels, speaking FIDL                   Client Creates Channel
      |            |
      v            v
+-------------------------+
| '/' Handle | CWD Handle |
+-------------------------+
|  I/O State |  I/O State |
+-------------------------+
|   Vnode A  |   Vnode B  |
+-------------------------+
| Filesystem Server |
+-------------------+


+----------------+
| Client Program |
+-------------------------+
|   fd: x    |   fd: y    |
| Fdio (FIDL)| Fdio (FIDL)|
+-------------------------+--------------+
| '/' Handle | CWD Handle | ‘foo’ Handle |
+-------------------------+--------------+
      ^            ^
      |            |
Zircon Channels, speaking FIDL                  Client Sends FIDL message to Server
      |            |                            Message includes a ‘foo’ handle
      v            v                            (and waits for response)
+-------------------------+
| '/' Handle | CWD Handle |
+-------------------------+
|  I/O State |  I/O State |
+-------------------------+
|   Vnode A  |   Vnode B  |
+-------------------------+
| Filesystem Server |
+-------------------+


+----------------+
| Client Program |
+-------------------------+
|   fd: x    |   fd: y    |
| Fdio (FIDL)| Fdio (FIDL)|
+-------------------------+--------------+
| '/' Handle | CWD Handle | ‘foo’ Handle |
+-------------------------+--------------+
      ^            ^
      |            |
Zircon Channels, speaking FIDL                  Server dispatches message to I/O State,
      |            |                            Interprets as ‘open’
      v            v                            Finds or Creates ‘foo’
+-------------------------+
| '/' Handle | CWD Handle |
+-------------------------+
|  I/O State |  I/O State |
+-------------------------+-------------+
|   Vnode A  |   Vnode B  |   Vnode C   |
+------------------------------+--------+
| Filesystem Server |
+-------------------+


+----------------+
| Client Program |
+-------------------------+
|   fd: x    |   fd: y    |
| Fdio (FIDL)| Fdio (FIDL)|
+-------------------------+--------------+
| '/' Handle | CWD Handle | ‘foo’ Handle |
+-------------------------+--------------+
      ^            ^          ^
      |            |          |
Zircon Channels, FIDL         |                   Server allocates I/O state for Vnode
      |            |          |                   Responds to client-provided handle
      v            v          v
+-------------------------+--------------+
| '/' Handle | CWD Handle | ‘foo’ Handle |
+-------------------------+--------------+
|  I/O State |  I/O State |  I/O State   |
+-------------------------+--------------+
|   Vnode A  |   Vnode B  |    Vnode C   |
+------------------------------+---------+
| Filesystem Server |
+-------------------+


+----------------+
| Client Program |
+-----------------------------+----------+
|   fd: x    |   fd: y    |    fd: z     |
| Fdio (FIDL)| Fdio (FIDL)|  Fdio (FIDL) |
+-------------------------+--------------+
| '/' Handle | CWD Handle | ‘foo’ Handle |
+-------------------------+--------------+
      ^            ^           ^
      |            |           |
Zircon Channels, speaking FIDL |                  Client recognizes that ‘foo’ was opened
      |            |           |                  Allocated Fdio + fd, ‘open’ succeeds.
      v            v           v
+-------------------------+--------------+
| '/' Handle | CWD Handle | ‘foo’ Handle |
+-------------------------+--------------+
|  I/O State |  I/O State |  I/O State   |
+-------------------------+--------------+
|   Vnode A  |   Vnode B  |    Vnode C   |
+------------------------------+---------+
| Filesystem Server |
+-------------------+