This guide contains best practices for helping Fuchsia developers navigate the tradeoffs of using various Inter-Process Communication (IPC) patterns in Starnix.
Best practices for making FIDL calls in Starnix:
And for the practical implementations of the best practices outlined in the guide, see Code examples.
Selecting FIDL bindings in Starnix
This section provides a decision tree to assist developers in selecting between synchronous and asynchronous FIDL bindings.
Asynchronous FIDL calls cannot be invoked directly from a Starnix syscall. This is because a thread in Starnix needs to return control back to Linux. This means there's no way to register an asynchronous callback handler in Starnix.
Instead, consider one of the following patterns for making asynchronous FIDL calls in Starnix:
- Do everything synchronously in a syscall handler.
- Spawn a future on the main executor.
- Spawn your own thread with an executor.
Figure 1. This flowchart visualizes the decision-making process for selecting FIDL bindings in Starnix.
What should I consider when choosing one of these patterns?
Select synchronous FIDL binding for the FIDL calls that serve the syscalls of a Linux program.
This is generally the default approach. It applies when the FIDL call needs to be made from a "user" thread that is servicing the syscalls of a Linux program. For code examples, see Synchronous FIDL binding in Starnix.
However, keep in mind that if a request can be blocked for a non-trivial amount of time, the Starnix thread making the request, which is blocked, cannot be killed by a Linux signal. The Zircon team is working on adding a mechanism for interrupting a thread that is blocked in a
zx_channel_call
. But until Starnix supports such a mechanism, you need to be careful about when to block Starnix threads in synchronous FIDL calls.Select asynchronous FIDL binding for the FIDL calls that are not associated with any syscalls.
In particular, consider the following cases:
- Multiple calls need to be composed and waited on concurrently.
- The FIDL protocol is meant to wake the container from suspend.
- The FIDL protocol uses features that are difficult to implement in synchronous bindings, such as requiring it to serve another protocol.
- The operation needs to be interruptible by a signal from another Linux
thread. If a FIDL call needs to be interruptible, it can't be a synchronous
call; you need to be able to block the syscall thread on a
Waiter
so that it can receive signals.
For asynchronous FIDL binding, there are two thread spawning techniques to consider:
Spawning a future onto the main thread's executor, which is generally the default case. For code examples, see Spawn a future directly onto the main thread's executor.
Spawning your own thread, which is an exceptional case that is useful because the developer gets control over when the executor runs, and it is possible to mix synchronous and asynchronous work on the same thread without blocking asynchronous work spawned by other parts of Starnix. For code examples, see Spawn a separate thread that runs its own executor.
Selecting namespaces in Starnix
This section clarifies the use of kernel and container namespaces for accessing FIDL protocols in Starnix.
A single Starnix program instance acts as two components: one is the kernel component (run by the Starnix runner and the other is the container component (run by the kernel component). This means there are two places where capabilities may be routed to be consumed by the same program. The API to use here depends on where the capability is routed. (For more information on the namespaces in Starnix, see Making Linux syscalls in Fuchsia.
Consider the following namespaces in Starnix that can provide access to FIDL protocols:
- Kernel namespace - This namespace is accessible with normal
fuchsia_component
calls. - Container namespace - For code examples, see Container namespace in Starnix.
Code examples
This section provides code examples illustrating the different IPC patterns described in this guide:
- Synchronous FIDL binding in Starnix
- Asynchronous FIDL binding in Starnix
- Container namespace in Starnix
These examples are sourced from various parts of the Starnix kernel for providing practical implementations of the best practices outlined in the guide.
Synchronous FIDL binding in Starnix
This section includes code examples that illustrate the standard approach for handling FIDL calls directly from within syscall handlers, representing a typical implementation of synchronous FIDL binding.
A code example that connects to a synchronous proxy in 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)
};
(Source: //src/starnix/kernel/runner/container.rs
)
A code example that uses a synchronous proxy in 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(())
}
(Source: //src/starnix/kernel/task/scheduler.rs
)
Asynchronous FIDL binding in Starnix
The section contains code examples illustrating two different approaches for handling asynchronous FIDL calls in Starnix:
- Spawn a future directly onto the main thread's executor
- Spawn a separate thread that runs its own executor
Spawn a future directly onto the main thread's executor
A code example that spawns a future directly onto the executor of Starnix's main thread:
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;
});
(Source: //src/starnix/kernel/task/interval_timer.rs
)
Spawn a separate thread that runs its own executor
A code example that spawns a separate kthread
that runs its own executor:
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 {
(Source: //src/starnix/kernel/device/framebuffer_server.rs
)
Container namespace in Starnix
This section contains a code example that demonstrates the syntax for accessing container-based FIDL protocols located within the container component.
A code example showing how to connect to the CryptManagement
protocol in the
container namespace:
let crypt_management_proxy = current_task
.kernel()
.connect_to_protocol_at_container_svc::<CryptManagementMarker>()
.map_err(|_| errno!(ENOENT))?
.into_sync_proxy();
(Source: //src/starnix/kernel/vfs/file_object.rs
)