在 Fuchsia 中进行 Linux 系统调用

本页面介绍了 Starnix 如何在 Fuchsia 中处理 Linux 系统调用(系统调用)。

Zircon 进程中 Linux 程序的地址空间

Linux 程序必须先加载到 Zircon 进程中,然后才能进行系统调用。starnix_kernel 是负责在 Fuchsia 中加载和执行 Linux 程序的组件。

starnix_kernel 首先会创建一个 Zircon 进程,其地址空间分为两部分。上半部分是共享地址空间,下半部分是私有地址空间。然后,starnix_kernel 按以下方式设置 Zircon 进程的地址空间:

  • 将 Linux 程序加载到进程的私有地址空间(下半部分)。
  • starnix_kernel 本身被加载到进程的共享地址空间(上半部分)中。

地址空间 Linux 部分的构造与常规 Fuchsia 进程不同,以匹配 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. starnix_kernel 实例在同一容器中的所有 Linux 进程之间共享。

在普通执行模式下,这些进程中的任何线程都可以访问共享地址空间。不过,在受限执行模式下,线程只能访问其自身进程的私有地址空间。换句话说,当 Linux 程序在进程中运行时,Zircon 会限制其线程只能访问进程中地址空间的 Linux(私有)部分。

受限模式

图 3. 受限模式可防止 Linux 线程访问地址空间的 starnix_kernel 部分。

在受限模式下运行 Linux 程序

将 Linux 程序加载到进程后,starnix_kernel 会指示 Zircon 启动该进程。进程开始在会进入系统调用调度循环的地址空间的 starnix_kernel 部分执行。

starnix_kernel 用于检查任务状态以确定在哪里进入 Linux 程序。下面显示的 restricted_enter() 方法用于输入 Linux 程序中的特定位置(如需了解详情,请参阅 Zircon 中的 zx_restricted_enter 系统调用)。

// 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 系统调用的流程

在 Linux 程序进行系统调用之前,进程将继续运行。执行系统调用的汇编指令因架构而异,但它们都会导致将控制权交给 Zircon。当 Zircon 检查线程时,发现线程处于受限模式,Zircon 不会自行处理系统调用,而是立即跳回 Starnix。

为了了解 Linux 程序发出的系统调用,Starnix 会读取 Zircon 保存的受限模式寄存器状态。然后,Starnix 会调用此特定系统调用的处理程序。该系统调用的结果也会保存到受限模式注册状态。

返回系统调用后,Starnix 会再次指示 Zircon 将线程置于受限模式。此循环会一直持续,直到线程退出。

Linux 系统调用流

图 4. Zircon 进程中 Linux 系统调用的流程。

以下是图 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);
  }
}