RFC-0041:支持统一服务和设备 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 介绍服务的概念,即一系列协议,其中可能有一个或多个协议集合。 |
作者 | |
提交日期(年-月-日) | 2019-04-08 |
审核日期(年-月-日) | 2019-04-23 |
摘要
介绍服务的概念,即一组协议,其中 该集合可能有一个或多个实例。
设计初衷
如今,在组件框架内,服务被定义为
且该协议只能存在一个实例,
位于 /svc
下的进程的命名空间。
这使我们无法描述更复杂的关系:
- 一种以两种不同形式表示的服务,具体取决于
例如,当存在两个不同版本的
协议,例如
FontProvider
和FontProviderV2
- 分成两个部分的服务,用于根据
访问权限级别 - 例如,常规访问权限与管理员权限
例如
Directory
和DirectoryAdmin
,后者会提供 特权访问 - 由许多不同协议构成的服务
不同的使用方,例如用于电源管理的
Power
,以及Ethernet
,适用于网络堆栈 - 具有多个实例的服务,例如,多个音频
提供
AudioRenderer
的设备,或多台提供Printer
的打印机
提供这种灵活性可让服务更清晰地表达,
而无需借助服务
中心。
借助这种灵活性,我们可以将设备定义为服务。
具体而言,我们计划改进/svc/
$Protocol
这意味着“每个进程命名空间只有一个协议”更改为:
/svc/$Service/$Instance/$Member
这引入了另外两个间接:服务(例如, 打印机、以太网)和实例(如 default、deskjet_by_desk、 e80::d189:3247:5fb6:5808). 然后,协议的路径将包含以下部分:
$Service
- 完全限定的服务类型,如 在 FIDL 中声明$Instance
- 服务实例的名称,其中 “默认”按照惯例,用于表示首选(或仅限) 实例已可供使用$Member
- FIDL 中声明的服务成员名称,其中 该成员的声明类型表示预期协议
设计
服务口味
我们先来考虑一下我们力求支持的各种服务类型:
一个唯一的协议:ONE 个实例、ONE 协议:
/svc/fuchsia.Scheduler/default/profile_provider
多个协议的组合:ONE实例、ONE协议:
/svc/fuchsia.Time/default/network .../rough
使用单一协议的服务的多个实例:MANY 个实例、ONE 协议:
/svc/fuchsia.hardware.Block/0/device .../1/device
具有不同协议集的多个实例:MANY 个实例、MANY 个协议:
/svc/fuchsia.Wlan/ff:ee:dd:cc:bb:aa/device .../power .../00:11:22:33:44:55/access_point .../power
语言
向 FIDL 介绍服务的概念并支持各种 我们将对 FIDL 语言进行以下更改:
- 添加
service
关键字。 - 移除
Discoverable
属性。
service
关键字允许我们编写服务声明,
我们可以使用它来将一组协议定义为服务的成员。
例如,我们可以按如下方式声明不同的服务类型:
一个唯一的协议:ONE 个实例、ONE 协议:
service Scheduler { fuchsia.scheduler.ProfileProvider profile_provider; };
多个协议的组合:ONE实例、ONE协议:
service Time { fuchsia.time.Provider network; fuchsia.time.Provider rough; };
使用单一协议的服务的多个实例:MANY 个实例、ONE 协议:
service Block { fuchsia.hardware.block.Device device; };
具有不同协议集的多个实例:MANY 个实例、MANY 个协议
service Wlan { fuchsia.hardware.ethernet.Device device; fuchsia.wlan.AccessPoint access_point; fuchsia.hardware.Power power; };
一个服务声明可以有多个使用同一个 但每个成员声明都必须使用不同的标识符。 请参阅“多个协议的组合”。
当服务实例可能包含一组不同的协议时 则服务声明会声明所有可能的 协议 请参阅“使用不同协议集的多个实例”。
服务声明未提及特定实例的名称 或提供服务的组件的 URI,这是 留在组件框架(基于组件清单)的范畴内 对 API 的调用和使用。
语言绑定
将修改语言绑定,以便更方便地连接到服务 非常方便。 具体而言,它们将更加面向服务,例如:
连接到“default”服务实例,它采用一个协议:ONE 实例、ONE 协议:
- C++:
Scheduler scheduler = Scheduler::Open(); ProfileProviderPtr profile_provider; scheduler.profile_provider().Connect(profile_provider.NewRequest());
- Rust:
let scheduler = open_service::<Scheduler>(); let profile_provider: ProfileProviderProxy = scheduler.profile_provider();
连接到“default”一个服务实例,其中包含多个协议:ONE 个实例、MANY 个协议:
- C++:
Time time = Time::Open(); ProviderPtr network; time.network().Connect(&network); ProviderPtr rough; time.rough().Connect(&rough);
- Rust:
let time = open_service::<Time>(); let network = time.network(); let rough = time.rough();
使用单一协议连接到服务的多个实例:MANY 个实例、ONE 协议:
- C++:
Block block_0 = Block::OpenInstance("0"); DevicePtr device_0; block_0.device().Connect(&device_0); Block block_1 = Block::OpenInstance("1"); DevicePtr device_1; block_1.device().Connect(&device_1);
- Rust:
let block_0 = open_service_instance::<Block>("0"); let device_0 = block_0.device(); let block_1 = open_service_instance::<Block>("1"); let device_1 = block_1.device();
使用多种协议连接到服务的多个实例:MANY 个实例、MANY 个协议:
- C++:
Wlan wlan_a = Wlan::OpenInstance("ff:ee:dd:cc:bb:aa"); DevicePtr device; wlan_a.device().Connect(&device); Power power_a; wlan_a.power().Connect(&power_a); Wlan wlan_b = Wlan::OpenInstance("00:11:22:33:44:55"); AccessPoint access_point; wlan_b.access_point().Connect(&access_point); Power power_b; wlan_b.power().Connect(&power_b);
- Rust:
let wlan_a = open_service_instance::<Wlan>("ff:ee:dd:cc:bb:aa"); let device = wlan_a.device(); let power_a = wlan_a.power(); let wlan_b = open_service_instance::<Wlan>("00:11:22:33:44:55"); let access_point = wlan_b.access_point(); let power_b = wlan_b.power();
下面展示了建议的函数签名。
请注意,Open()
和 OpenInstance()
方法也接受
用于指定命名空间的可选参数。
默认情况下将使用进程的全局命名空间
使用 fdio_ns_get_installed)。
// Generated code.
namespace my_library {
class MyService final {
public:
// Opens the "default" instance of the service.
//
// |ns| the namespace within which to open the service or nullptr to use
// the process's "global" namespace as defined by |fdio_ns_get_installed()|.
static MyService Open(fdio_ns_t* ns = nullptr) {
return OpenInstance(fidl::kDefaultInstanceName, ns);
}
// Opens the specified instance of the service.
//
// |name| the name of the instance, must not be nullptr
// |ns| the namespace within which to open the service or nullptr to use
// the process's "global" namespace as defined by |fdio_ns_get_installed()|.
static MyService OpenInstance(const std::string& instance_name,
fdio_ns_t* ns = nullptr);
// Opens the instance of the service located within the specified directory.
static MyService OpenAt(zxio_t* directory);
static MyService OpenAt(fuchsia::io::DirectoryPtr directory);
// Opens a directory of available service instances.
//
// |ns| the namespace within which to open the service or nullptr to use
// the process's "global" namespace as defined by |fdio_ns_get_installed()|.
static fidl::ServiceDirectory<MyService> OpenDirectory(fdio_ns_t* ns = nullptr) {
return fidl::ServiceDirectory<MyService>::Open(ns);
}
// Gets a connector for service member "foo".
fidl::ServiceConnector<MyService, MyProtocol> foo() const;
// Gets a connector for service member "bar".
fidl::ServiceConnector<MyService, MyProtocol> bar() const;
/* more stuff like constructors, destructors, etc... */
}
以及绑定代码:
/// FIDL bindings code.
namespace fidl {
constexpr char[] kDefaultInstanceName = "default";
// Connects to a particular protocol offered by a service.
template <typename Service, typename Protocol>
class ServiceConnector final {
public:
zx_status_t Connect(InterfaceRequest<Protocol> request);
};
// A directory of available service instances.
template <typename Service>
class ServiceDirectory final {
public:
// Opens a directory of available service instances.
//
// |ns| the namespace within which to open the service or nullptr to use
// the process's "global" namespace as defined by |fdio_ns_get_installed()|.
static ServiceDirectory Open(fdio_ns_t* ns = nullptr);
// Gets the underlying directory.
zxio_t* directory() const;
// Gets a list of all available instances of the service.
std::vector<std::string> ListInstances();
// Opens an instance of the service.
Service OpenInstance(const std::string& name);
// Begins watching for services to be added or removed.
//
// Invokes the provided |callback| to report all currently available services
// then reports incremental changes. The callback must outlive the returned
// |Watcher| object.
//
// The watch ends when the returned |Watcher| object is destroyed.
[[nodiscard]] Watcher Watch(WatchCallback* callback,
async_dispatcher_t* dispatcher = nullptr);
// Keeps watch.
//
// This object has RAII semantics. The watch ends once the watcher has
// been destroyed.
class Watcher final {
public:
// Ends the watch.
~Watcher();
};
// Callback invoked when service instances are added or removed.
class WatchCallback {
public:
virtual void OnInstanceAdded(std::string name) = 0;
virtual void OnInstanceRemoved(std::string name) = 0;
virtual void OnError(zx_status_t error) = 0;
};
}
语言绑定可在这些基础上进一步扩展, 来迭代服务实例,并观察是否有新的 实例才可用
服务演变
为了改进服务,我们可以随着时间的推移向其添加新协议。 为了保持源代码兼容性,现有协议不得 否则源代码兼容性可能会遭到破坏 依赖于通过语言绑定从服务生成的代码。
由于服务中的所有协议实际上都是可选的,因此它们可能或 在运行时可能无法提供,而应针对这种情况构建组件 它可以简化我们在发展 AI 技术时所面临的一系列问题, 服务:
- 您可以随时向服务添加协议成员
- 应避免移除协议成员(出于源代码兼容性考虑)
- 重命名协议成员涉及添加新的协议成员; 让现有的协议成员
为了改进服务本身,我们制定了一组类似的限制。 服务不一定存在于组件的命名空间中,并且 一个命名空间内的多个不同位置上都能看到 因此:
- 可以随时添加服务
- 应避免移除服务(出于源代码兼容性考虑)
- 重命名服务包括复制服务并使用新的 同时保留服务的原始副本(针对源服务器) 兼容性)
可能的扩展
我们预计 service
实例最终会成为“一类”成为
就像 protocol P
标识名可以
以 P
或 request<P>
的形式传递。
其形式可能类似于 service_instance<S>
,表示
service S
。
我们会确保在扩展生效前,
提供支持。
我们允许(并计划)拓展会员种类
而不仅仅是允许使用协议。
例如,我们可能希望服务公开 VMO (handle<vmo>
):
service DesignedService {
...
handle<vmo>:readonly logo; // gif87a
};
实施策略
此提案应分阶段实施,以免破坏现有 代码。
第 1 阶段
- 修改 component_manager,以便组件 v2 支持新的 服务的目录架构
- 修改 appmgr 和 sysmgr,以便组件 v1 支持新的 服务的目录架构
第 2 阶段
- 添加对服务声明的支持。
- 修改语言绑定以生成服务。
第 3 阶段
- 对于具有
Discoverable
属性的所有协议,请创建 适当的服务声明。 >注意:在此阶段,我们应验证 >新旧目录架构之间可能发生的冲突。 - 迁移所有源代码才能使用服务。
第 4 阶段
- 从 FIDL 文件中移除所有
Discoverable
属性。 - 从 FIDL 和语言绑定中移除了对
Discoverable
的支持。 - 从 component_manager 中移除了对旧目录架构的支持, appmgr 和 sysmgr。
文档和示例
我们需要扩充 FIDL 教程,以解释服务的使用 以及它们如何与协议交互 然后,我们会解释服务的不同结构:单例与 以及如何使用语言绑定
术语库
协议声明描述了一组可能会由服务器发送或 及其二进制表示法。
服务声明描述了作为单元提供的功能 由服务提供商提供 它由服务名称以及零个或多个已命名的成员协议组成 用来与该功能交互的客户端。
同一协议可能会作为一项服务的成员出现多次 声明,其中成员名称表示预期解释 协议的名称:
service Foo {
fuchsia.io.File logs;
fuchsia.io.File journal;
};
组件声明描述了可执行软件的单元, 包括组件二进制文件的位置以及 (例如服务)的用途,或者向这些用户提供 其他组件
此类信息通常会编码为组件清单文件。 中:
// frobinator.cml
{
"uses": [{ "service": "fuchsia.log.LogSink" }],
"exposes": [{ "service": "fuchsia.frobinator.Frobber" }],
"offers": [{
"service": "fuchsia.log.LogSink",
"from": "realm",
"to": [ "#child" ]
}],
"program": { "binary": ... }
"children": { "child": ... }
}
服务实例是符合给定服务要求的功能 声明。 在 Fuchsia 上,它表示为目录。 其他系统可能使用不同的服务发现机制。
组件实例是组件的特定实例,具有 私有沙盒 在运行时,它会通过 在其传入的命名空间中打开目录。 相反,它通过将自身的服务实例公开给其他组件, 并在其传出目录中显示它们。 组件管理器充当服务发现的代理。
- 组件实例通常(但并不总是)与 process。
- 组件运行程序通常可以在 相同的进程,每个进程都有自己的传入命名空间。
以习惯方式使用服务
向后兼容性
此提案将弃用,并最终移除“Discoverable
”
属性。
线路格式没有变化。
如果要引入新的数据类型或语言功能,请考虑 您希望用户在不更改 FIDL 定义的情况下 来破坏用户生成的代码 如果您的功能放置了任何新的源代码兼容性, 有关生成的语言绑定的限制,请在此处列出。
性能
在连接到 服务的默认实例或已知的先验顺序实例。
连接到未知实例 ID 的其他实例 apriori - 将要求用户列出服务的目录并找到 然后才能进行连接
对 build 和二进制文件大小的影响微乎其微,因为服务 必须由后端为特定语言绑定生成定义。
安全
这一方案将使我们能够实施更精细的访问权限控制, 因为我们可以将服务拆分为多个具有不同访问权限的独立协议 权利。
此方案对安全性没有其他影响。
测试
编译器中的单元测试,并将兼容性测试套件更改为 检查服务中包含的协议是否可以连接。
缺点、替代方案和未知问题
探讨了以下问题:
- 为什么服务声明属于 FIDL?
- 协议、服务和 组件?
- 针对服务实例提议的平面拓扑是否足够 富有表现力?
- 我们应如何逐步扩展服务?
- 如果某个组件实例希望公开多个相关的服务, 单个底层逻辑资源,该如何表达?
问题 1:为什么服务声明属于 FIDL?
响应
- 我们使用 FIDL 来描述 Fuchsia 的系统 API,包括协议 组件交换的组件。
- 根据具体情况,可以通过多种方式使用相同的协议。 以服务的形式展示这些协议的各种用途 以便开发者针对每种平台 情况。
- FIDL 已提供易于扩展的语言绑定 为开发者提供统一且便捷的方式 服务。
讨论
- [ianloic] 但是,组件清单呢?为何不使用 FIDL 来 能否描述一下这些方面?
- [jeffbrown] 组件清单描述的概念远不止 IPC 个问题
- [abdulla] 在组件清单中描述服务 这些服务的说明重复
- [ianloic] 我们能否根据组件清单生成组件框架?
- [drees] 在 FIDL 中放置服务声明会导致 这种结构是否适用于其他平台?
- [jeffbrown] 我们希望服务声明位于组件外部 因为它们需要在组件之间共享,所以其要点 服务交换协议
- [ianloic] 的 Overnet 服务声明可能类似
- [pascallouis] 根据我们掌握的需要,从简单的问题入手是不是很好 。我们以后可以根据需要进行调整。
- [pascallouis] FIDL 是 Fuchsia 首选,因此引入 只有根据我们掌握的信息, 但随着时间的推移,我们可以将其推广到其他环境,
- [dustingreen] 想创建一个单独的文件呢?
- [pascallouis] 这些文件会非常小且孤零, 进行静态类型检查,如果我们将其保存在 FIDL 中,迁移的风险似乎较低 以后需要时再设置
问题 2:协议、服务和组件之间有什么区别?
响应
- 协议声明描述了一组可能被 通过通道发送或接收的内容及其二进制表示法。
- 服务声明描述了作为
由服务提供商提供
它由服务名称和零个或零个以上已命名的成员协议组成
客户端用来与该功能交互时使用的凭据。
- 同一个协议可能会以
服务声明;成员的名称表明了
协议的解释。
- 例如:
service Foo { fuchsia.io.File logs; fuchsia.io.File journal; };
- 例如:
- 同一个协议可能会以
服务声明;成员的名称表明了
协议的解释。
组件声明描述了可执行软件的单元, 包括组件二进制文件的位置以及 (例如服务)的用途,或者向这些用户提供 其他组件
此信息通常编码为组件清单 file。 示例:
// frobinator.cml { "uses": [{ "service": "fuchsia.log.LogSink" }], "exposes": [{ "service": "fuchsia.frobinator.Frobber" }], "offers": [{ "service": "fuchsia.log.LogSink", "from": "realm", "to": [ "#child" ]}], "program": { "binary": ... } "children": { "child": ... } }
服务实例是指符合 服务声明。 在 Fuchsia 上,它表示为目录。 其他系统可能使用不同的服务发现机制。
组件实例是具有 专属的沙盒 在运行时,它会使用其他组件提供的服务实例 方法是在其传入的命名空间中打开目录。 相反,它会将自己的服务实例公开给其他组件 传出目录中显示它们。 组件管理器充当服务发现的代理。
- 组件实例通常(但并不总是)与 process。
- 组件运行程序通常可以运行多个组件实例 在同一个进程中,每个进程都具有自己的传入命名空间。
讨论
- [ianloic] 在选择协议组合方面,我们应该提供什么指南 与服务声明相比?
- [abdulla] 协议构成表示协议本身 高度相关,这表明 (可能不相关的)共同提供
- [pascallouis] 在单个通道上编写多路复用协议,因此 对消息排序与服务各个协议的影响 有不同的频道
- [jeffbrown] 可以在不同的位置(不相关)进行委托 无法实现此功能,服务支持“发现”在运行时 例如列出可用的协议
问题 3:为服务实例提议的扁平拓扑是否具有足够的表现力?
响应
- 平面拓扑易于使用,因为使用 遍历路径查找所有实例。 这对易用性和性能都有影响。
- 扁平拓扑与分层拓扑一样富有表现力
当相关信息在实例名称中进行编码时,例如
/svc/fuchsia.Ethernet/
rack.5,port.9/packet_receiver
。 - 您可以使用 Open() 从不同的位置访问服务, Open(namespace) 和 OpenAt(directory)。 也就是说,并非所有服务都需要来自 `/svc"在 一个进程的全局命名空间 这允许创建任意服务拓扑(如果 。
问题 4:我们应如何逐步扩展服务?
响应
- 我们可以将新成员添加到现有服务声明中。 添加新成员不会破坏源代码或二进制文件的兼容性 因为每个成员实际上是可选的(尝试连接到 协议是可能失败的操作)。
- 我们可以从服务声明中移除现有成员。 移除(或重命名)现有成员可能会破坏源代码和二进制文件 兼容性,可能需要制定周密的迁移计划来减轻不利影响 影响。
- 服务的文档应就服务如何提供明确的预期 服务的使用或实现目的,尤其是在 用途不明显,例如,说明服务有哪些功能 并计划移除
预期的版本控制模式:将新成员作为 协议演变。 协议枚举(列出目录)可让客户端 支持的系统类型 示例:
在版本 1 中...
service Fonts { FontProvider provider; }; protocol FontProvider { GimmeDaFont(string font_name) -> (fuchsia.mem.Buffer ttf); };
在版本 2 中,增量更新...
service Fonts { FontProvider provider; FontProvider2 provider2; }; protocol FontProvider2 { compose FontProvider; GetDefaultFontByFamily(string family) -> (string family); };
在版本 3 中,经过全面重新设计...
service Fonts { [Deprecated] FontProvider provider; [Deprecated] FontProvider provider2; TypefaceChooser typeface_chooser; } protocol TypefaceChooser { GetTypeface(TypefaceCriteria criteria); }; table TypefaceCriteria { 1: Family family; 2: Style style; 3: int weight; };
问题 5:如果某个组件实例希望公开与单个底层逻辑资源相关的多项服务,该如何表达?
响应
一个组件会定义通过 其组件清单 示例:
// frobinator.cml { ... "exposes": [ { "service": "fuchsia.frobinator.Fooer" }, { "service": "fuchsia.frobinator.Barer" }, ], ... }
然后,该组件会在单个 但这些服务的用户无需知道 事实。