🚩 核心目標

在 Day 17 中,我們將用戶程序關進了 Ring 3 的「監獄」。今天,我們為這座監獄安裝了一扇受控的窗戶 —— 系統調用 (System Call)。透過 int 0x80 指令,用戶程序現在可以安全地請求內核執行高權限操作,例如調用 kprintf 在螢幕上輸出字符。


1. 基礎理論:中斷門 (Interrupt Gate) 與特權切換

由於用戶態無法直接執行 I/O 指令,必須透過一種「主動觸發異常」的方式進入內核。

1.1 系統調用的流程

  1. 觸發:用戶程序將「系統調用號」放入 eax,參數放入其他寄存器(如 ebx, ecx),執行 int 0x80
  2. 切換:CPU 查找 IDT(中斷描述符表),發現 0x80 號中斷是一個「陷阱門」,自動切換回 Ring 0 環境。
  3. 分發:內核的中斷處理程序根據 eax 的值,跳轉到對應的內核函數。
  4. 返回:執行完畢後,透過 iret 返回 Ring 3。

2. 代碼實作:從用戶態到顯存輸出

我們實作了第一個實用的系統調用:sys_print

2.1 內核端的系統調用表

我們定義了一個函數指針數組,將調用號( 1 代表 Print)映射到真正的 kprintf 實現。

2.2 用戶端的封裝

用戶不再直接寫彙編,而是透過一個包裝好的 C 函數進行調用:

// 用戶態調用代碼
static inline int syscall(int num, int arg1) {
    int ret;
    __asm__ __volatile__ (
        "int $0x80"
        : "=a"(ret)             // 返回值存於 eax
        : "a"(num), "b"(arg1)   // 編號存於 eax, 參數存於 ebx
    );
    return ret;
}
// 內核端初始化函數以及中斷功能表定義
// 系統調用表
static void *syscall_table[MAX_SYSCALLS] = {
    [1] = sys_print
};

void syscall_dispatch(registers_t *regs)
{
    if (regs->eax >= MAX_SYSCALLS)
    {
        kprintf("Unknown syscall: %d\\n", regs->eax);
        return;
    }

    void (*handler)(registers_t*) = syscall_table[regs->eax];
    if (handler) {
        handler(regs);
    }
}

void init_syscall()
{
    register_interrupt_handler(128, syscall_dispatch);
}