Fuchsia 的命名空间
命名空间是 Fuchsia 中文件访问与服务发现的基石。
定义
命名空间是文件、目录、套接字、服务、设备和其它由环境提供给某一组件的命名对象之间的复合层级结构。
下面我们展开进行一些解释。
对象已被命名:包含 对象 的命名空间能被枚举或通过名称访问,这与列出一个目录或打开一个文件非常相似。
复合层级结构:命名空间是一个对象组成的 树,这棵 树 是由其它命名空间的对象 组合 在一起的子树构成的一个复合结构,每个组成部分都按照规则约定了一个路径前缀。
每个组件的命名空间:每个组件接收一个属于它自己的,根据其需求定制的命名空间。组件同样能够将它自己的对象发布并包含到其它的命名空间中去。
由环境构建:如果某个环境实例化了一个组件,那么那个组件需要在其作用域内为那个组件构建一个合适的命名空间。
命名空间同样能独立于组件之外被创建和使用,即使本文档主要聚焦在与组件绑定的典型用法之上。
命名空间实战
你可能已经花了一些时间来探索 Fuchsia 的命名空间——它们无处不在。如果你在命令提示符中敲下 ls /
,你会发现该终端命名空间下的一系列可访问的对象。
与其他操作系统不同,Fuchsia 并没有「根文件系统」。正如先前所说的,命名空间是为每一个组件定义的,而非全局定义或为每个进程定义。
下面有一些很有趣的概念:
- 不存在全局的「根」命名空间。
- 不存在「运行在切换过根目录(chroot 命令)的环境中」的概念,因为事实上每个组件 都有它自己私有的「根」。
- 组件的命名空间是按照其需求定制的。
- 对象的路径在跨越命名空间时可能并不起作用。
- 一个进程可能会同时访问多个不同的命名空间。
- 用于控制文件访问的机制同样可以用于控制服务和其它命名对象的访问,前提是在同一个组件上。
对象
命名空间中的内容被称为对象。它们分为以下几种:
- 文件:包含二进制数据的对象。
- 目录:包含其它对象的对象。
- 套接字:打开时能建立连接的对象,与命名管道相似。
- 服务:打开时能提供 FIDL 服务的对象。
- 设备:提供硬件资源访问的对象。
访问对象
为了访问一个命名空间中的对象,你必须已经拥有另外一个对象。一个组件,通常是在 命名空间转移 时才在其命名空间作用域内接受对象的管道处理。
同样,你也能通过实现合适的 FIDL 协议来无中生有地创造一个新对象。
给出一个对象的管道,通过传递一个包含某个对象的相对路径表达式(以此指明子对象)的 FIDL 信息,就能打开该对象的一个子对象的管道。这与打开一个目录中的文件非常相似。
注意,你只能访问那些能从你已经访问过的对象处访问的对象。此处不涉及环境权限(Ambient Authority)。
下面我们将介绍对象名和路径是如何构建的。
对象名
一个对象名是一个本地的唯一标签,用于在某一容器(如目录)内定位一个对象。注意,对象名是其容器的子对象表中的属性,而非该对象本身的属性。
例如,猫
在某个 Open()
请求的接收器内标出了一个毛绒绒的对象。
对象从根本上就被设计成无名的,但是它们能被其它对象用名字来调用。
对象名由具有以下规定的八位字节二进制字符串(任意的字节序列)表示:
- 最短为 1 字节
- 最长为 255 字节
- 不包含 NUL(0 值字节)
- 不包含
/
- 不包含
.
或..
- 逐比特对比是否相等(即大小写敏感)
对象名是容器的 Open()
方法的合法参数。详见 FIDL 协议。
为使其具有可读性,对象名可以使用 UTF-8 进行编解码,但是这一特性并非强制。
因此,由客户端负责决定如何将含有非法、无法显示、有歧义的字符序列呈现给用户。
对象的相对路径表达式
某个对象的相对路径表达式是一个对象名或一个以 /
为分隔符的一连串对象名,以此在一系列嵌套的容器内定位一个对象(例如一个目录)。
举个例子,房子/盒子/猫
标示出了在某个未指定的 Open()
请求地接收器中的一个 房子
里的一个 盒子
里的一个毛绒绒的对象。
一个对象相对路径表达式总是向命名空间的更深处移动。值得注意的是,命名空间并不直接支持由里向外地访问容器(例如,使用 ..
),但是这个功能可以由客户端进行部分模拟(见下文)。
对象相对路径表达式具有以下的额外约束:
- 最小长度为 1 个字节
- 最大长度为 4095 个字节
- 不以
/
作为开始或结束 - 每一部分均为合法的对象名
- 逐比特对比是否相等(即大小写敏感)
对象相对路径表达式是容器的 Open()
方法的合法参数。详见 FIDL 协议。
客户端解释的路径表达式
客户端解释的路径表达式是对象路径表达式的泛化,为了兼容那些需要有类似根文件结构的接口,客户端程序可以根据需要模拟出一些特性。
虽然这些特性超出了 Fuchsia 命名空间协议的范围,但是它们经常用到,因此我们也会在此进行描述。
- 一个客户端可能会指定它的某一个命名空间作为「根」。这个命名空间就使用
/
表示。 - 一个客户端可能会通过增加一个
/
来表示与根命名空间的相对路径。 - 一个客户端可能会构建一个从容器内向外部移动的路径,方法是通过一个进程(称为客户端规范化),使用
..
来将路径各部分折叠到一起(假设容器的路径已知)。 - 这些特性可以组合到一起。
例如,/某地/房子/盒子/../沙发/猫
指明了在某个客户端定义为「根」容器的 某地/房子/沙发/猫
处的一只 猫
。
包含有这些特性的客户端解释的路径表达式并不是容器的 Open()
方法的合法参数;它们必须先根据命名空间的协议进行翻译。详见 FIDL 协议。
例如,fdio
在文件操作 API,例如 open()
,stat()
,unlink()
中实现了带有 ..
的客户端路径表达式。
命名空间转移
当一个组件在一个环境中被实例化(如,它的进程启动了),它会接受一张含有一个或多个命名空间路径前缀与对象操作之间的映射表。
表中的路径前缀按照约定将它相关的对象的预期作用进行了编码。例如, pkg
前缀需要与一个目录对象相连,目录中包含了这个组件自己的二进制文件和资源文件。
命名空间约定
这一节描述了 Fuchsia 上运行的典型组件的命名空间的常规设计。
先前的内容表明组件命名空间是如何组织的取决于组件的角色、类型、身份、作用域、与其他组件的关系以及权限。查看 沙盒 以了解是如何使用命名空间来为组件创建沙盒的。
更多关于某组件能从环境接收到的命名空间的信息,请查看关于你正在实现的组件的类型的文档。
典型对象
一个组件的命名空间一般可能含有以下典型对象:
- 组件包中包含的仅读可执行文件和资源
- 私有的本地持久存储
- 私有的临时存储
- 系统、组件框架或启动它的客户端提供给组件的服务
- 设备节点(驱动和其它特权组件)
- 配置信息
典型目录结构
pkg/
:当前程序包中的内容bin/
:包中的可执行二进制文件lib/
:包中的共享仓库data/
:包中的数据,例如资源文件
data/
:本地的持久存储(可读写,包私有)tmp/
:临时存储(可读写,包私有)svc/
:提供给组件的服务fuchsia.process.Launcher
:启动进程fuchsia.logger.Log
:日志信息vendor.topic.Interface
:vendor 定义的服务
dev/
:设备树(需要时,特权组件可见的相关部分)class/
, ……
hub/
:系统自检。见 Hub(仅特权组件)config/
:组件的配置数据
命名空间成员
下面是一些 Fuchsia 命名空间协议进行交互或对协议提供支持的概念的描述。
文件系统
文件系统使文件在命名空间中可用。
文件系统是一个能从其他命名空间中发布文件类型的对象的一个简单组件。
服务
服务存在于命名空间中。
服务是一种常见的对象,它提供了对 FIDL 协议的实现,可以在命名空间发现服务的存在。
服务在命名空间中的路径通常是服务名与 svc
分支相组合,通过服务名,组件能够访问该服务的实现。
举个例子,Fuchsia 的默认日志服务叫做 fuchsia.logger.Log
,它在命名空间中的位置就是 svc/fuchsia.logger.Log
。
组件
组件使用并扩展了命名空间。
组件是一个已经在某个环境中实例化并被给予了一个命名空间的可执行程序对象。
一个组件从以下两个方面在 Fuchsia 命名空间中发挥作用:
它能使用命名空间从环境中接收的对象,尤其是能访问它自己包中的内容和获得(从外部)的服务。
它能以命名空间的形式通过其环境向外发布对象,环境会根据请求使其它组件能够获取到发布的对象。这就是服务是如何被组件实现的。
环境
环境构建了命名空间。
环境是组件的容器。每个环境负责为其中的组件 构建 命名空间。
环境决定组件能访问什么对象,以及组件根据名称对服务的请求将如何绑定到特定的实现上。
配置
组件可能会有多种配置信息,这取决于它们的 组件清单 中列出的功能特性。组件清单在命名空间的 /config
入口中以文件的形式存在,它由组件的特性集定义。