本指南提供最佳做法,協助 Fuchsia 開發人員瞭解在 Starnix 中使用各種處理序間通訊 (IPC) 模式的取捨。
在 Starnix 中發出 FIDL 呼叫的最佳做法:
如要瞭解如何實際運用本指南列出的最佳做法,請參閱程式碼範例。
在 Starnix 中選取 FIDL 繫結
本節提供決策樹狀圖,協助開發人員在同步和非同步 FIDL 繫結之間做出選擇。
無法直接從 Starnix 系統呼叫叫用非同步 FIDL 呼叫。這是因為 Starnix 中的執行緒需要將控制權傳回 Linux。這表示您無法在 Starnix 中註冊非同步回呼處理常式。
建議改用下列其中一種模式,在 Starnix 中進行非同步 FIDL 呼叫:
- 在系統呼叫處理常式中同步執行所有作業。
- 在主要執行器上產生 Future。
- 使用執行器產生自己的執行緒。
圖 1. 這份流程圖以視覺化方式呈現 Starnix 中選取 FIDL 繫結的決策過程。
選擇其中一種模式時,應考量哪些因素?
針對 Linux 程式的系統呼叫所服務的 FIDL 呼叫,選取「同步 FIDL 繫結」。
這通常是預設做法。當 FIDL 呼叫需要從「使用者」執行緒發出,以服務 Linux 程式的系統呼叫時,就會套用這項規則。如需程式碼範例,請參閱「Starnix 中的同步 FIDL 繫結」。
不過請注意,如果要求遭到封鎖的時間相當長,提出要求的 Starnix 執行緒就會遭到封鎖,無法由 Linux 信號終止。Zircon 團隊已探討新增中斷
zx_channel_call
中遭封鎖執行緒的機制 (請參閱 Bug 42106200),但尚未找到令人滿意的設計。在 Starnix 支援這類機制前,請避免在同步 FIDL 呼叫中,無限期封鎖使用者執行緒。針對未與任何系統呼叫相關聯的 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 中的同步 Proxy:
// 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 中使用同步 Proxy 的程式碼範例:
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();