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 編號。

floppy disk

指令

  • 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 的意思

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
CYLS	EQU		10				; 宣告常數 CYLS = 10

; (中略)

; read disk
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; cylinder 0
MOV DH,0 ; 磁頭 0
; sector 2, 因為 floppy disk 第一個 sector 是 boot sector, 所以從第二個 sector 開始 read
MOV CL,2
readloop:
MOV SI,0 ; 紀錄失敗次數的 register
retry:
MOV AH,0x02 ; AH=0x02 : read floppy disk
MOV AL,1 ; 1 sector
MOV BX,0
MOV DL,0x00 ; A 驅動器
INT 0x13 ; call BIOS
JNC next ; 沒出錯就跳到 next
ADD SI,1 ; SI + 1 (這段開始在做 retry)
CMP SI,5 ; SI 跟 5 比較
JAE error ; SI >= 5 就跳到 error
MOV AH,0x00
MOV DL,0x00 ; A 驅動器
INT 0x13 ; 重置驅動器
JMP retry ; 回到 retry 再嘗試讀一次
next:
MOV AX,ES ; 將 memory address 往後移 0x200 (512)
ADD AX,0x0020
MOV ES,AX ; 因為沒有 ADD ES,0x020 的指令所以用比較迂迴的方式做
ADD CL,1 ; CL + 1 (sector)
CMP CL,18 ; CL 跟 18 比較
JBE readloop ; CL <= 18 則跳到 readloop (這邊就是 read 到 sector 18 的 loop)
MOV CL,1 ; CL = 1
ADD DH,1 ; DH + 1
CMP DH,2
JB readloop ; DH < 2 則跳到 readloop (這邊是 read 另一個磁頭的 loop)
MOV DH,0 ; 回到磁頭 0
ADD CH,1 ; CH + 1 (cylinder + 1)
CMP CH,CYLS
JB readloop ; CH < CYLS 則跳到 readloop (這邊是 read cylinder 的 loop, CYLS 是常數)

到這裡,我們就有一個將 floppy disk 資料讀到 memory 的 IPL 了!

最簡單的 OS

IPL 寫好,當然要來寫 OS 啦!

在這個部分,無論是切換 video mode、從 BIOS 取得各種資料、把一些資訊保存在 memory 中等等,都是由組語寫成的 haribote.nas —— 我們最簡單的 OS 程式,像這樣一個什麼事也沒幹的 OS:

1
2
3
fin:
HLT
JMP 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
2
3
4
5
6
// 程式是從這個 function 開始的,function name 不能更改,應該跟處理 C 語言的組語有關
void HariMain(void)
{
fin:
goto fin;
}

如何將它變成可以在我們自製 OS 上執行的 machine code?

  1. 使用 cc1.exebootpack.c 產生 bootpack.gas
    cc1.exe 是作者用 gcc 改造而來的 C compiler。gcc 是以 gas 組語為基礎,它 output 的是 gas 的 source code。gas 是 GNU 的 assembler。
  2. gas2nask.exebootpack.gas 轉成 bootpack.nas
    作者使用 nask 當 assembler,所以得把 gas 組語轉成 nask 組語。
  3. nask.exebootpack.nas 組譯為 bootpack.obj
  4. obi2bim.exebootpack.obj 產生 bootpack.bim
    • obi2bim.exe 是 linker,這邊是將 object file bootpack.obj link 成完整的 machine code。
    • 雖然現在只有一個 object file,不是多個 object file 要 link 在一起。但即使只有一個 object file 也要幫它加些資訊才能成為完整可執行的 machine code,所以需要 link。
    • bim 是作者設計的一種格式,是種 binary image file。
  5. 最後用 bim2hrb.exebootpack.bim 產生 bootpack.hrb
    這是將 bootpack.bim 依照我們自製 OS 的需要做點加工,成為最後的 bootpack.hrb

