🚀 核心目標

今天的目標是實現「混合語言開發」。我們將撰寫一個 C 語言核心,並透過一個彙編跳板(Entry Point)將控制權從引導程式(Bootloader)交接給 C 語言的 main() 函式。


1. 為什麼不能直接執行 C 程式碼?

在作業系統開發中,我們面臨兩個核心挑戰:

  1. 沒有標準庫:不能用 printfmalloc,因為這些功能需要作業系統支援,而我們就是那個作業系統。
  2. 檔案佈局:C 編譯器生成的機器碼並不保證 main() 函式會放在檔案的第一個位元組。

為了確保 Bootloader 跳轉到正確的位置,我們需要一個 Kernel Entry 作為「指路標」。


2. 實作:核心與跳板

第一步:撰寫 C 核心 (kernel.c)

我們直接操作 VGA 顯示記憶體(0xb8000)來驗證 C 代碼是否正確運行。

// kernel.c
void main() {
    // 指向 VGA 顯示記憶體起始位址
    char* video_memory = (char*) 0xb8000;

    // 記憶體佈局:[字元, 顏色屬性, 字元, 顏色屬性...]
    video_memory[0] = 'X';
    video_memory[1] = 0x0f; // 0x0f: 黑底白字
    video_memory[2] = 'Y';
    video_memory[3] = 0x0f;
    video_memory[4] = 'Z';
    video_memory[5] = 0x0f;

    while(1); // 讓 CPU 停在這裡,不要退出
}

第二步:核心進入點 (kernel_entry.asm)

這段簡短的代碼會呼叫我們 C 檔案裡的 main 函式。

[bits 32]
[extern main]    ; 聲明 main 是一個外部符號(在 C 檔案裡定義)
call main        ; 呼叫 C 語言的 main 函式
jmp $            ; 無限循環,防止回傳

3. 修改引導程式載入核心 (boot.asm)

Bootloader 現在多了一個任務:從磁碟讀取核心並加載到記憶體 0x1000 處。

[org 0x7c00]
    KERNEL_OFFSET equ 0x1000    ; 核心載入的目標位址
    mov [BOOT_DRIVE], dl    ; BIOS 會把啟動磁碟號碼存在 DL,先存起來備用

    mov bp, 0x9000 ;stack for real mode
    mov sp, bp

    ; 1. 印出啟動訊息 (16-bit)
    mov si, MSG_REAL_MODE
    call print_string

    ; 2. 載入核心 (從磁碟讀取)
    call load_kernel

    ; 3. 切換到保護模式 (一去不回頭)
    call switch_to_pm      ; 這裡一去不回頭

    jmp $
    
%include "gdt.asm"
%include "print_string.asm"
%include "switch_to_pm.asm"

[bits 16]
load_kernel:
    mov si, MSG_LOAD_KERNEL
    call print_string

    mov bx, KERNEL_OFFSET   ; 設定讀取目標位址:ES:BX = 0x0000:0x1000
    mov dh, 15              ; 預計讀取 15 個磁區 (目前的 kernel.c 很小,15 綽綽有餘)
    mov dl, [BOOT_DRIVE]    ; 使用剛才存好的磁碟號
    call disk_load          ; 調用你之前的磁碟讀取函式
    ret

[bits 16]
; --- 磁碟讀取函式 (如果沒拆分出去,就放在這) ---
disk_load:
    pusha            ; 備份所有暫存器 (重要!)
    push dx          ; 備份傳入的 dx (包含 dh 讀取數量)

    mov ah, 0x02     ; BIOS 讀取磁區功能
    mov al, dh       ; 讀取 dh 個磁區
    mov ch, 0x00     ; 磁柱 0
    mov dh, 0x00     ; 磁頭 0
    mov cl, 0x02     ; 從第 2 磁區開始 (第 1 磁區是 Bootloader)

    int 0x13         ; 呼叫 BIOS 中斷
    jc disk_error    ; 如果進位標誌 (Carry Flag) 為 1,代表出錯

    pop dx           ; 還原當初要求的 dx
    cmp dh, al       ; 檢查實際讀取的數量 (al) 是否等於要求的數量 (dh)
    jne disk_error
    
    popa
    ret

disk_error:
    mov si, DISK_ERR_MSG
    call print_string
    jmp $

[bits 32]
BEGIN_PM:
    ; 到這裡時,GDT 已經設定好,段暫存器也已經更新
    ; 我們直接跳轉到 C 核心被載入的位置執行
    call KERNEL_OFFSET
    jmp $

BOOT_DRIVE      db 0
MSG_REAL_MODE   db "Started in 16-bit Real Mode", 0x0d, 0x0a, 0
MSG_LOAD_KERNEL db "Loading kernel into memory...", 0x0d, 0x0a, 0
DISK_ERR_MSG db "Disk read error!", 0

times 510-($-$$) db 0
dw 0xaa55