Day 3 進入 32 bit 模式並導入 C 語言
製作真正的 IPL
IPL 是 Initial Program Loader,現在要開始做真正的 IPL──把磁碟上的東西 load 到 memory 裡!
這部分書上是一點一點的增加 assembly code,從讀一個 sector 到讀多個 cylinder 的所有 sector。
floppy disk 的 cylinder、sector
floppy disk 的磁片由外而內的一個個同心圓稱為 cylinder(柱面),共有 80 個 cylinder。由圓心往外切(像切蛋糕),cylinder 切成一格格的稱為 sector,一個 cylinder 有 18 個 sector。磁片正反面都可以存放資料,因此磁頭有正反面,以 0 跟 1 表示。
一張磁片共有 2 * 80 * 18
個 sector,每個 sector 是 512 byte,所以一張磁片的容量是 2 * 80 * 18 * 512 = 1474560 byte = 1440 KB
。就是古早時代的 3.5 磁片…現在應該有些人都不知道那是什麼了…
指定某個 sector 需要指定磁頭、cylinder 編號以及 sector 編號。
指令
- JC:jump if carry
- 如果 carry flag 是 1 就 jump
- carry flag 是個只能存一個 bit 的 register,通常用於表示計算有無進位,也會被一些 BIOS function 用來表示回傳值。
- JNC:jump if not carry
- JC 的相反囉
- JAE:jump if above or equal
- 大於等於時 jump
- JBE:jump if below or equal
- JB:jump if below
- EQU
- 相當於 C 的
#define
,可以用來宣告 const CYLS EQU 10
就是CYLS = 10
的意思
- 相當於 C 的
Segment Register
一個 16 bit register 的值的範圍是 0 ~ 65535,只能表示 64 KB 的 memory address。在還沒有 32 bit register 的時代用 segment register 來表示更多 memory address。
使用 segment register 時,以 ES:BX
表示 ES * 16 + BX
的 memory address,在指令裡寫成 MOV AL, [ES:BX]
。實際上指定 memory address 時都需要指定 segment register。省略 segment register 會預設使用 DS
register,所以寫 MOV CX, [1234]
實際上是 MOV CS, [DS:1234]
。
因為 ES:BX
表示 memory address ES * 16 + BX
,如果想將 address 往前移動 512 byte,可以在 ES 加上 512 / 16 = 32 = 0x20
。
IPL 主要 read disk 的部分
1 | CYLS EQU 10 ; 宣告常數 CYLS = 10 |
到這裡,我們就有一個將 floppy disk 資料讀到 memory 的 IPL 了!
最簡單的 OS
IPL 寫好,當然要來寫 OS 啦!
在這個部分,無論是切換 video mode、從 BIOS 取得各種資料、把一些資訊保存在 memory 中等等,都是由組語寫成的 haribote.nas
—— 我們最簡單的 OS 程式,像這樣一個什麼事也沒幹的 OS:
1 | fin: |
從 boot sector 執行 OS
首先透過 image file 了解在 floppy disk 上檔案是怎麼被放置的(放在哪些位置)。image file 可以想成是把 disk 上的 data「原封不動」的一個個 bit 存下來而成的一個檔案。
利用 image file 我們知道在 floppy disk 的一個檔案,檔名會寫在 0x002600
後(這裡的 address 是 disk 上的 address),檔案內容會在 0x004200
後。
所以我們把 OS 內容寫到名為 haribote.sys
的檔案中並存到 floppy disk,它的內容會在 0x004200
之後,所以我們只要從 boot sector 去執行這個位置上的東西就行了!
要怎麼去執行 disk 上 0x004200
後的程式呢?現在的程式是從 boot sector 開始,把 floppy disk 上的東西 load 到 memory address 0x8000
,所以 disk 上 0x4200
的內容會位於 memory address 0x8000 + 0x4200 = 0xc200
。
所以我們在 OS 程式加上 ORG 0xc200
,並且在 IPL 的最後加上 JMP 0xc200
,讓 IPL 完成工作後跳到 OS 程式所在位置開始執行!
切換顯示模式(video mode)
切換 video mode 的 BIOS 可以參考 BIOS 網頁知道要用哪些 register(雖然是日文的)。
VRAM 是 video RAM,用來顯示畫面的 memory。它的每個 address 都對應畫面上的像素,在這塊 memory 裡填值就能在畫面上畫東西~VRAM 在 memory 中有好幾塊,分別給不同 video mode 使用。
進入 32 bit mode 前的準備
32 bit mode 是 CPU 的 mode。不同 mode 有不同 instruction,所以 16 bit 跟 32 bit 的程式是不相容的。register 使用上的方便程度也不同(例如在 16 bit mode 可以方便使用 register AX,但 EAX 不好用)。另外 CPU 的保護功能(識別出可疑的 machine code 並屏蔽它)要在 32 bit mode 才能用。
不過 32 bit mode 不能使用 BIOS 功能,因為 BIOS 是用 16 bit mode 的 instruction 寫的。如果有什麼是想用 BIOS 做,要一開始先做。
後來出了想取代 BIOS 的 UEFI,又是另一回事了…
導入 C 語言
終於開始要用 C 語言啦~在這之前得在 haribote.nas
加入一些組語 code,好讓我們能 call C 程式,不過作者在這裡還不想解釋那段,所以也先略過。
現在 OS 開始有組語跟 C 語言的部分,先將組語部分的 haribote.nas
rename 成 asmhead.has
。再加入一個很簡單的 C 程式叫 bootpack.c
:
1 | // 程式是從這個 function 開始的,function name 不能更改,應該跟處理 C 語言的組語有關 |
如何將它變成可以在我們自製 OS 上執行的 machine code?
- 使用
cc1.exe
從bootpack.c
產生bootpack.gas
cc1.exe
是作者用 gcc 改造而來的 C compiler。gcc 是以 gas 組語為基礎,它 output 的是 gas 的 source code。gas 是 GNU 的 assembler。 - 用
gas2nask.exe
將bootpack.gas
轉成bootpack.nas
作者使用 nask 當 assembler,所以得把 gas 組語轉成 nask 組語。 - 用
nask.exe
將bootpack.nas
組譯為bootpack.obj
- 用
obi2bim.exe
從bootpack.obj
產生bootpack.bim
obi2bim.exe
是 linker,這邊是將 object filebootpack.obj
link 成完整的 machine code。- 雖然現在只有一個 object file,不是多個 object file 要 link 在一起。但即使只有一個 object file 也要幫它加些資訊才能成為完整可執行的 machine code,所以需要 link。
bim
是作者設計的一種格式,是種 binary image file。
- 最後用
bim2hrb.exe
從bootpack.bim
產生bootpack.hrb
這是將bootpack.bim
依照我們自製 OS 的需要做點加工,成為最後的bootpack.hrb
到這裡終於把 C 語言程式編譯成可以在自製 OS 上執行的 machine code,最後用 copy
指令將 asmhead.nas
組譯出來的 asmhead.bin
跟 bootpack.hrb
結合起來,成為最終的 haribote.sys
。(原本 haribote.sys
是直接由 haribote.nas
組譯而來,現在有了 C 語言的部分,要把兩個部分結合起來才是完整的 haribote.sys
)
Makefile
1 | TOOLPATH = ../z_tools/ |
在 C 語言程式 call 組語程式
如果想在 C 語言寫的程式 call 組語的程式該怎麼做呢?寫了個組語程式 naskfunc.nas
如下:
1 | ; naskfunc |
這段組語包含一個 _io_hlt
function。
在 nask object file 模式下,要設定 file name 資訊並且寫清楚程式的 function name。function name 要在前面加上 _
,不然不能跟 C 語言 function link。需要 link 的 function name 都要用 GLOBAL
指令宣告。
來看看在 C 語言裡要怎麼使用 _io_hlt
function:
1 | // 告訴 C compiler,這個 function 在別的 file 裡 |
這邊的 Makefile 跟前面的差異只有增加了 naskfunc.obj
的 target 跟修改 bootpack.bim
target 的 link 內容:
1 | naskfunc.obj : naskfunc.nas Makefile |
到這天結束,我們已經有以下 source code:
ipl10.nas
:IPL 程式asmhead.nas
:OS 的組語部份bootpack.c
:OS 的 C 語言部份naskfunc.nas
:給 C 語言部份使用的組語 function