30天自制操作系統 01-03 從彙編到C語言

01-03-Day-Note

第一天到第三天的筆記

1. 運行環境準備

可以直接在作者提供的開發包中執行作者提供的文件,執行過程如下:

  1. 將對應project中包含源代碼的目錄複製到tolset
  2. 執行對應的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.html

    qemu-system-i386 -fda youros.img
    
  • 代替作者的imgtol

    可以使用Linux的dd命令替換 Windows 下可在此處下載:http://www.chrysocome.net/download

    dd是類似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

計算機加載操作系統的流程如下:

  1. 從特定位置讀取操作系統數據(USB或者軟盤,軟盤已經淘汰了),但這裏使用的是軟盤
  2. 軟盤的第一個512字節的扇區作爲啓動區,執行此啓動區指令
  3. 該啓動區將軟盤內容加載到內存指定位置(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結尾說明是啓動區

該啓動區代碼包含了試錯,循環讀取扇區和柱面

主要注意:

  1. 第41行:MOV AX,0x0820

    這段是把第一個柱面的第二個扇區(第一個爲啓動扇區),加載到內存0x8200的位置,0x13通過段寄存器ES和BX設置,這裏ES爲0x0820需要擴大16倍即爲0x8200

    這裏BIOS將系統啓動代碼(第一個扇區)加載到0x8000處,然後我們的IPL加載之後的扇區,所以將AX賦值爲0x0820然後在賦值給ES

  2. 第82行:JMP 0xc200

    這裏是啓動區代碼執行成功後,跳轉到0xc200處執行代碼

    我們的真正的OS代碼保留在軟盤的0x4200的位置,軟盤的第一個扇區的位置是0x8000所以有0x8000+0x4200 = 0xc200,所以跳轉到此位置

    0x4200是因爲向軟盤寫文件時一幫保存到此位置

  3. 第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即可(使用作者自帶的工具)

有幾個需要注意的地方:

  1. 需要和C語言鏈接的函數都需要標識爲GLOBAL,反義爲LOCAL
  2. 導出的函數需要前加_,這樣才能和C語言鏈接,C語言編譯後的函數會加_

可以看看作者的Makefile可以更好的明白整個編譯的過程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章