自制一個 Linux 作業系統(4) — Ready for C

YC
6 min readMar 4, 2020

--

這系列的文章會記錄我嘗試做出一個 Linux OS kernel的過程,要主是依照 https://littleosbook.github.io/ 這系列的文章,加上自已踩下的坑,希望能幫助同樣有做一個作業系統夢想的各位。

進入第三章,這是一章介紹性章節,告訴我們可以開始使用 C 來為我們的 OS 服務了,主要關注於如何讓 Assembly 跟 C 可以連結在一起使用。

首先,要知道使用 C 的主要因原是因為 “Stack” —— 所有有意義的 C programs 都會使用到 Stack。

要在 Assembly 中呼叫 C 的 function,文章中提到是使用 cdecl calling convention (呼叫慣例) 方法,因為這是 GCC 在使用的,更多詳情可到 x86 calling conventions 查看。主要與 Stack 有關的是,function 的參數會以 Stack 的方式,從按照從右至左的順序依次推到 (push) Stack 裡面,而 function 結果儲存在 eax register 中,如以下範例 :

/* The C function */
int sum_of_three(int arg1, int arg2, int arg3)
{
return arg1 + arg2 + arg3;
}
; The assembly code
external sum_of_three ; the function sum_of_three is defined
elsewhere


push dword 3 ; arg3
push dword 2 ; arg2
push dword 1 ; arg1
call sum_of_three ; call the function, the result will be in
eax

然後我們需要了解一下,什麼是 Packed Structures

先了解一下什麼是 Padding (可以參考這篇 stack overflow)。
struct 中的每個成員都應該要位於可按其大小整除的地址,編譯器會在你的 struct 裡面為你的成員間或最後插入空間,目的是為了對齊 (align) 記憶體位置的邊界,這樣做是為了使硬件更輕鬆,更有效率。

Packing 則是把 Padding 都移除掉,使 struct 保持著原有的大小 。(在以下的內容,我們希望能刻意使 struct 的大小為 32 bits,但因為有 Padding 機制存在,所以我們需要 Packing 來控制大小)。

現在我們需要用 struct 來放置我們的 “configuration bytes”,以下的例子為 32 bits 系統的 configuration bytes :

Bit:     | 31     24 | 23          8 | 7     0 |
Content: | index | address | config |

因為硬體會認為 struct 是一個 32 bits unsigned int,我們只需要使用__attribute__((packed)) 就可以強制 GCC 不加 padding。

struct example {
unsigned char config; /* bit 0 - 7 ,char:1-byte = 8-bits */
unsigned short address; /* bit 8 - 23 ,short:2-byte = 16-bits */
unsigned char index; /* bit 24 - 31 */
} __attribute__((packed));

我們現在可以開始試著編譯 C 了,因為我們的 OS 不能使用任個的 standard library,所以我們需要使用以下的 flags 來編譯 C :

-m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector
-nostartfiles -nodefaultlibs
  1. -m32 : 可以在配置為默認編譯 64-bit 對象的編譯器上編譯 32-bit 對象。
  2. -nostdlib : 不使用 standard library。
  3. -nostdinc : 不要把系統提供的標頭檔給包進來。
  4. -fno-builtin : 不使用C 中的內建 function。
  5. -fno-stack-protector : 關閉 stack protector,stack protector 的作用是防止出現緩衝區溢位 (Stack buffer overflow)
  6. -nostartfiles : 不使用 standard system startup files。
  7. -nodefaultlibs : 不使用 standard system librarie。

而且在寫 C 時,我們習慣盡量把所有警告變為 error,這可以使我們的 code 更安全且不易出錯 :

-Wall -Wextra -Werror

現在我們可以創建一個 C 檔 kmain.c,並在裡面創建一個 kmain function。這將會被 loader.s 使用。

void kmain(){} //kmain.c

最後,我們創建一個 makefile :

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.elfkernel.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

這會是現在的資料夾結構 :

.
|-- bochsrc.txt
|-- iso
| |-- boot
| |-- grub
| |-- menu.lst
| |-- stage2_eltorito
|-- kmain.c
|-- loader.s
|-- Makefile
|-- link.ld

(文章這邊少打了一個 link.ld 檔。)

我們在 makefile 所在的位置執行 make run 指令,編譯通過後就會啟動 Bochs。

下一章,我們來了解如何實作硬體跟 OS 之間的互動 :
自制一個 Linux 作業系統(5) — Output (上)

如果你覺得我的文章幫助到你,希望你也可以化讚為賞,加入 Liker ,再按下方的綠色拍手按鈕,為文章點讚!為作者增加收益,再回饋更多好文章!

--

--

YC
YC

Written by YC

提供更精確的技術內容為目標,另創立「程式愛好者」專頁。首席軟體工程師,專研後端技術、物件導向、軟體架構。

No responses yet