從零開始制作 MyOS(四)
從零開始制作 MyOS(四)—— 跳轉到 C 語言編寫的小型內核
目標
在引導加載器準備好后,用 C 語言編寫一個小型內核并實現從引導加載器跳轉到內核執行。
代碼
引導程序文件
org 0x7C00
bits 16
start:
; 初始化段寄存器
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
; 顯示啟動信息
mov si, msg_loading
call print_string
; 加載內核到 0x10000
mov ax, 0x1000
mov es, ax
xor bx, bx
mov ah, 0x02 ; 讀扇區
mov al, 4 ; 4個扇區
mov ch, 0 ; 柱面0
mov cl, 2 ; 扇區2
mov dh, 0 ; 磁頭0
mov dl, 0x80 ; 驅動器
int 0x13
jc disk_error
; 切換到保護模式
cli
lgdt [gdt_descriptor]
; 啟用A20
in al, 0x92
or al, 2
out 0x92, al
mov eax, cr0
or eax, 1
mov cr0, eax
jmp CODE_SEG:init_pm
disk_error:
mov si, msg_error
call print_string
jmp $
print_string:
lodsb
test al, al
jz .done
mov ah, 0x0E
int 0x10
jmp print_string
.done:
ret
bits 32
init_pm:
mov ax, DATA_SEG
mov ds, ax
mov es, ax
mov ss, ax
mov esp, 0x90000
; 跳轉到內核
jmp 0x10000
; 數據區
msg_loading db "Booting...", 0xD, 0xA, 0
msg_error db "Disk error!", 0
; GDT
gdt_start:
dq 0
gdt_code:
dw 0xFFFF
dw 0
db 0
db 10011010b
db 11001111b
db 0
gdt_data:
dw 0xFFFF
dw 0
db 0
db 10010010b
db 11001111b
db 0
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
times 510-($-$$) db 0
dw 0xAA55
內核入口文件
section .text
global _start
_start:
mov esp, 0x90000 ; 設置棧指針
extern kernel_main
call kernel_main ; 調用C內核
hlt
小型內核文件
void kernel_main(void) {
char *vga = (char*)0xB8000;
// 清屏
for (int i = 0; i < 80 * 25 * 2; i += 2) {
vga[i] = ' ';
vga[i + 1] = 0x07;
}
// 顯示字符串
char *msg = "Hello from C Kernel!";
for (int i = 0; msg[i]; i++) {
vga[i * 2] = msg[i];
vga[i * 2 + 1] = 0x07;
}
while(1) asm("hlt");
}
鏈接器
ENTRY(_start)
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
}
Makefile
CC = gcc
LD = ld
ASM = nasm
CFLAGS = -m32 -ffreestanding -nostdlib -c
ASFLAGS = -f elf32
LDFLAGS = -m elf_i386 -T linker.ld -nostdlib
all: os.img
os.img: boot.bin kernel.bin
dd if=/dev/zero of=os.img bs=512 count=2880
dd if=boot.bin of=os.img conv=notrunc
dd if=kernel.bin of=os.img bs=512 seek=1 conv=notrunc
boot.bin: boot.asm
$(ASM) -f bin boot.asm -o boot.bin
kernel.bin: kernel.elf
objcopy -O binary kernel.elf kernel.bin
kernel.elf: entry.o kernel.o
$(LD) $(LDFLAGS) -o kernel.elf entry.o kernel.o
kernel.o: kernel.c
$(CC) $(CFLAGS) kernel.c -o kernel.o
entry.o: entry.asm
$(ASM) $(ASFLAGS) entry.asm -o entry.o
clean:
rm -f *.o *.bin *.elf os.img
run: os.img
qemu-system-x86_64 -drive format=raw,file=os.img
.PHONY: all clean run
編譯運行
# ubuntu 中,VGA 顯示
make clean && make run
運行結果
當終端打印出來“Hello from C Kernel!”,就表示運行成功了

問題
本節遇到最大的問題是內核跳轉不過來,目前只找到以下一些可能:
磁盤問題
-
段寄存器設置順序錯誤
- 先設置ES再操作磁盤
-
缺少錯誤重試機制?
+ -
扇區數不足
A20地址線啟用問題
-
and al, 0xFE會錯誤清除bit 0(可能影響系統重啟)
-
缺少狀態檢查?:未驗證A20是否真正啟用成功
保護模式跳轉問題
- 無效跳轉指令,jmp 0x10000未使用段選擇子,在保護模式下會崩潰
- 缺少段寄存器初始化?:未正確設置DS/ES/SS等數據段寄存器
內核入口對齊問題
- 未聲明全局符號?:_start未在entry.asm中聲明為global
- ?棧未對齊?:x86要求棧指針按16字節對齊
編譯鏈接問題
- 缺少內核地址指定?:鏈接腳本未強制指定內核加載地址為0x10000
- 未處理BSS段?:未清零未初始化數據段

浙公網安備 33010602011771號