01-03-Day-Note
第一天到第三天的筆記
1. 運行環境準備
可以直接在作者提供的開發包中執行作者提供的文件,執行過程如下:
- 將對應
project
中包含源代碼的目錄複製到tolset
中 - 執行對應的
Makefile
指令
需要在Windows的Shell環境下執行,在Git Bash執行會存在命令的不一致問題
作者在nasm
的基礎上開發了名爲nask
的彙編編譯器,然後通過imgtol
製作img鏡像文件,最後通過qemu
虛擬機運行此鏡像文件,作者已經寫好所有樣例代碼以及運行的腳本文件(批處理文件和Makefile)
上面所有的作者工具都在書本配套文件的tolset
中
當然以上基礎編譯工具都可以使用已有工具代替(作者的類似很古老了)
其他工具替代
-
使用
nasm
替代nask
nasm infile.asm -o outfile.img
-
使用最新的
qemu
替代(此處爲qemu4)qemu
基本命令行參數參考 https://www.datarelab.com/blog/Technical_literature/562.htmlqemu-system-i386 -fda youros.img
-
代替作者的
imgtol
可以使用Linux的
dd
命令替換 Windows 下可在此處下載:http://www.chrysocome.net/downloaddd
是類似cp
的一個工具,不過dd
針對的是塊而cp針對的是文件可參考:http://blackblog.tech/2018/07/19/CreateOSDay3/#comments
2. 彙編
i. 寄存器
16 位寄存器
名字 | 功能 |
---|---|
AX | accumulator, 累加寄存器 |
CX | counter, 計數寄存器 |
DX | data, 數據寄存器 |
BX | base, 基址寄存器 |
SP | stack pointer, 棧指針寄存器 |
BP | base pointer, 基址指針寄存器 |
SI | source index, 源變址寄存器 |
DI | destination index, 目的變址寄存器 |
8 位寄存器
名字 | 功能 |
---|---|
AL | 累加寄存器低位(accumulator low) |
CL | 計數寄存器低位(counter low) |
DL | 數據寄存器低位(data low) |
BL | 基址寄存器低位(base low) |
AH | 累加寄存器高位(accumulator high) |
CH | 計數寄存器高位(counter high) |
DH | 數據寄存器高位(data high) |
BH | 基址寄存器高位(base high) |
段寄存器
名字 | 功能 |
---|---|
ES | 附加段寄存器(extra segment) |
CS | 代碼段寄存器(code segment) |
SS | 棧段寄存器(stack segment) |
DS | 數據段寄存器(data segment) |
FS | 沒有名稱(segment part 2) |
GS | 沒有名稱(segment part 3) |
32 位拓展寄存器
EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI (加了個extend拓展的標籤)
ii. 基礎指令
-
DB:按字節定義類似的還有DW(定義字),DD(定義雙字)
-
MOV:移動指令
mov A, B
意爲A = B
,mov
後的寄存器或者字面量加上[]
則代表引用此地址的值如:
MOV AL, BYTE [BX]
,會將指定段寄存器乘16倍將上BX的值形成目標地址BX、 BP、 SI、 DI這幾個。 剩下的AX、 CX、 DX、 SP不能用來指定內存地址
-
JMP:跳轉指令 跳轉到指定內存地址
-
INT:軟件中斷指令 後接中斷號,調用BIOS預設的函數(功能)
-
JE:相等則跳轉(工具FLAGS寄存器的標誌寄存器的值跳轉)
-
CMP:比較兩個寄存器(書面量)的值,修改對應的標誌寄存器
-
HLT:讓CPU進入待機狀態只要外部發生變化, 比如按下鍵盤, 或是移動鼠標, CPU就會醒過來, 繼續執行程序
-
RESB:填充指定數量字節的0x00
-
ORG:將指令加載到指定位置,詳情可見:https://blog.csdn.net/yuduoluogongwu/article/details/7359242
iii. NASM 和 NASK 的區別
nask 和 nasm 部分語法不同,差別如下:
NASK代碼 | NASM代碼 |
---|---|
JMP entry | JMP SHORT entry |
RESB <填充字節數> | TIMES <填充字節數> DB <填充數據> |
RESB 0x7dfe-$ | TIMES 0x1fe-($-$$) DB 0 |
ALIGNB 16 | ALIGN 16, DB 0 |
在文中出現了美元符代表的意思如下:
$ 是當前位置
$$ 是段開始位置
$ - $$ 是當前位置在段內的偏移
3. Makefile
Makefile就像是一個非常聰明的批處理文件
具體操作說明可參考:http://www.ruanyifeng.com/blog/2015/02/make.html
4. IPL
軟盤 FAT12
作者使用的是格式爲FAT12
格式的軟盤
用Windows或MS-DOS格式化出來的軟盤就是這種格式。 作者的helloos也採用了這種格式, 其中容納了作者開發的操作系統。 這個格式兼容性好, 在Windows上也能用, 而且剩餘的磁盤空間還可以用來保存自己喜歡的文件。
1張軟盤有80個柱面, 2個磁頭, 18個扇區, 且一個扇區有512字節。 所以, 一張軟盤的容量是:
80×2×18×512 = 1474560 Byte = 1440KB
啓動區
(boot sector) 軟盤第一個的扇區稱爲啓動區。 那麼什麼是扇區呢? 計算機讀寫軟盤的時候, 並不是一個字節一個字節地讀寫的, 而是以512字節爲一個單位進行讀寫。 因此,軟盤的512字節就稱爲一個扇區。 一張軟盤的空間共有1440KB, 也就是1474560字節, 除以512得2880, 這也就是說一張軟盤共有2880個扇區。 那爲什麼第一個扇區稱爲啓動區呢? 那是因爲計算機首先從最初一個扇區開始讀軟盤, 然後去檢查這個扇區最後2個字節的內容。如果這最後2個字節不是0x55 AA, 計算機會認爲這張盤上沒有所需的啓動程序, 就會報一個不能啓動的錯誤。 (也許有人會問爲什麼一定是0x55AA呢? 那是當初的設計者隨便定的, 筆者也沒法解釋) 。 如果計算機確認了第一個扇區的最後兩個字節正好是0x55 AA, 那它就認爲這個扇區的開頭是啓動程序, 並開始執行這個程序。
IPL 啓動程序裝載器
initial program loader的縮寫。 啓動程序加載器。 啓動區只有區區512字節, 實際的操作系統不像hello-os這麼小, 根本裝不進去。 所以幾乎所有的操作系統, 都是把加載操作系統本身的程序放在啓動區裏的。 有鑑於此, 有時也將啓動區稱爲IPL。 但hello-os沒有加載程序的功能, 所以HELLOIPL這個名字不太順理成章。 如果有人正義感特別強, 覺得“這是撒謊造假, 萬萬不能容忍! ”, 那也可以改成其他的名字。 但是必須起一個8字節的名字, 如果名字長度不到8字節的話, 需要在最後補上空格
製作 IPL
計算機加載操作系統的流程如下:
- 從特定位置讀取操作系統數據(USB或者軟盤,軟盤已經淘汰了),但這裏使用的是軟盤
- 軟盤的第一個512字節的扇區作爲啓動區,執行此啓動區指令
- 該啓動區將軟盤內容加載到內存指定位置(0x7c00)運行,根據最後兩字節判斷是否是啓動區
文中的IPL加載了軟盤的10個柱面
文中的IPL如下:
; haribote-ipl
; TAB=4
CYLS EQU 10 ; 聲明CYLS=10
ORG 0x7c00 ; 指明程序裝載地址
; 標準FAT12格式軟盤專用的代碼 Stand FAT12 format floppy code
JMP entry
DB 0x90
DB "HARIBOTE" ; 啓動扇區名稱(8字節)
DW 512 ; 每個扇區(sector)大小(必須512字節)
DB 1 ; 簇(cluster)大小(必須爲1個扇區)
DW 1 ; FAT起始位置(一般爲第一個扇區)
DB 2 ; FAT個數(必須爲2)
DW 224 ; 根目錄大小(一般爲224項)
DW 2880 ; 該磁盤大小(必須爲2880扇區1440*1024/512)
DB 0xf0 ; 磁盤類型(必須爲0xf0)
DW 9 ; FAT的長度(必??9扇區)
DW 18 ; 一個磁道(track)有幾個扇區(必須爲18)
DW 2 ; 磁頭數(必??2)
DD 0 ; 不使用分區,必須是0
DD 2880 ; 重寫一次磁盤大小
DB 0,0,0x29 ; 意義不明(固定)
DD 0xffffffff ; (可能是)卷標號碼
DB "HARIBOTEOS " ; 磁盤的名稱(必須爲11字?,不足填空格)
DB "FAT12 " ; 磁盤格式名稱(必??8字?,不足填空格)
RESB 18 ; 先空出18字節
; 程序主體
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
; 讀取磁盤
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁頭0
MOV CL,2 ; 扇區2
readloop:
MOV SI,0 ; 記錄失敗次數寄存器
retry:
MOV AH,0x02 ; AH=0x02 : 讀入磁盤
MOV AL,1 ; 1個扇區
MOV BX,0
MOV DL,0x00 ; A驅動器
INT 0x13 ; 調用磁盤BIOS
JNC next ; 沒出錯則跳轉到fin
ADD SI,1 ; 往SI加1
CMP SI,5 ; 比較SI與5
JAE error ; SI >= 5 跳轉到error
MOV AH,0x00
MOV DL,0x00 ; A驅動器
INT 0x13 ; 重置驅動器
JMP retry
next:
MOV AX,ES ; 把內存地址後移0x200(512/16十六進制轉換)
ADD AX,0x0020
MOV ES,AX ; ADD ES,0x020因爲沒有ADD ES,只能通過AX進行
ADD CL,1 ; 往CL裏面加1
CMP CL,18 ; 比較CL與18
JBE readloop ; CL <= 18 跳轉到readloop
MOV CL,1
ADD DH,1
CMP DH,2
JB readloop ; DH < 2 跳轉到readloop
MOV DH,0
ADD CH,1
CMP CH,CYLS
JB readloop ; CH < CYLS 跳轉到readloop
; 讀取完畢,跳轉到haribote.sys執行!
MOV [0x0ff0],CH ; IPLがどこまで読んだのかをメモ
JMP 0xc200
error:
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1 ; 給SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 顯示一個文字
MOV BX,15 ; 指定字符顏色
INT 0x10 ; 調用顯卡BIOS
JMP putloop
fin:
HLT ; 讓CPU停止,等待指令
JMP fin ; 無限循環
msg:
DB 0x0a, 0x0a ; 換行兩次
DB "load error"
DB 0x0a ; 換行
DB 0
RESB 0x7dfe-$ ; 填寫0x00直到0x001fe
DB 0x55, 0xaa
最後以0x55aa結尾說明是啓動區
該啓動區代碼包含了試錯,循環讀取扇區和柱面
主要注意:
-
第41行:
MOV AX,0x0820
這段是把第一個柱面的第二個扇區(第一個爲啓動扇區),加載到內存
0x8200
的位置,0x13通過段寄存器ES和BX設置,這裏ES爲0x0820
需要擴大16倍即爲0x8200
這裏BIOS將系統啓動代碼(第一個扇區)加載到
0x8000
處,然後我們的IPL加載之後的扇區,所以將AX賦值爲0x0820
然後在賦值給ES -
第82行:
JMP 0xc200
這裏是啓動區代碼執行成功後,跳轉到
0xc200
處執行代碼我們的真正的OS代碼保留在軟盤的
0x4200
的位置,軟盤的第一個扇區的位置是0x8000
所以有0x8000+0x4200 = 0xc200
,所以跳轉到此位置0x4200
是因爲向軟盤寫文件時一幫保存到此位置 -
第107行:
RESB 0x7dfe-$ ; 填寫0x00直到0x001fe
只是將啓動區後續部分填充爲0
0x7dfe = 0x7c00 + 511
得到,表示512字節的啓動區
5. 導入C語言
文章中將C語言代碼bootpack.c
編譯爲32位彙編,要使用C語言,在操作系統中必然是C語言和彙編是混合複用的,所以需要專門的代碼進行鏈接,文章中給出的是asmhead.nas
,這裏進行了對顯卡顯示模式的設置,以及對C語言的導入操作,可以到此文件中看一看,作者給出了很清楚的註釋。(中文代碼:https://github.com/yourtion/30dayMakeOS/blob/master/03_day/)
對C語言的處理作者分爲以下幾步:
- 使用cc1.exe從bootpack.c生成bootpack.gas
- 使用gas2nask.exe從bootpack.gas生成bootpack.nas
- 使用nask.exe從bootpack.nas生成bootpack.obj
- 使用obj2bim.exe從bootpack.obj生成bootpack.bim
- 使用bim2hrb.exe從bootpack.bim生成bootpack.hrb
- 這樣就做成了機器語言, 再使用copy指令將asmhead.bin與bootpack.hrb單純結合到起來, 就成了haribote.sys
cc1
是C編譯器, 將C語言代碼編譯爲32位的GAS的彙編代碼
gas2nask
是將gas彙編編譯爲nasm識別的彙編格式了,通過nask(nasm)編譯位OBJ目標文件
obj2bim
將目標文件編譯爲二進制鏡像文件,方便不同的目標文件進行合併
bim2hrb
將最後的合併目標文件編譯爲hrb
文件(這個是適合作者的這個編譯環境的最終二進制文件)
C語言調用匯編
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ; 製作目標文件的模式
[BITS 32] ; 製作32位模式用的機器語言
; 製作目標文件的信息
[FILE "naskfunc.nas"] ; 源文件名信息
GLOBAL _io_hlt ; 程序中包含的函數名
; 以下是實際的函數
[SECTION .text] ; 目標文件中寫了這些後再寫程序
_io_hlt: ; void io_hlt(void);
HLT
RET
將此文件的obj
文件和C語言的obj
文件一起編譯爲bim
即可(使用作者自帶的工具)
有幾個需要注意的地方:
- 需要和C語言鏈接的函數都需要標識爲
GLOBAL
,反義爲LOCAL
- 導出的函數需要前加
_
,這樣才能和C語言鏈接,C語言編譯後的函數會加_
可以看看作者的Makefile
可以更好的明白整個編譯的過程