本页面介绍了 Starnix 如何在 Fuchsia 中处理 Linux 系统调用(系统调用)。
Zircon 进程中 Linux 程序的地址空间
Linux 程序必须先加载到 Zircon 进程中,然后才能进行系统调用。starnix_kernel
是负责在 Fuchsia 中加载和执行 Linux 程序的组件。
starnix_kernel
首先会创建一个 Zircon 进程,其地址空间分为两部分。上半部分是共享地址空间,下半部分是私有地址空间。然后,starnix_kernel
按以下方式设置 Zircon 进程的地址空间:
- 将 Linux 程序加载到进程的私有地址空间(下半部分)。
starnix_kernel
本身被加载到进程的共享地址空间(上半部分)中。
地址空间 Linux 部分的构造与常规 Fuchsia 进程不同,以匹配 Linux 程序预期的布局。
图 1. 通过 Linux 程序和 starnix_kernel
加载的 Zircon 进程的地址空间。
共享 Starnix 实例
每个 Linux 程序都会加载到单独的 Zircon 进程中,并且每个 Linux 线程都在专用的 Zircon 线程中运行。但是,Starnix 容器中的所有 Zircon 进程共享相同的 starnix_kernel
实例。此共享 starnix_kernel
实例用于管理容器中 Linux 程序的所有状态,例如文件描述符表、全局虚拟文件系统和正在运行的线程表。
如需详细了解运行 starnix_kernel
的 Zircon 进程之间共享的状态,请参阅受限模式 RFC 和 ShareableProcessState
结构体。
图 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 将线程置于受限模式。此循环会一直持续,直到线程退出。
图 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);
}
}