本指南包含一些最佳实践,可帮助 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
中阻塞的线程。不过,在 Starnix 支持此类机制之前,您需要谨慎考虑何时在同步 FIDL 调用中阻塞 Starnix 线程。对于与任何系统调用无关的 FIDL 调用,请选择异步 FIDL 绑定。
具体而言,请考虑以下情况:
- 需要并发组合多个调用并等待它们。
- FIDL 协议旨在唤醒处于暂停状态的容器。
- FIDL 协议使用了在同步绑定中难以实现的功能,例如要求它提供其他协议。
- 操作需要能被其他 Linux 线程发送的信号中断。如果 FIDL 调用需要可中断,则不能是同步调用;您需要能够在
Waiter
上阻塞系统调用线程,以便其能够接收信号。
对于异步 FIDL 绑定,有 两种线程生成技术需要考虑:
在主线程的执行器上生成 Future,这通常是默认情况。如需查看代码示例,请参阅直接在主线程的执行器上生成 Future。
生成自己的线程,这是一种非常有用的特殊情况,因为开发者可以控制执行器的运行时间,并且可以在同一线程中混合使用同步和异步工作,而不会阻塞 Starnix 的其他部分生成的异步工作。如需查看代码示例,请参阅生成运行自己的执行器的单独线程。
在 Starnix 中选择命名空间
本部分阐明了如何使用内核和容器命名空间在 Starnix 中访问 FIDL 协议。
单个 Starnix 程序实例可充当两个组件:一个是内核组件(由 Starnix 运行程序运行),另一个是容器组件(由内核组件运行)。这意味着,功能可能会被路由到两个位置,以供同一程序使用。此处要使用的 API 取决于功能的路由位置。(如需详细了解 Starnix 中的命名空间,请参阅在 Fuchsia 中执行 Linux 系统调用。)
请考虑 Starnix 中可以提供对 FIDL 协议访问权限的以下命名空间:
- 内核命名空间 - 可以通过常规
fuchsia_component
调用访问此命名空间。 - 容器命名空间 - 如需查看代码示例,请参阅 Starnix 中的容器命名空间。
代码示例
本部分提供了代码示例,说明本指南中介绍的不同 IPC 模式:
这些示例来自 Starnix 内核的各个部分,旨在提供指南中所述最佳实践的实用实现。
Starnix 中的同步 FIDL 绑定
本部分包含一些代码示例,说明了直接从系统调用处理程序中处理 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();
(来源://src/starnix/kernel/vfs/file_object.rs
)