xUnit framework 這類型 framework 執行測試大致的作法:找到 test class 裡所有 test method(依據語言不同有不同作法,有些語言可以用 reflection),為每個 test method 產生一個單獨的 object,該 object 的任務是去執行那個 test method。利用不同 object 隔離 test case,讓 test case 不會互相影響。
例如某段程式 call 了一個 function 來計算商品價格,現在想改變計算價格的 strategy。想在不改變 call 計價 function 的情況下,改變計價結果(程式行為)。
又例如程式 call 到牽連龐大子系統的 function,我們希望在 test 中避免執行到那些複雜的 code(不然很難測或無法測),又要在 prodcution code 裡照常執行到。如果這段 code 有 seam,便能在不改動到原本 call function 的情況下,換掉該 function 的行為來避免在 test 中碰到子系統。
每個 seam 都有一個 enabling point,在這裡你可以決定使用哪種行為。
seam 是可以讓你改變程式行為的「縫隙」,enabling point 則是決定那個 seam 要是什麼行為。在 enabling point 給不同的值,可以讓 seam 有不同的行為。例如物件 seam,物件 method 的參數列表是 enabling point,因為我們可以傳入不同的物件來改變程式行為。
; read 8 bit _io_in8: ; int io_in8(int port); MOV EDX,[ESP+4] ; port (port number 只有 16 bit) MOV EAX,0 ; 把 return value 清成 0 IN AL,DX ; read 8 bit (AL) RET ; EAX 的值是 return value
_io_in16: ; int io_in16(int port); MOV EDX,[ESP+4] ; port MOV EAX,0 IN AX,DX ; read 16 bit (AX) RET
_io_in32: ; int io_in32(int port); MOV EDX,[ESP+4] ; port IN EAX,DX ; read 32 bit (EAX) RET
; write 8 bit _io_out8: ; void io_out8(int port, int data); MOV EDX,[ESP+4] ; port MOV AL,[ESP+8] ; data OUT DX,AL RET
_io_out16: ; void io_out16(int port, int data); MOV EDX,[ESP+4] ; port MOV EAX,[ESP+8] ; data OUT DX,AX RET
_io_out32: ; void io_out32(int port, int data); MOV EDX,[ESP+4] ; port MOV EAX,[ESP+8] ; data OUT DX,EAX RET
我們要設定調色板,就是找出對應 device 設定調色板的指令,然後照著做~
CLI 與 STI
CLI 是將 interrupt flag clear 為 0 的指令, STI 則是將 interrupt flag set 為 1 的指令。
interrupt flag 為 0 時,CPU 遇到 interrupt 會忽略它、不處理,flag 為 1 時 CPU 就會處理 interrupt。
EFLAGS register
EFLAGS 是由 FLAGS 16 bit 的 register 擴展而來的 32 bit register。
FLAGS 儲存 carry flag(進位 flag)與 interrupt flag 等 flag,不同 bit 代表不同 flag(有 1 個 bit 表示一個 flag 也有 2 個 bit),如下所示:
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
NT
IOPL
IOPL
OF
DF
IF
TF
SF
ZF
AF
PF
CF
IOPL 是第 12 跟 13 bit 放在一起處理。
EFLAGS 沒有 MOV 指令,只能使用 PUSHFD 跟 POPFD 來讀寫。
PUSHFD 是 push flags double-word 的縮寫,以 double word 的長度(32 bit)將 flag 的值 push 進 stack,等同 PUSH EFLAGS。POPFD 是 pop flags double-word,將 double word 長度的值從 stack pop 出來到 flag,等同 POP EFLAGS。
要怎麼去執行 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 語言的組語有關 voidHariMain(void) { fin: goto fin; }
如何將它變成可以在我們自製 OS 上執行的 machine code?
使用 cc1.exe 從 bootpack.c 產生 bootpack.gas cc1.exe 是作者用 gcc 改造而來的 C compiler。gcc 是以 gas 組語為基礎,它 output 的是 gas 的 source code。gas 是 GNU 的 assembler。
_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 裡 voidio_hlt(void);
BIOS 是讓 OS 開發人員可以使用的各種 function 的集合。可以用指令 INT call 這些 function,它後面是個數字,不同數字代表不同 function,0x10 的功能是控制顯卡。
使用 BIOS 的 function 跟其他 level 的程式 call function 一樣,查要的功能是用哪個數字(像 function name),接著照 function 定義在各 register 放值(像 function 參數),register 設好後用 INT call function。