這一系列是我讀 The little book about OS development 的一些實作紀錄,此篇為介紹一些基礎概念,以及利用這些簡化的流程,實作一個非常簡單的奈米級作業系統。
我選擇的作業系統是 Ubuntu 22.04 LTS(之後改用 16.04,後續會說明原因)。
準備好後需要先下載一些 package:sudo apt-get install build-essential nasm genisoimage bochs bochs-sdl
針對上方指令安裝的東西稍稍解釋。
nasm
,NASM 全稱是 Netwide Assembler,是一款組譯與反組譯工具,算是 Linux 平台上最受歡迎的組譯工具之一。另外這本書主要使用 C 語言作為開發語言,為的是能更直接控制記憶體。
genisoimage
,是 generate ISO image 的縮寫,如其名就是建立 ISO 映像檔案。
bochs
,Bochs,發音同 box,開放原始碼的 x86、x86-64IBM PC 相容機模擬器以及除錯工具,主要用於作業系統開發,也可以用來執行不相容的舊軟體。
Booting 基本概念
Booting 又稱啟動程式,算是電腦中的一個初始化過程,當我們啟動一個作業系統,可以看作是一個一個小程式的串聯,而作業系統就是最後一個程式。所以簡單來看,可以將 boot 簡化為:
BIOS → GRUB1 → GRUB2 → OS
BIOS
BIOS(Basic Input Output System),也稱作基本輸入輸出系統,通常儲存在主機板上的唯讀晶片,主要作用是在啟動時載入的第一個軟體。人如其名,控制著電腦的基本輸出及輸入裝置,在開機時對各系統測試和初始化裝置,會對電腦的設備進行檢驗和測試,若有問題可能會發出提示音或在螢幕中提示。
我們的系統軟體大部分會放在硬碟,我們必須要有一個開機管理程式來處理核心載入的問題,這個程式就是 boot loader。
GRUB
GRUB 是 GRand Unified Bootloader 的縮寫,是 Linux 中最常見的 Bootloader。老實說我一直不理解 boot 和 loader 這兩個字到底要分開還是黏一起。
Bootloader
Loader 最主要的功能是認識作業系統的檔案格式,並且依此載入核心到主記憶體中去執行。在不同的作業系統中,必須使用自己的 loader 才能夠載入屬於自己的作業系統核心,這是因為每種作業系統的格式都不一樣,因此每個都有自己的 bootloader。
Bootloader 這個程式會放在 MBR,Master Boot Record,也就是主要開機紀錄區(開機裝置的第一個 sector 內)。使用 GRUB,可以將操作系統建置為 ELF 可執行檔,再來由 GRUB 將它載入至正確的記憶體位置,kernel 的編譯會使用特定的方式將程式碼發佈到記憶體中。
Operating System
再來就是我們的作業系統啦。GRUB 會尋找一個 magic number 以確保有正確跳轉到作業系統,之後作業系統就會完全接管電腦。
迷你的類作業系統
講述完基本概念後,接下來要嘗試用組合語言寫一個非常迷你的作業系統(有些人甚至覺得不叫作業系統)。這是我有生以來第一次寫組合語言,果然是另外一個世界XD
主要目的很簡單,要將數字 0xCAFEBABE
寫入 eax
暫存器中(最後可以看 log 驗證有沒有成功)。
loader
global loader MAGIC_NUMBER equ 0x1BADB002 FLAGS equ 0x0 CHECKSUM equ -MAGIC_NUMBER section .text: align 4 dd MAGIC_NUMBER dd FLAGS dd CHECKSUM loader: mov eax, 0xCAFEBABE loop: jmp .loop
畢竟是第一次寫,來個不負責任解說,說錯了可以在下方留言指正:
第一行的 global loader
有一點像是我們宣告
再來是 MAGIC_NUMBER
、FLAGS
以及 CHECKSUM
。我才剛聽完有關網路的課程,說了各種封包的 header,對於這部分感到很熟悉XD MAGIC_NUMBER
會被用來偵測特定格式的資料。這次 Boot loader 不需要做任何事,也不需要要求任何東西,所以不需要設定,也就是 0x0
。如果是非 ELF 另外的格式,就必須要加上 loading address。而 CHECKSUM
是校驗碼,一個 32-bit unsigned 的值,是 Multiboot header 的必填項目,會被用來測試資料的完整性。
MAGIC_NUMBER
、FLAGS
三者加起來必須為 0。
equ
大概就是 equal?總而言之可以指派常數,且不能改變。
section .bss
用於宣告變數、section .data
用於宣告初始化資料,另外 section .text
用於保存程式碼,以上三者算是組合語言的三個部分。
align 4
主要功用是 4 byte 對齊編譯,對齊的概念對於系統來說很重要,也能夠提升執行效率。
dd
是一個偽指令,定義操作數占用的字節數。結合上面的 align 4
,就可以對齊好資料,寫進 section .text
。
在 loader
區塊中,可以看到我們這次程式的主要目的:將 0xCAFEBABE
寫到 eax
中。Loader 給 linker script 作為入口,接著執行程式。
接下來是 jmp
,進入某段程式。.loop
則是會循環,不會結束。
再來是將 loader.s
編譯為 32 位元 elf 檔的指令:
nasm -f elf32 loader.s
指令下完後,同一個路徑中就會出現檔案 loaders.o
。
linker
另外我們要把寫好的 loader.s
放到剛才提到的 linker script 中。在此新增一個檔案叫做 link.ld
:
ENTRY(loader) SECTIONS { . = 0x00100000; .text ALIGN (0x1000) : { *(.text) } .rodata ALIGN (0x1000) : { *(.rodata*) } .data ALIGN (0x1000) : { *(.data) } .bss ALIGN (0x1000) : { *(COMMON) *(.bss) } }
Linker script 用於規定如何把輸入檔案內的 section 放到輸出檔案內,並且控制輸出檔案內記憶體位置。而 linker 是把一到多個輸入檔案合成一個輸出檔案。
ld -T link.ld -melf_i386 loader.o -o kernel.elf
操作後將產生 kernel.elf
檔案。
GRUB Config、產生映像檔
接下來建立一個 GRUB 的設定檔 menu.lst
,像是 kernel 的位置等等。
default=0 timeout=0 title os kernel /boot/kernel.elf
輸入以下指令:mkdir -p iso/boot/grub
cp stage2_eltorito iso/boot/grub
cp kernel.elf iso/boot/
cp menu.lst iso/boot/grub
如果用圖形介面搞不好自己移一移比較快。第一列指令是建立資料夾、第二、三、四個指令是將 stage2_eltorito
、kernel.elf
、menu.lst
三個檔案分別放進指定路徑中。
最後回到 iso
資料夾的上一層,並且輸入以下指令:
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
這麼長的指令主要是用來生成 os.iso
檔,也就是所謂的映像檔。
Bochs Config
在使用 Bochs 之前,我們也要先設定關於 Bochs 的設定檔:
megs: 32 display_library: sdl romimage: file=/usr/share/bochs/BIOS-bochs-latest vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latest ata0-master: type=cdrom, path=os.iso, status=inserted boot: cdrom log: bochslog.txt clock: sync=realtime, time0=local cpu: count=1, ips=1000000
最後利用 Bochs 執行映像檔 os.iso
。
bochs -f bochsrc.txt -q
若出現以下錯誤訊息:
Bochs is exiting with the following message:
[ ] bochsrc.txt:2: display library 'sdl' not available
可以試著將 bochsrc.txt
設定檔中的 display_library
後方內容改成 sdl2
或是 x
。不太確定是不是因為作業系統版本所造成的差異,我在 Ubuntu 16.04 沒有遇到,一切順利,但是在 22.04 出現錯誤,最後甚至無法順利執行,之前在 GitHub 上也有不少人發 Issue 並提到版本問題。
另外有一個問題是 Bochs 執行時,出現視窗顯示 "bochs-bin" is not responding
。
目前這題…… 我這邊無解,因為剛好我手邊有版本較舊的 Ubuntu,又是實體機器,所以虛擬機的 22.04 版遇到的問題就乾脆先放著不理會。
若一切順利,或是錯誤皆避開後,可以接續以下步驟。我們這部分主要目的是將數字 0xCAFEBABE
寫入 eax
暫存器中,所以接下來啟動後應該可以在紀錄檔中驗證這件事情。
首先應該會看到 Bochs 視窗被開啟但是沒有反應,我們必須回到終端機輸入 c
並按 Enter 後,讓 Bochs 繼續執行。沒錯,c
代表的是 continue,所以要搞威一點輸入 continue
也行。
應該可以看到 Bochs 有開始動作的樣子,看到之後可以按右上角的電源鍵將視窗關閉。
接著我們查看剛產生的紀錄檔 bochslog.txt
,或是在命令列輸入:
cat bochslog.txt | grep EAX
搜尋 EAX
是否已改成 0xCAFEBABE
。
若看到表示成功執行!也就完成了一個非常微小的作業系統了。