這一系列是我讀 The little book about OS development 的一些實作紀錄,此篇為介紹有關於 C 的使用,讓組合語言和 C 語言可以結合在一起。
組合語言雖然可以很直接地和電腦溝通,但對於人類來說難以理解。C 語言相對親民得多,所以在控制系統上,我們會傾向用 C 語言,除非有必要才會使用組語。接下來會介紹如何由組合語言呼叫 C 語言所寫的函式。以這本 little book about OS 而言,他們使用 cdecl 呼叫慣例,這也是 C 和 C++ 程式的預設呼叫慣例。
堆疊 Stack
C 語言有個很重要的資料結構概念叫做「Stack」。函數內的參數會從右到左依序堆疊到 stack 裡面,並將結果儲存在 eax
暫存器中。用下面簡單的 C 程式碼和組語說明:
int sum_of_three(int arg1, int arg2, int arg3){ return arg1 + arg2 + arg3; }
組語:
external sum_of_three ; 在別的地方定義函式 sum_of_three push dword 3 ; push arg3 push dword 2 ; push arg2 push dword 1 ; push arg1 call sum_of_three ; 呼叫函式,並且把定義存在 eax 內
Struct 的大小
接下來要理解的是 struct 資料結構的大小計算,不過這裡我打算獨立一篇文章來討論,所以這邊不多加說明,屆時會再放連結在這邊。
在本次的目的中,主要是強調 packing 和 padding 的概念。
編譯 C 語言
先隨便寫個程式碼,儲存檔名為 kmain.c
,裡面有個名字為 kmain
的函式:
void kmain(){}
當我們程式寫好,最後當然還是必須能成功編譯才有用。我們這個迷你的作業系統目前啥都沒有,不能使用任何的標準函式庫,所以會需要很多個 flags(旗標)。
接下來寫個 Makefile。對了,下面是為了閱讀美觀用了很多空白格對齊,但實際操作不要複製下方的檔按,主要是因為 Makefile 內的 tab 和空格有不同的用法,若直接複製下方會有錯誤。
OBJECTS = loader.o kmain.o CC = gcc CFLAGS = -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector \ -nostartfiles -nodefaultlibs -Wall -Wextra -Werror -c LDFLAGS = -T link.ld -melf_i386 AS = nasm ASFLAGS = -f elf all: kernel.elf kernel.elf: $(OBJECTS) ld $(LDFLAGS) $(OBJECTS) -o kernel.elf os.iso: kernel.elf cp kernel.elf iso/boot/kernel.elf genisoimage -R \ -b boot/grub/stage2_eltorito \ -no-emul-boot \ -boot-load-size 4 \ -A os \ -input-charset utf8 \ -quiet \ -boot-info-table \ -o os.iso \ iso run: os.iso bochs -f bochsrc.txt -q %.o: %.c $(CC) $(CFLAGS) $< -o $@ %.o: %.s $(AS) $(ASFLAGS) $< -o $@ clean: rm -rf *.o kernel.elf os.iso
我上傳一個正確版本,但很亂:Makefile ,也可以參考原著 Getting to C 的章節。
針對 flag 簡單抓幾個解釋一下:
-m32
:編譯 32-bit 程式-nostdlib
:不使用標準函式庫-nostdinc
:不要把系統提供的標頭檔給包進來-fno-builtin
:不使用 C 語言內建的函式-fno-stack-protector
:關閉 stack protector-Wall -Wextra -Werror
:把所有警告變為 error
並且把這兩個檔案 kmain.c
和 Makefile
放入和 loader.s
同一層的位置,並且在這個位置執行指令:
make run
正常來說可以看見 Bochs 啟動。記得和之前一樣,在終端機按 c 並 Enter 後,讓它繼續執行。