| 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 负责通过将 Starnix 内核的所有线程置于 ZX_THREAD_SUSPENDED 状态来暂停 Starnix 内核,并观察 Fuchsia 平台组件发送给 Starnix 内核的消息。Starnix Runner 充当代理,代表 Starnix 内核接收消息,然后在 Starnix 内核未暂停时转发消息,或者在 Starnix 内核暂停时恢复并转发消息。
我们希望确保不会出现任何“丢失的唤醒”,因此 Starnix Runner 和 Starnix Kernel 必须就是否存在任何未完成或“正在处理”的消息达成一致,这些消息应阻止 Starnix Kernel 被暂停。
目前,Starnix Runner 和 Starnix Kernel 通过单个 zx::event 进行协调。此事件用于指示有正在传输的消息。
以这种方式使用事件是可行的,并且可以防止丢失唤醒。不过,它存在一个限制,即一次最多只能代理一条消息。如果没有此限制,Starnix Runner 就无法判断 Starnix Kernel 是已处理所有正在传输的消息,还是仅处理了已收到的消息。
我们希望移除此限制并支持多个正在发送的消息。为了支持多个正在传输的消息,我们需要某种方法来对它们进行计数,以便仅在计数达到零时暂停。
当有多个正在传送的消息时,仅使用 zx::event 是不够的。不过,zx::event 加上共享整数就足够了。如果 Starnix Runner 和 Starnix Kernel 共享一个地址空间,我们可以简单地将整数存储在内存中。不过,这些进程不共享地址空间,因此我们需要将整数存储在其他共享资源(例如 VMO)中。鉴于这些进程位于同一逻辑信任网域中,因此完全可以使用 VMO 进行协调。不过,如果我们有一种新的 Zircon 对象,其行为类似于带有计数的事件,那么我们可以做得更好,并简化一些事情。
带时间戳的图形栅栏
ZirconVmoSemaphore 用于实现带时间戳的图形栅栏。它类似于 zx::event,外加一个时间戳,用于指明对象发出信号的时间。图形堆栈会为每个帧创建多个此类对象。每个 VMO 都由一个 VMO 提供支持,因此每个 VMO 都占用一页内存(外加一些开销)。使用 VMO 实现带时间戳的图形栅栏是可行的,但效率不高。如果我们有一个类似于 zx::event 加时间戳的对象,就可以做得更好。
另请参阅 VkFence。
摘要
我们建议创建一个新的 Zircon 对象 zx::counter,该对象包含一个整数,可以递增/递减,并且在值大于零或小于等于零时断言信号。
利益相关方
主要利益相关者是 Zircon、Starnix 和 Graphics。
教员: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 应该类似,只是它可能还会执行 usercopy out 来返回值。读取是否执行 usercopy 实际上是一个实现细节。我们可以先实现一个执行 usercopy 的版本,然后通过更改 vDSO/内核接口来优化它,以使用寄存器更新 vDSO 来存储返回值。
我们预计 Starnix 暂停使用情形不会对性能产生任何重大影响(无论是提升还是退化)。
对于带时间戳的图形栅栏使用情形,我们预计 CPU 和内存都会有小幅改进。计数器所需的内存比基于 VMO 的信号量少(几十个字节与超过一个页面),并且创建成本应该更低。
工效学设计
计数器旨在与现有的异步等待模式(例如 Zircon Ports)搭配使用。
向后兼容性
计数器是一个新的 Zircon 对象,因此不存在向后兼容性问题。
安全注意事项
无安全注意事项。
隐私保护注意事项
无隐私权注意事项。
测试
将添加新的核心测试来测试新的 Zircon 对象。
文档
将添加/更新系统调用文档。
缺点、替代方案和未知因素
自行开发
如上所述,zx::event 不足以满足 Starnix 用例的需求。不过,zx::event 加上共享整数和商定的协议就足够了。除了引入新的 Zircon 对象之外,Starnix Runner 和 Starnix Kernel 还可以使用 zx::event 以及包含它们以原子方式操作的整数的共享映射 VMO。虽然这种解决方案可行,但它更复杂,并且存在一些难以正确处理的细微之处。我们认为 zx::counter 是一个自然的 Zircon 对象,添加它只会略微增加内核复杂性。
一般用途和完整用途与狭窄用途和有限用途
设计和构建功能更全面的计数器或信号量对象,与构建仅满足当前使用场景的对象之间存在冲突。我们之前曾探索过提供类似的对象,但一直没有实现,直到我们有了更具体的用例。这次,我们将构建一个相对简单的模型,旨在满足一到两种情况。我们的目标是,随着更多使用情形的出现,我们将改进或替换此对象,而不会创建大量类似但不完全相同的对象。
另请参阅未来方向。
无符号计数
主要的用例只需要一个无符号计数器。我们可以更改行为,使该值在逻辑上为无符号。我们还可以使用一个选项标志来指定它是有符号还是无符号,并在它是无符号时将递减到零以下视为错误。由于我们没有找到支持同时使用有符号和无符号的有力论据,并且有符号更灵活,可以涵盖更多未来的使用情形,因此建议仅提供有符号语义。
缺少初始值
我们考虑了另一种替代方案,即可以将初始值传递给 create 方法。不过,调用方只需在允许句柄“逸出”之前调用 add 即可实现相同目的,因此我们认为没有必要。
未来方向
在短期到中期内,我们预计 zx::counter 不会得到广泛采用。在未来的某个时间点,我们可能会使用更通用、更完整的信号量对象来替换 zx::counter(或许还有 zx::event)。