本指南包含一些最佳实践,可帮助 Fuchsia 开发者了解在 Starnix 中使用各种进程间通信 (IPC) 模式的权衡取舍。
在 Starnix 中发出 FIDL 调用的最佳实践:
如需了解本指南中概述的最佳实践的实际应用,请参阅代码示例。
在 Starnix 中选择 FIDL 绑定
本部分提供了一个决策树,可帮助开发者在同步和异步 FIDL 绑定之间进行选择。
无法直接从 Starnix 系统调用中调用异步 FIDL 调用。这是因为 Starnix 中的线程需要将控制权返回给 Linux。这意味着无法在 Starnix 中注册异步回调处理程序。
请改为考虑使用以下某种模式在 Starnix 中进行异步 FIDL 调用:
- 在系统调用处理程序中同步执行所有操作。
- 在主执行器上生成 future。
- 使用执行器生成您自己的线程。
图 1. 此流程图直观地展示了在 Starnix 中选择 FIDL 绑定的决策过程。
选择这些模式之一时应考虑哪些因素?
为处理 Linux 程序系统调用的 FIDL 调用选择同步 FIDL 绑定。
这通常是默认方法。当需要从处理 Linux 程序系统调用的“用户”线程发出 FIDL 调用时,此属性适用。如需查看代码示例,请参阅 Starnix 中的同步 FIDL 绑定。
不过,请注意,如果请求可能会被阻塞相当长的时间,则发出请求的 Starnix 线程(被阻塞)无法被 Linux 信号终止。Zircon 团队曾探索过添加一种机制来中断在
zx_channel_call
中被阻塞的线程(请参阅 Bug 42106200),但尚未找到令人满意的设计。在 Starnix 支持此类机制之前,请避免在同步 FIDL 调用中无限期地阻塞用户线程。对于未与任何系统调用关联的 FIDL 调用,请选择异步 FIDL 绑定。
请特别考虑以下情况:
- 需要同时组合并等待多个调用。
- FIDL 协议旨在将容器从挂起状态唤醒。
- FIDL 协议使用了一些难以在同步绑定中实现的功能,例如要求它服务于另一个协议。
- 该操作需要可被来自其他 Linux 线程的信号中断。如果 FIDL 调用需要可中断,则不能是同步调用;您需要能够在
Waiter
上阻塞 syscall 线程,以便它可以接收信号。
对于异步 FIDL 绑定,有两种线程生成技术可供考虑:
将 future 生成到主线程的执行器(通常是默认情况)。如需查看代码示例,请参阅直接在主线程的执行器上生成 future。
生成自己的线程,这是一种特殊情况,非常有用,因为开发者可以控制执行器何时运行,并且可以在同一线程上混合使用同步和异步工作,而不会阻塞由 Starnix 的其他部分生成的异步工作。如需查看代码示例,请参阅生成运行自有执行器的单独线程。
在 Starnix 中选择命名空间
本部分阐明了如何使用内核和容器命名空间在 Starnix 中访问 FIDL 协议。
单个 Starnix 程序实例充当两个组件:一个是 kernel 组件(由 Starnix runner 运行),另一个是 container 组件(由 kernel 组件运行)。这意味着,功能可能被路由到两个位置,以供同一程序使用。此处要使用的 API 取决于功能路由到的位置。(如需详细了解 Starnix 中的命名空间,请参阅在 Fuchsia 中发出 Linux 系统调用。
请考虑 Starnix 中可提供对 FIDL 协议的访问权限的以下命名空间:
- 内核命名空间 - 此命名空间可通过正常的
fuchsia_component
调用进行访问。 - 容器命名空间 - 如需查看代码示例,请参阅 Starnix 中的容器命名空间。
代码示例
本部分提供了代码示例,用于说明本指南中介绍的不同 IPC 模式:
这些示例来自 Starnix 内核的各个部分,旨在提供指南中概述的最佳实践的实际实现。
Starnix 中的同步 FIDL 绑定
本部分包含一些代码示例,这些示例展示了直接从 syscall 处理程序内部处理 FIDL 调用的标准方法,代表了同步 FIDL 绑定的典型实现。
连接到 Starnix 中的同步代理的代码示例:
// Check whether we actually have access to a role manager by trying to set our own
// thread's role.
let role_manager = connect_to_protocol_sync::<RoleManagerMarker>().unwrap();
let role_manager = if let Err(e) =
set_thread_role(&role_manager, &*fuchsia_runtime::thread_self(), Default::default())
{
log_warn!("Setting thread role failed ({e:?}), will not set thread priority.");
None
} else {
log_info!("Thread role set successfully.");
Some(role_manager)
};
(来源://src/starnix/kernel/runner/container.rs
)
在 Starnix 中使用同步代理的代码示例:
pub fn set_thread_role(
role_manager: &RoleManagerSynchronousProxy,
thread: &zx::Thread,
policy: SchedulerPolicy,
) -> Result<(), Errno> {
let role_name = policy.kind.role_name();
log_debug!(policy:?, role_name; "setting thread role");
let thread = thread.duplicate_handle(zx::Rights::SAME_RIGHTS).map_err(impossible_error)?;
let request = RoleManagerSetRoleRequest {
target: Some(RoleTarget::Thread(thread)),
role: Some(RoleName { role: role_name.to_string() }),
..Default::default()
};
let _ = role_manager.set_role(request, zx::MonotonicInstant::INFINITE).map_err(|err| {
log_warn!(err:?; "Unable to set thread role.");
errno!(EINVAL)
})?;
Ok(())
}
(来源://src/starnix/kernel/task/scheduler.rs
)
Starnix 中的异步 FIDL 绑定
本部分包含代码示例,用于说明在 Starnix 中处理异步 FIDL 调用的两种不同方法:
直接在主线程的执行器上生成 future
一个直接在 Starnix 主线程的执行器上生成 future 的代码示例:
current_task.kernel().kthreads.spawn_future(async move {
let _ = {
// 1. Lock the state to update `abort_handle` when the timer is still armed.
// 2. MutexGuard needs to be dropped before calling await on the future task.
// Unfortunately, std::mem::drop is not working correctly on this:
// (https://github.com/rust-lang/rust/issues/57478).
let mut guard = self_ref.state.lock();
if !guard.armed {
return;
}
let (abortable_future, abort_handle) = futures::future::abortable(
self_ref.start_timer_loop(kernel_ref.kthreads.system_task(),thread_group),
);
guard.abort_handle = Some(abort_handle);
abortable_future
}
.await;
});
(来源://src/starnix/kernel/task/interval_timer.rs
)
生成一个运行其自身执行器的单独线程
生成运行自己的执行器的单独 kthread
的代码示例:
kernel.kthreads.spawner().spawn(|_, _| {
let mut executor = fasync::LocalExecutor::new();
let scheduler = ThroughputScheduler::new();
let mut view_bound_protocols = Some(view_bound_protocols);
let mut view_identity = Some(view_identity);
let mut maybe_view_controller_proxy = None;
executor.run_singlethreaded(async move {
(来源://src/starnix/kernel/device/framebuffer_server.rs
)
Starnix 中的容器命名空间
本部分包含一个代码示例,演示了用于访问容器组件内基于容器的 FIDL 协议的语法。
一个代码示例,展示了如何在容器命名空间中连接到 CryptManagement
协议:
let crypt_management_proxy = current_task
.kernel()
.connect_to_protocol_at_container_svc::<CryptManagementMarker>()
.map_err(|_| errno!(ENOENT))?
.into_sync_proxy();