到這裡終於把 C 語言程式編譯成可以在自製 OS 上執行的 machine code,最後用 copy 指令將 asmhead.nas 組譯出來的 asmhead.binbootpack.hrb 結合起來,成為最終的 haribote.sys 。(原本 haribote.sys 是直接由 haribote.nas 組譯而來,現在有了 C 語言的部分,要把兩個部分結合起來才是完整的 haribote.sys

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
TOOLPATH = ../z_tools/
INCPATH = ../z_tools/haribote/

MAKE = $(TOOLPATH)make.exe -r
NASK = $(TOOLPATH)nask.exe
CC1 = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet
GAS2NASK = $(TOOLPATH)gas2nask.exe -a
OBJ2BIM = $(TOOLPATH)obj2bim.exe
BIM2HRB = $(TOOLPATH)bim2hrb.exe
RULEFILE = $(TOOLPATH)haribote/haribote.rul
EDIMG = $(TOOLPATH)edimg.exe
IMGTOL = $(TOOLPATH)imgtol.com
COPY = copy
DEL = del

default :
$(MAKE) img

; 組譯 IPL
ipl10.bin : ipl10.nas Makefile
$(NASK) ipl10.nas ipl10.bin ipl10.lst

; 組譯 OS 前面的組語部分
asmhead.bin : asmhead.nas Makefile
$(NASK) asmhead.nas asmhead.bin asmhead.lst

; 使用 cc1 compile bootpack.c 得到 gas 組語
bootpack.gas : bootpack.c Makefile
$(CC1) -o bootpack.gas bootpack.c

; 將 gas 組語轉成 nask 組語
bootpack.nas : bootpack.gas Makefile
$(GAS2NASK) bootpack.gas bootpack.nas

; nask 組譯
bootpack.obj : bootpack.nas Makefile
$(NASK) bootpack.nas bootpack.obj bootpack.lst

; linking
bootpack.bim : bootpack.obj Makefile
$(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
bootpack.obj
# 3MB+64KB=3136KB

; 幫 bootpack.bim 做點加工好能在我們的 OS 上執行
bootpack.hrb : bootpack.bim Makefile
$(BIM2HRB) bootpack.bim bootpack.hrb 0

; 將兩個部份的 machine code 結合在一起
haribote.sys : asmhead.bin bootpack.hrb Makefile
copy /B asmhead.bin+bootpack.hrb haribote.sys

; 將 IPL 跟 OS 的 machine code 做成 floopy disk 的 image file
haribote.img : ipl10.bin haribote.sys Makefile
$(EDIMG) imgin:../z_tools/fdimg0at.tek \
wbinimg src:ipl10.bin len:512 from:0 to:0 \
copy from:haribote.sys to:@: \
imgout:haribote.img

img :
$(MAKE) haribote.img

run :
$(MAKE) img
$(COPY) haribote.img ..\z_tools\qemu\fdimage0.bin
$(MAKE) -C ../z_tools/qemu

install :
$(MAKE) img
$(IMGTOL) w a: haribote.img

clean :
-$(DEL) *.bin
-$(DEL) *.lst
-$(DEL) *.gas
-$(DEL) *.obj
-$(DEL) bootpack.nas
-$(DEL) bootpack.map
-$(DEL) bootpack.bim
-$(DEL) bootpack.hrb
-$(DEL) haribote.sys

src_only :
$(MAKE) clean
-$(DEL) haribote.img

在 C 語言程式 call 組語程式

如果想在 C 語言寫的程式 call 組語的程式該怎麼做呢?寫了個組語程式 naskfunc.nas 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
; naskfunc
; TAB=4

[FORMAT "WCOFF"] ; 製作 object file 的模式
[BITS 32] ; 製作 32 bit mode 用的 machine code

; 製作 object file 的資訊

[FILE "naskfunc.nas"] ; source code file name

GLOBAL _io_hlt ; 程式中包含的 function name

; 以下是實際 function

[SECTION .text]

_io_hlt: ; void io_hlt(void);
HLT
RET ; 相當於 C 語言的 return

這段組語包含一個 _io_hlt function。

在 nask object file 模式下,要設定 file name 資訊並且寫清楚程式的 function name。function name 要在前面加上 _ ,不然不能跟 C 語言 function link。需要 link 的 function name 都要用 GLOBAL 指令宣告。

來看看在 C 語言裡要怎麼使用 _io_hlt function:

1
2
3
4
5
6
7
8
9
// 告訴 C compiler,這個 function 在別的 file 裡
void io_hlt(void);

void HariMain(void)
{
fin:
io_hlt();
goto fin;
}

這邊的 Makefile 跟前面的差異只有增加了 naskfunc.obj 的 target 跟修改 bootpack.bim target 的 link 內容:

1
2
3
4
5
6
7
naskfunc.obj : naskfunc.nas Makefile
$(NASK) naskfunc.nas naskfunc.obj naskfunc.lst

# 多 link naskfunc.obj
bootpack.bim : bootpack.obj naskfunc.obj Makefile
$(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
bootpack.obj naskfunc.obj

到這天結束,我們已經有以下 source code:

  • ipl10.nas:IPL 程式
  • asmhead.nas:OS 的組語部份
  • bootpack.c:OS 的 C 語言部份
  • naskfunc.nas:給 C 語言部份使用的組語 function