在 Fuchsia 中進行 Linux syscall

本頁說明 Starnix 如何處理 Fuchsia 中的 Linux 系統呼叫 (系統呼叫)。

Zircon 程序中 Linux 程式的位址空間

在 Linux 程式可以發出任何系統呼叫之前,必須先將其載入 Zircon 程序。starnix_kernel 是負責在 Fuchsia 中載入及執行 Linux 程式的元件。

starnix_kernel 會先建立一個 Zircon 程序,其中包含將位址空間分割成兩個部分。上半部是共用位址空間,下半部是私人位址空間。starnix_kernel 接著,透過下列方式設定 Zircon 程序的位址空間:

  • Linux 程式會載入程序的私人位址空間 (下半部)。
  • starnix_kernel 本身會載入至程序的共用位址空間 (上半部)。

與一般 Fuchsia 程序相比,位址空間的 Linux 部分的結構不同,以符合 Linux 程式的預期版面配置。

用於 Zircon 程序的位址空間

圖 1. 透過 Linux 程式和 starnix_kernel 載入的 Zircon 程序位址空間。

共用的 Starnix 執行個體

每個 Linux 程式都會載入至獨立的 Zircon 程序,而每個 Linux 執行緒都會在專屬的 Zircon 執行緒中執行。不過,Starnix 容器中的所有 Zircon 程序都會共用同一個 starnix_kernel 執行個體。這個共用的 starnix_kernel 執行個體會管理容器中的所有 Linux 程式狀態,例如檔案描述元資料表、全域虛擬檔案系統以及執行執行緒的資料表。

如要進一步瞭解執行 starnix_kernel 的 Zircon 程序之間共用的狀態,請參閱受限模式 RFCShareableProcessState 結構。

共用的 Starnix 執行個體

圖 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 將執行緒設為受限模式。這個迴圈會持續到執行緒結束為止。

Linux syscall 的流程

圖 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);
  }
}