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 中被阻塞的线程(请参阅 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();

来源//src/starnix/kernel/vfs/file_object.rs