總覽
應用程式叫用系統呼叫時,執行作業會經過多個階段。為減少樣板程式碼,系統會使用 zither 工具產生 syscall 專屬邏輯。在深入探討上述各階段的運作方式之前,最好先瞭解這段程式碼的生成方式。

使用 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 檔案,並產生來源檔案,透過每個 syscall 的輸入和輸出內容叫用 C++ 巨集。這些巨集是依架構定義,可讓 zither 輸出與架構無關的來源。
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
在整個 syscall 階段中,都可以看到這種將 zither 產生的來源與 C++ 巨集和組合常式連結的模式。
計畫
如要使用 syscall,請加入 Fuchsia SDK 的 <zircon/syscalls.h> 標頭,該標頭是由 zither 產生。雖然程式在編譯期間可以使用標頭,但實作只能在 vDSO 內的執行階段使用。
vDSO
虛擬動態共用物件 (vDSO) 是 ELF 檔案,包含每個系統呼叫的使用者空間實作。vDSO 中的組合常式大多由 zither 產生,但結構都相同:
- 將使用者提供的引數儲存至架構專屬暫存器
- 將 syscall 編號儲存至架構專屬暫存器 (x86 為
%eax) - 將情境切換至核心 (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 會與核心一起封裝為 ELF 檔案。在啟動期間,physboot 會將 vDSO 映像檔交給 vmo 中的核心,核心會從中建立 vDSO。在啟動期間,系統會將部分常數寫入 vDSO,讓使用者空間程式可以查詢這些常數,而不必跳至核心。
在呼叫程式的進入點之前,ld.so 會將 vDSO 對應至記憶體。為防止 return-to-libc 攻擊,vDSO 會放置在程序位址空間中的隨機位置,而基準位址則會提供給特定暫存器中的第一個執行緒。
vDSO 會在 libc 提供的進入點中,動態連結至使用者程式

Syscall 處理常式
為了在特殊權限模式下接收系統呼叫,核心會在啟動時註冊系統呼叫處理常式。呼叫這個常式時,系統會使用 syscall 編號,在調度常式對應表中建立索引。
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)
分派常式負責將 syscall 引數移至適當的暫存器,以便做為 C 函式引數,並呼叫包裝函式。系統會在建構時使用 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
Syscall 包裝函式
syscall 包裝函式函式是由 zither 產生,負責呼叫 syscall 實作,然後將任何控制代碼複製回用戶端。這些包裝函式的命名慣例為 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;
});
}
Syscall 實作
syscall 實作項目是手寫函式,命名慣例為 sys_<syscall>。這些函式包含系統呼叫的核心邏輯,且與架構無關。
//zircon/kernel/lib/syscalls/channel.rs
zx_status_t sys_channel_create(...) {
...
}
附錄
建立新 syscall 的修訂版本範例。