本頁說明 Starnix 如何處理 Fuchsia 中的 Linux 系統呼叫 (系統呼叫)。
Zircon 程序中 Linux 程式的位址空間
在 Linux 程式可以發出任何系統呼叫之前,必須先將其載入 Zircon 程序。starnix_kernel
是負責在 Fuchsia 中載入及執行 Linux 程式的元件。
starnix_kernel
會先建立一個 Zircon 程序,其中包含將位址空間分割成兩個部分。上半部是共用位址空間,下半部是私人位址空間。starnix_kernel
接著,透過下列方式設定 Zircon 程序的位址空間:
- Linux 程式會載入程序的私人位址空間 (下半部)。
starnix_kernel
本身會載入至程序的共用位址空間 (上半部)。
與一般 Fuchsia 程序相比,位址空間的 Linux 部分的結構不同,以符合 Linux 程式的預期版面配置。
圖 1. 透過 Linux 程式和 starnix_kernel
載入的 Zircon 程序位址空間。
共用的 Starnix 執行個體
每個 Linux 程式都會載入至獨立的 Zircon 程序,而每個 Linux 執行緒都會在專屬的 Zircon 執行緒中執行。不過,Starnix 容器中的所有 Zircon 程序都會共用同一個 starnix_kernel
執行個體。這個共用的 starnix_kernel
執行個體會管理容器中的所有 Linux 程式狀態,例如檔案描述元資料表、全域虛擬檔案系統以及執行執行緒的資料表。
如要進一步瞭解執行 starnix_kernel
的 Zircon 程序之間共用的狀態,請參閱受限模式 RFC 和 ShareableProcessState
結構。
圖 2:同一個容器中的所有 Linux 程序會共用 starnix_kernel
執行個體。
在一般執行模式下,這些程序中的任何執行緒都可以存取共用位址空間。但在受限執行模式下,執行緒只能存取自身程序的私人位址空間。換句話說,當 Linux 程式以程序執行時,Zircon 會限制其執行緒只能存取程序中位址空間的 Linux (私人) 部分。
圖 3 嚴格篩選模式會禁止 Linux 執行緒存取位址空間的 starnix_kernel
部分。
以受限模式執行 Linux 程式
將 Linux 程式載入至處理程序後,starnix_kernel
會指示 Zircon 啟動該程序。程序會從進入 syscall 分派迴圈的位址空間 starnix_kernel
部分開始執行。
starnix_kernel
會檢查工作狀態,以判斷進入 Linux 程式的位置。以下顯示的 restricted_enter()
方法是用於在 Linux 程式中輸入特定位置 (詳情請參閱 Zircon 中的 zx_restricted_enter
syscall)。
// Enter the restricted portion of the address space at the location pointed to by `rip`.
restricted_enter(task.registers.rip);
不過,完整呼叫比上述範例還要複雜。這個方法會透過稱為「補充車」的個別 VMO 取得受限模式的完整註冊狀態做為輸入項目。(如要進一步瞭解這個 VMO「補充資訊」,用於儲存包含受限模式所用架構狀態的 zx_restricted_state
物件,請參閱 Zircon 中的 zx_restricted_bind_state
系統呼叫)。
呼叫 restricted_enter()
方法後,Zircon 會留意執行緒進入受限模式,並在跳至 Linux 程式碼之前,將執行緒設為僅存取私人位址空間。
Linux syscall 的流程
此程序將持續執行,直到 Linux 程式發出 syscall 為止。執行 Sys 呼叫的組裝指令會因架構而異,但這些指令都會將控制權轉移至 Zircon。Zircon 檢查執行緒並發現執行緒處於受限模式時,不會自行處理系統呼叫,而 Zircon 會立即跳到 Starnix。
為了找出 Linux 程式建立的系統呼叫,Starnix 會讀取 Zircon 儲存的受限模式註冊狀態。接著,Starnix 會針對這個特定系統呼叫叫用處理常式。此系統呼叫的結果也會儲存在受限模式的註冊狀態。
傳回系統呼叫後,Starnix 會再次指示 Zircon 將執行緒設為受限模式。這個迴圈會持續到執行緒結束為止。
圖 4 Zircon 程序中 Linux syscall 的流程。
以下是圖 4 中流程的虛擬程式碼表示法:
loop {
// Enter restricted mode at the instruction pointer address.
restricted_enter(task.registers.rip);
// Update the register state of the task to match what Zircon saved before exiting
// restricted mode.
task.registers = thread_state_general_regs_t::from(&restricted_state);
// Figure out which syscall was made based on the appropriate register value.
let syscall = Syscall::from_number(task.registers.rax);
// Execute the syscall for the current task.
execute_syscall(task, syscall);
// After handling the syscall, check for signals, exit status, etc. before
// continuing the loop.
if let Some(exit_status) = process_completed_syscall(task)? {
return Ok(exit_status);
}
}