RFC-0265:计数器:用于跨进程同步的简单 Zircon 对象 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 提议了新的 Zircon 对象 zx::counter。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2024-12-09 |
审核日期(年-月-日) | 2025-02-12 |
问题陈述
此 RFC 的动机是两个单独的用例:Starnix 挂起和带时间戳的图形栅栏。虽然 Starnix 挂起是主要用例,但带时间戳的图形栅栏是一个重要的次要用例。
Starnix Suspension
我们需要一种方法,让 Starnix 内核和 Starnix Runner 能够协调工作,并在有来自 Fuchsia 平台组件的待处理消息时防止挂起。
其中涉及三个流程:
某个 Fuchsia 平台组件,正在通过某个通道发送消息。
Starnix 内核,需要读取该消息并根据该消息执行操作。
Starnix Runner,其角色如下所述。
Starnix Runner 负责通过将其所有线程置于 ZX_THREAD_SUSPENDED
状态并监控 Fuchsia 平台组件向 Starnix 内核发送的消息来暂停 Starnix 内核。Starnix Runner 充当代理,代表 Starnix 内核接收消息,然后在 Starnix 内核未暂停时转发这些消息,或者在 Starnix 内核暂停时恢复并转发这些消息。
我们希望确保不会出现任何“唤醒丢失”问题,因此 Starnix Runner 和 Starnix 内核必须就是否存在任何应阻止 Starnix 内核暂停的未处理或“传输中”消息达成一致。
目前,Starnix Runner 和 Starnix 内核使用单个 zx::event
进行协调。此事件用于指示有消息正在传输。以这种方式使用事件是可行的,并且可以防止唤醒丢失。不过,这有一个限制,即一次最多只能代理一条消息。如果没有此限制,Starnix Runner 将无法确定 Starnix 内核是否已处理所有传输中的消息,还是仅处理了已收到的消息。
我们希望移除此限制,并支持多条传输中的消息。为了支持多个传输中的消息,我们需要找到一种方法来统计这些消息,以便仅在计数达到零时才暂停。
当有多个传输中的消息时,仅使用 zx::event
是不够的。不过,zx::event
加上共享整数就足以满足要求了。如果 Starnix Runner 和 Starnix 内核共享地址空间,我们可以直接将整数存储在内存中。但是,这些进程不共享地址空间,因此我们需要将整数存储在某些其他共享资源(例如 VMO)中。鉴于这些进程位于同一逻辑信任域中,因此完全可以使用 VMO 进行协调。不过,如果我们有一个新的 Zircon 对象,其行为类似于带计数的事件,我们就可以做得更好,并简化一些操作。
带时间戳的图形栅栏
ZirconVmoSemaphore 用于实现带时间戳的图形栅栏。它类似于 zx::event
和一个时间戳,表示对象收到信号的时间。图形堆栈会为每个帧创建多个此类对象。每个 VMO 都由 VMO 提供支持,因此每个 VMO 都占用一页内存(外加一些开销)。使用 VMO 实现带时间戳的图形栅栏是可行的,但效率不高。如果我们有一个类似于 zx::event
和时间戳的对象,就可以做得更好。
另请参阅 VkFence。
摘要
我们建议创建一个新的 Zircon 对象 zx::counter
,用于存储一个整数,可进行递增/递减,并在值大于零或小于等于零时断言信号。
利益相关方
主要利益相关方包括 Zircon、Starnix 和图形团队。
教练:davemoore@google.com
审核人员:emircan@google.com、lindkvist@google.com、mcgrathr@google.com
咨询了以下人员:adanis@google.com、eieio@google.com、rudymathu@google.com
共享:在 RFC 的第一个草稿之前,我们已通过电子邮件与多位人员讨论过此提案的不同变体。另请参阅 go/zx-counter 中的内部讨论。
目标和要求
解决当前问题 - 启用 Starnix 内核和 Starnix Runner,以便在 Fuchsia 平台组件中有待处理的消息时进行协调并防止挂起。启用图形堆栈,以便将基于 VMO 且略显重量级的 ZirconVmoSemaphore 替换为更高效的同步工具。
力求简单 - 用于解决当前问题的所有内容都应保持简单。不要构建超出必要的功能。如果出现更多用例,我们可以针对这些未来用例改进解决方案(或创建其他解决方案!)。
设计
我们引入了新的 Zircon 对象:Counter。计数器类似于事件,但使用可递增、递减、读取或写入的 64 位有符号整数。
当计数器的值大于零时,系统会使信号 ZX_COUNTER_POSITIVE
有效并使 ZX_COUNTER_NON_POSITIVE
无效。当值小于或等于零时,系统会取消断言 ZX_COUNTER_POSITIVE
并断言 ZX_COUNTER_NON_POSITIVE
。
虽然 Starnix 挂起用例可通过断言/取消断言信号的递增和递减操作来满足,但带时间戳的图形栅栏用例需要读取和写入操作。用作栅栏的计数器将从值零开始,表示未发送信号的栅栏。正值表示已发出信号的栅栏,值本身就是发出栅栏信号的时间。
如需对栅栏进行信号传递,图形堆栈中的信号传递组件将使用写入操作将计数器的值设置为当前时间(假设为 zx_instant_mono_t
)。当计数器的值从零更改为此正值时,系统会断言 ZX_COUNTER_POSITIVE
。然后,图形堆栈中的 signalee 组件可以读取计数器的值,以确定栅栏信号的时间。
为了支持这两种用例,我们添加了四个新的系统调用 (zx_counter_create
、zx_counter_add
、zx_counter_read
、zx_counter_write
) 和两个额外的信号:
// Asserted when a counter's value is less than or equal to zero.
#define ZX_COUNTER_NON_POSITIVE __ZX_OBJECT_SIGNAL_4
// Asserted when a counter's value is greater than zero.
#define ZX_COUNTER_POSITIVE __ZX_OBJECT_SIGNAL_5
计数器是使用 zx_counter_create
创建的:
## Summary
Create a counter.
## Declaration
zx_status_t zx_counter_create(uint32_t options, zx_handle_t* out);
## Description
zx_counter_create() creates a counter, which is an object that encapsulates
a signed 64-bit integer value that can be incremented, decremented, read, or
written.
When the value is greater than zero, the signal ZX_COUNTER_POSITIVE is
asserted. Otherwise ZX_COUNTER_NON_POSITIVE is asserted. Exactly one of
these two signals is always asserted, and never both at once.
The newly-created handle will have rights ZX_RIGHTS_BASIC, ZX_RIGHTS_IO, and
ZX_RIGHT_SIGNAL. The value will be zero and the signal
ZX_COUNTER_NON_POSITIVE will be asserted on the newly-created object.
## Rights
Caller job policy must allow ZX_POL_NEW_COUNTER.
## Return value
zx_counter_create() returns ZX_OK and a valid event handle (via *out*) on
success.
On failure, an error value is returned.
## Errors
ZX_ERR_INVALID_ARGS *out* is an invalid pointer, or *options* is non-zero.
ZX_ERR_NO_MEMORY Failure due to lack of memory.
There is no good way for userspace to handle this (unlikely) error.
In a future build this error will no longer occur.
可以使用 zx_counter_add
递增或递减计数器:
## Summary
Add an amount to a counter.
## Declaration
zx_status_t zx_counter_add(zx_handle_t handle, int64_t amount);
## Description
zx_counter_add() adds amount to the counter referenced by handle.
After the result of the addition, if the counter's value is:
* less than or equal to zero - ZX_COUNTER_NON_POSITIVE will be asserted
and ZX_COUNTER_POSITIVE will be deasserted.
* greater than zero - ZX_COUNTER_POSITIVE will be asserted and
ZX_COUNTER_NON_POSITIVE will be deasserted.
## Rights
handle must have both ZX_RIGHT_READ and ZX_RIGHT_WRITE. Because a counter's
value could be determined by checking for ZX_ERR_OUT_OF_RANGE on a series of
carefully crafted zx_counter_add calls, there is no way to create a counter
that cannot be read, but which can be added.
## Return value
zx_counter_add() returns ZX_OK on success.
On failure, an error value is returned.
## Errors
ZX_ERR_WRONG_TYPE if handle is not a counter handle.
ZX_ERR_ACCESS_DENIED if handle does not have ZX_RIGHT_WRITE.
ZX_ERR_OUT_OF_RANGE if the result of the addition would overflow or underflow.
可以使用 zx_counter_read
读取计数器:
## Summary
Read the value of a counter.
## Declaration
zx_status_t zx_counter_read(zx_handle_t handle, int64_t* value);
## Description
zx_counter_read() reads the value of the counter referenced by handle into the
integer value points at.
## Rights
handle must have ZX_RIGHT_READ.
## Return value
zx_counter_read() returns ZX_OK on success.
On failure, an error value is returned.
## Errors
ZX_ERR_WRONG_TYPE if handle is not a counter handle.
ZX_ERR_ACCESS_DENIED if handle does not have ZX_RIGHT_READ.
ZX_ERR_INVALID_ARGS if value is an invalid pointer.
可以使用 zx_counter_write
编写计数器:
## Summary
Write the value to a counter.
## Declaration
zx_status_t zx_counter_write(zx_handle_t handle, int64_t value);
## Description
zx_counter_write() writes value to the counter referenced by handle,
asserting/deasserting signals as necessary.
Because concurrent operations on a counter may be interleaved with one another,
an implementation of a "counting semaphore" synchronization protocol should use
zx_counter_add() instead of a sequence of zx_counter_read(), modify,
zx_counter_write().
## Rights
handle must have ZX_RIGHT_WRITE.
## Return value
zx_counter_write() returns ZX_OK on success.
On failure, an error value is returned.
## Errors
ZX_ERR_WRONG_TYPE if handle is not a counter handle.
ZX_ERR_ACCESS_DENIED if handle does not have ZX_RIGHT_WRITE.
ZX_ERR_INVALID_ARGS if value is an invalid pointer.
实现
计数器将由几个 CL(可能只有一个)在 Zircon 中实现。Starnix 内核和 Starnix Runner,以及图形堆栈稍后将更新为使用计数器。
各种语言绑定、FIDL 句柄支持、fidlcat/fidl_coded 等将更新。
性能
我们预计 zx_counter_create
的性能与 zx_event_create
类似。我们预计 zx_counter_add
和 zx_counter_write
的性能与 zx_object_signal
类似。
zx_counter_read
应该类似,但它可能还会执行 usercopyout 来返回值。读取操作是否执行 usercopy 实际上是一个实现细节。我们可以先从执行用户拷贝的实现入手,然后通过更改 vDSO/内核接口以使用寄存器返回值来优化,并更新 vDSO 以存储返回值。
我们预计 Starnix Suspension 用例不会受到任何重大性能影响(提升或回归)。
对于带时间戳的图形栅栏用例,我们预计 CPU 和内存性能会略有提升。与基于 VMO 的信号量相比,计数器需要的内存更少(几十个字节与超过一页),并且创建费用应该更低。
工效学设计
计数器旨在与现有的异步等待模式(例如 Zircon 端口)搭配使用。
向后兼容性
Counter 是一个新的 Zircon 对象,因此没有向后兼容性问题。
安全注意事项
无安全注意事项。
隐私保护注意事项
无隐私权注意事项。
测试
将添加新的核心测试来测试新的 Zircon 对象。
文档
将添加/更新系统调用文档。
缺点、替代方案和未知
自行组合
如上所述,zx::event
不足以满足 Starnix 用例。不过,只需 zx::event
加上共享整数和商定的协议即可。引入新 Zircon 对象的替代方案是,Starnix Runner 和 Starnix 内核使用 zx::event
以及包含要原子操作的整数的共享映射 VMO。虽然这种解决方案可行,但它更复杂,并且有一些细微之处可能很难处理得当。我们认为 zx::counter
是一个自然的 Zircon 对象,并且添加它对内核复杂性的增加非常小。
通用且完整,与狭窄且有限
在设计和构建功能更全面的计数器或信号量对象与构建仅满足我们当前用例的对象之间存在矛盾。我们之前曾探索过提供类似对象的可能性,但在有更具体的用例之前,我们一直在等待。这次,我们将构建一个相对简单的模型,旨在满足一到两种情况。我们的目标是,随着出现更多用例,我们会改进或替换此对象,而不是创建大量类似但不完全相同的对象。
另请参阅未来发展方向。
无符号计数
驾驶用例只需要无符号计数器。我们可以更改行为,使值在逻辑上为无符号。我们还可以使用选项标志来指定它是带符号还是无符号,并在它是无符号时将递减小于零视为错误。由于我们没有找到同时支持有符号和无符号的强有力的论据,并且有符号更灵活,并且可以涵盖更多未来的用例,因此建议仅提供有符号的语义。
缺少初始值
我们考虑了一种替代方案,即可以将初始值传递给 create 方法。不过,调用方只需在允许句柄“逃逸”之前调用 add,即可实现相同的操作,因此我们认为这项功能没有必要。
未来方向
在短期至中期内,我们预计 zx::counter
不会被广泛采用。未来某个时候,我们可能会将 zx::counter
(也许还有 zx::event
)替换为更通用、更完整的信号量对象。