Starnix 中的 IPC 模式

本指南包含一些最佳实践,可帮助 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