Fuchsia 系统调用的生命周期

概览

当应用调用系统调用时,执行会经历多个阶段。为了减少样板代码,使用 zither 工具生成了特定于系统调用的逻辑。在深入了解上述每个阶段的运作方式之前,最好先了解此代码的生成方式。

用户模式和内核模式块的示意图,以及特定代码块的创建方式。

使用 zither 生成代码

Fuchsia 系统调用在 //zircon/vdso 中的 FIDL 文件中声明。

zx_channel_create 的声明示例:

library zx;

@transport("Syscall")
protocol channel {
    channel_create(struct {
        options uint32;
    }) -> (resource struct {
        status status;
        out0 handle;
        out1 handle;
    });
};

构建内核时,fidlc(FIDL 前端)会获取这些 FIDL 文件并生成 FIDL 中间表示 (IR) JSON 文件。IR 文件生成于 //out/default/gen/zircon/vdso/zx.fidl.json。

Zither 会读取此 IR 文件,并生成源文件,这些源文件会使用每个系统调用的输入和输出来调用 C++ 宏。这些宏是按架构定义的,以便 Zither 输出与架构无关的源代码。

示例扬琴输出:

KERNEL_SYSCALL(channel_create, zx_status_t, /* no attributes */, 3,
    (options, out0, out1), (
    uint32_t options,
    _ZX_SYSCALL_ANNO(acquire_handle("Fuchsia")) zx_handle_t* out0,
    _ZX_SYSCALL_ANNO(acquire_handle("Fuchsia")) zx_handle_t* out1))

x86 实现示例:

#define KERNEL_SYSCALL(name, type, attrs, nargs, arglist, prototype) \
  m_syscall zx_##name, ZX_SYS_##name, nargs, 1

.macro m_syscall name, num, nargs, public
syscall_entry_begin \name
    .cfi_same_value %r12
    .cfi_same_value %r13
.if \nargs <= 3
    zircon_syscall \num, \name, \name
    ret
.endif

.macro zircon_syscall num, name, caller
    mov $\num, %eax
    syscall
// This symbol at the return address identifies this as an approved call site.
    .hidden CODE_SYSRET_\name\()_VIA_\caller
CODE_SYSRET_\name\()_VIA_\caller\():
.endm

在整个系统调用阶段,都可以看到这种将 Zither 生成的源代码与 C++ 宏和汇编例程相连接的模式。

计划

如需使用系统调用,您应包含来自 Fuchsia SDK 的 <zircon/syscalls.h> 标头,该标头由 zither 生成。虽然在编译期间,程序可以使用标头,但实现仅在运行时在 vDSO 内部可用。

vDSO

虚拟动态共享对象 (vDSO) 是一个 ELF 文件,其中包含每个系统调用的用户空间实现。vDSO 中的汇编例程大多由 zither 生成,但都具有相同的结构:

  1. 将用户提供的实参保存到特定于架构的寄存器
  2. 将系统调用号存储到特定于架构的寄存器(x86 为 %eax)
  3. 将上下文切换到内核(x86 为 syscall)

可以使用以下命令查看 vDSO 中的例程:

$ objdump -d `find out/default.zircon -name libzircon.so.debug` | less

zx_channel_create 的 x86 实现:

0000000000007a70 <_zx_channel_create>:
    7a70:       b8 03 00 00 00          mov    $0x3,%eax
    7a75:       0f 05                   syscall

0000000000007a77 <CODE_SYSRET_zx_channel_create_VIA_zx_channel_create>:
    7a77:       c3                      retq

构建内核时,vDSO 会作为内核中的字符数组进行链接,然后加载到 vmo 中。在启动期间,一些常量会被写入 vDSO,以允许用户空间程序查询这些常量,而无需跳转到内核。

在调用程序的入口点之前,ld.so 会将 vDSO 映射到内存中。为了防止 return-to-libc 攻击,vDSO 会放置在进程地址空间中的随机位置,并且基地址会通过特定寄存器提供给第一个线程。

vDSO 在 libc 提供的入口点中动态链接到用户程序

进程加载和 vDSO 的序列图

系统调用处理程序

为了在特权模式下接收系统调用,内核会在启动时注册系统调用处理程序。调用此例程时,系统会使用系统调用号来索引调度例程的映射。

x86 实现://zircon/kernel/arch/x86

write_msr(X86_MSR_IA32_LSTAR, (uint64_t)&x86_syscall);

FUNCTION_LABEL(x86_syscall)
  leaq    .Lcall_wrapper_table(%rip), %r11
  movq    (%r11,%rax,8), %r11
  jmp     *%r11
END_FUNCTION(x86_syscall)

调度例程负责将系统调用实参移至适当的寄存器,以便作为 C 函数实参提取,并调用封装函数。这些例程在 build 时使用 syscall_dispatch 宏和 zither 生成的源文件生成。

#define KERNEL_SYSCALL(name, type, attrs, nargs, arglist, prototype) \
  syscall_dispatch nargs, name

KERNEL_SYSCALL(channel_create, zx_status_t, /* no attributes */, 3,
    (options, out0, out1), (
    uint32_t options,
    _ZX_SYSCALL_ANNO(acquire_handle("Fuchsia")) zx_handle_t* out0,
    _ZX_SYSCALL_ANNO(acquire_handle("Fuchsia")) zx_handle_t* out1))

.macro syscall_dispatch nargs, syscall
  LOCAL_FUNCTION(.Lcall_\syscall\())
    // move args around
    pre_\nargs\()_args
    // calls wrapper
    call wrapper_\syscall
    // cleans up
    post_\nargs\()_args
  END_FUNCTION(.Lcall_\syscall\())
.endm

系统调用封装容器

系统调用封装函数由 zither 生成,负责调用系统调用实现,然后将所有句柄复制回客户端。这些封装容器的命名惯例为 wrapper_<syscall>。

syscall_result wrapper_channel_create(uint32_t options, zx_handle_t* out0, zx_handle_t* out1, uint64_t pc) {
    return do_syscall(ZX_SYS_channel_create, pc, &VDso::ValidSyscallPC::channel_create, [&](ProcessDispatcher* current_process) -> uint64_t {
        zx_handle_t out_handle_out0;
        zx_handle_t out_handle_out1;
        auto result = sys_channel_create(options, &out_handle_out0, &out_handle_out1);
        if (result != ZX_OK)
            return result;
        result = make_user_out_ptr(SafeSyscallArgument<zx_handle_t*>::Sanitize(out0))
                                   .copy_to_user(out_handle_out0);
        if (result != ZX_OK) {
            // We should never fail to copy out a handle to userspace. If we do, a
            // handle will be leaked, so throw a SignalPolicyException.
            Thread::Current::SignalPolicyException(ZX_EXCP_POLICY_CODE_HANDLE_LEAK, 0u);
        }
        result = make_user_out_ptr(SafeSyscallArgument<zx_handle_t*>::Sanitize(out1))
                    .copy_to_user(out_handle_out1);
        if (result != ZX_OK) {
            // We should never fail to copy out a handle to userspace. If we do, a
            // handle will be leaked, so throw a SignalPolicyException.
            Thread::Current::SignalPolicyException(ZX_EXCP_POLICY_CODE_HANDLE_LEAK, 0u);
        }
        return result;
    });
}

系统调用实现

系统调用实现是一个手写函数,命名惯例为 sys_<syscall>。这些函数包含系统调用的核心逻辑,并且与架构无关。

//zircon/kernel/lib/syscalls/channel.rs

zx_status_t sys_channel_create(...) {
   ...
}

附录

用于创建新系统调用的示例提交。