操作系統製作(7)v0.01連接彙編代碼和C語言main函數

環境:
virtual-box:版本 6.0.10 r132072 (Qt5.6.2)運行的的ubuntu18.04系統。
nasm彙編器:NASM version 2.13.02

執行效果如下:
在這裏插入圖片描述

bootsect.s代碼如下:

; boot.s 程序
; 首先利用 BIOS 中斷把內核代碼( head 代碼)加載到內存 0x10000 處,然後移動到內存 0 處。
; 最後進入保護模式,並跳轉到內存 0( head 代碼)開始處繼續運行。
BOOTSEG equ 0x07c0		; 引導扇區(本程序)被 BIOS 加載到內存 0x7c00 處。
SYSSEG equ 0x1000		; 內核( head)先加載到 0x10000 處,然後移動到 0x0 處。
SYSLEN equ 17			; 內核佔用的最大磁盤扇區數。
start:
        jmp BOOTSEG:go		; 段間跳轉至 0x7c0:go 處。當本程序剛運行時所有段寄存器值

go:
        mov ax,cs		; 均爲 0。該跳轉語句會把 CS 寄存器加載爲 0x7c0(原爲 0)。
        mov ds,ax		; 讓 DS 和 SS 都指向 0x7c0 段。
        mov ss,ax
        mov sp,0x400		; 設置臨時棧指針。其值需大於程序末端並有一定空間即可。

; 加載內核代碼到內存 0x10000 開始處。
load_system:
        mov dx,0x0000		; 利用 BIOS 中斷 int 0x13 功能 2 從啓動盤讀取 head 代碼。
        mov cx,0x0002		; DH - 磁頭號; DL - 驅動器號; CH - 10 位磁道號低 8 位;
        mov ax,SYSSEG		; CL - 位 7、 6 是磁道號高 2 位,位 5-0 起始扇區號(從 1 計)。
        mov es,ax		; ES:BX - 讀入緩衝區位置( 0x1000:0x0000) 。
        xor bx,bx		; AH - 讀扇區功能號; AL - 需讀的扇區數( 17)。
        mov ax,0x200+SYSLEN
        int 0x13
        jnc ok_load		; 若沒有發生錯誤則跳轉繼續運行,否則死循環。
die:
        jmp die 

; 把內核代碼移動到內存 0 開始處。共移動 8KB 字節(內核長度不超過 8KB)。
ok_load:
        cli ; 關中斷。
        mov ax, SYSSEG		; 移動開始位置 DS:SI = 0x1000:0;目的位置 ES:DI=0:0。
        mov ds, ax
        xor ax, ax
        mov es, ax
        mov cx, 0x1000		; 設置共移動 4K 次,每次移動一個字(dw) 。
        sub si,si
        sub di,di
        rep movsw		; 執行重複移動指令。
        ; 加載 IDT 和 GDT 基地址寄存器 IDTR 和 GDTR。
        mov ax, BOOTSEG
        mov ds, ax		; 讓 DS 重新指向 0x7c0 段。
        lidt [idt_48]		; 加載 IDTR。 6 字節操作數: 2 字節表長度, 4 字節線性基地址。
        lgdt [gdt_48]		; 加載 GDTR。 6 字節操作數: 2 字節表長度, 4 字節線性基地址。

        ; 設置控制寄存器 CR0(即機器狀態字),進入保護模式。段選擇符值 8 對應 GDT 表中第 2 個段描述符。
        mov ax,0x0001		; 在 CR0 中設置保護模式標誌 PE(位 0)。
        lmsw ax			; 然後跳轉至段選擇符值指定的段中,偏移 0 處。
        jmp 8:0			; 注意此時段值已是段選擇符。該段的線性基地址是 0。

; 下面是全局描述符表 GDT 的內容。其中包含 3 個段描述符。第 1 個不用,另 2 個是代碼和數據段描述符。
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |_____________________word3_____________________|__________________word2______________|      
;low   |_____________________word1_____________________|__________________word0______________|
;
;---------------------------------------------------------------------------------------------
;
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |____Base_addr(31~24)___|___________|limit(19~16|___________|___TYPE__|__addr(23~16)__|      
;low   |_______________Base_addr(15~0)_________________|____________Seg_limit(15~0)__________|

gdt:
        dw 0,0,0,0		; 段描述符 0,不用。每個描述符項佔 8 字節。

        dw 0x07FF		; 段描述符 1。 8Mb - 段限長值=2047 (2048*4096=8MB)。
        dw 0x0000		; 段基地址=0x00000。
        dw 0x9A00		; 是代碼段,可讀/執行。
        dw 0x00C0		; 段屬性顆粒度=4KB, 80386。

        dw 0x07FF		; 段描述符 2。 8Mb - 段限長值=2047 (2048*4096=8MB)。
        dw 0x0000		; 段基地址=0x00000。
        dw 0x9200		; 是數據段,可讀寫。
        dw 0x00C0		; 段屬性顆粒度=4KB, 80386。

; 下面分別是 LIDT 和 LGDT 指令的 6 字節操作數。
idt_48:
        dw 0			; IDT 表長度是 0。
        dw 0,0			; IDT 表的線性基地址也是 0。
gdt_48:
        dw 0x7ff		; GDT 表長度是 2048 字節,可容納 256 個描述符項。
        dw 0x7c00+gdt,0		; GDT 表的線性基地址在 0x7c0 段的偏移 gdt 處。

end:
        times 510-($-$$) db 0
        dw 0xAA55		; 引導扇區有效標誌。必須處於引導扇區最後 2 字節處。 

head.s代碼如下:

; head.s 包含 32 位保護模式初始化設置代碼、時鐘中斷代碼、系統調用中斷代碼和兩個任務的代碼。
; 在初始化完成之後程序移動到任務 0 開始執行,並在時鐘中斷控制下進行任務 0 和 1 之間的切換操作。
LATCH equ 11930		; 定時器初始計數值,即每隔 10 毫秒發送一次中斷請求。
		;0x08	;00001 0 00b	--->GDT1	; 是代碼段選擇符.
		;0x10	;00010 0 00b	--->GDT2	; 是數據段描述符.
SCRN_SEL equ 0x18 	;00011 0 00b	--->GDT3	; 屏幕顯示內存段選擇符。
TSS0_SEL equ 0x20 	;00100 0 00b	--->GDT4	; 任務 0 的 TSS 段選擇符。
LDT0_SEL equ 0x28	;00101 0 00b 	--->GDT5	; 任務 0 的 LDT 段選擇符。
TSS1_SEL equ 0X30 	;00110 0 00b	--->GDT6	; 任務 1 的 TSS 段選擇符。
LDT1_SEL equ 0x38 	;00111 0 00b	--->GDT7	; 任務 1 的 LDT 段選擇符。

bits 32			;nasm instruction:32 bits model.
global startup_32,write_char1
extern main
startup_32:
; 首先加載數據段寄存器 DS、堆棧段寄存器 SS 和堆棧指針 ESP。所有段的線性基地址都是 0。
	mov eax,0x10 	; 0x10 是 GDT 中數據段選擇符。00010 0 00b --->GDT2
	mov ds,ax
	lss esp,[init_stack]
; 在新的位置重新設置 IDT 和 GDT 表。
	call setup_gdt	; 設置 GDT。
	mov eax,0x10	; 在改變了 GDT 之後重新加載所有段寄存器。
	mov ds,ax
	mov es,ax
	mov fs,ax
	mov gs,ax
	lss esp,[init_stack]

after_page_tables:
	push 0		;# These are the parameters to main :-)
	push 0
	push 0
	push L6		;# return address for main, if it decides to.
	push main
	jmp setup_paging
L6:
	jmp L6

align 4
setup_paging:
	ret

; 以下是設置 GDT 和 IDT 中描述符項的子程序。
setup_gdt:			; 使用 6 字節操作數 lgdt_48 設置 GDT 表位置和長度。
	lgdt [lgdt_48]
	ret

;------------------------------------------------------------------------------------------
; 顯示字符子程序。取當前光標位置並把 AL 中的字符顯示在屏幕上。整屏可顯示 80 X 25 個字符。
;------------------------------------------------------------------------------------------
write_char1:
	push ax
	push gs			; 首先保存要用到的寄存器, EAX 由調用者負責保存。
	push ebx
	push ecx
	mov ax,'A'
again:	mov ecx,0xFFFFF
	mov ebx,SCRN_SEL	; 然後讓 GS 指向顯示內存段( 0xb8000) 。
	mov gs,bx
	mov bx,[scr_loc]	; 再從變量 scr_loc 中取目前字符顯示位置值。
	shl ebx,1		; 因爲在屏幕上每個字符還有一個屬性字節,因此字符
	mov byte [gs:ebx],al		; 實際顯示位置對應的顯示內存偏移地址要乘 2。
delay:	loop delay
	shr ebx,1		; 把字符放到顯示內存後把位置值除 2 加 1,此時位置值對
	inc ebx			; 應下一個顯示位置。如果該位置大於 2000,則復位成 0。
	cmp ebx,2000
	jb .1
	mov ebx,0
	inc ax
.1:
	mov [scr_loc],ebx	; 最後把這個位置值保存起來( scr_loc),
	mov ecx,2000
	loop again
	pop ebx			; 並彈出保存的寄存器內容,返回。
	pop gs
	pop ax
	ret

scr_loc:
	dd 0			; 屏幕當前顯示位置。按從左上角到右下角順序顯示。

align 4
lidt_48:
	dw 256*8-1 ; 加載 IDTR 寄存器的 6 字節操作數:表長度和基地址。
	dd idt
lgdt_48:
	dw (end_gdt-gdt)-1 ; 加載 GDTR 寄存器的 6 字節操作數:表長度和基地址。
	dd gdt

align 8
; LDT
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |_____________________word3_____________________|__________________word2______________|
;low   |_____________________word1_____________________|__________________word0______________|
;
;---------------------------------------------------------------------------------------------
;
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |___________entry_offset_addr(31~16)____________|_P|_DPL_|____________________________|
;low   |_______________Seg_select______________________|_______entry_offset_addr(15~0)_______|

idt:
	times 256 dq 0 ; IDT 空間。共 256 個門描述符,每個 8 字節,佔用 2KB。

; 下面是全局描述符表 GDT 的內容。其中包含 3 個段描述符。第 1 個不用,另 2 個是代碼和數據段描述符。
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |_____________________word3_____________________|__________________word2______________|
;low   |_____________________word1_____________________|__________________word0______________|
;
;---------------------------------------------------------------------------------------------
;
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |____Base_addr(31~24)___|___________|limit(19~16|___________|___TYPE__|__addr(23~16)__|
;low   |_______________Base_addr(15~0)_________________|____________Seg_limit(15~0)__________|

gdt:
	dq 0x0000000000000000		; GDT 表。第 1 個描述符不用。
	dq 0x00c09a00000007ff		; 第 2 個是內核代碼段描述符。其選擇符是 0x08。
	dq 0x00c09200000007ff		; 第 3 個是內核數據段描述符。其選擇符是 0x10。
	dq 0x00c0920b80000002		; 第 4 個是顯示內存段描述符。其選擇符是 0x18。

end_gdt:
	times 128 dd 0	; 初始內核堆棧空間。
init_stack: 		; 剛進入保護模式時用於加載 SS:ESP 堆棧指針值。
	dd init_stack	; 堆棧段偏移位置。
	dw 0x10		; 堆棧段同內核數據段。

main.c代碼如下:

extern void write_char1();

int main(int argc, char **argv)
{
	while(1) {
		write_char1();
	}
	return 0;
}

Makefile如下:

LDFLAGS=-s -x -M -nostartfiles -m elf_i386 -Ttext 0 -e startup_32
CFLAGS=-m32 -nostdinc -Wall -O

all:
	nasm -f bin bootsect.s -o bootsect
	nasm -f elf head.s -o head.o
	gcc $(CFLAGS) -c main.c -o main.o
	ld $(LDFLAGS) -o system head.o main.o > System.map
	dd if=bootsect of=boot.img
	dd ibs=160 skip=1 seek=1 if=system of=boot.img

clean:
	rm bootsect head head.o main.o system System.map -f

這裏ibs=160,是從二進制文件看出的偏移值。
objdump -D system反彙編system文件,地址0處的內容爲:b8 10 00 00 00
在這裏插入圖片描述

使用UltraEdit編輯器打開system,看到b8 10 00 00 00在a0h處,即160.去掉system的文件頭,拷貝到boot.img 的512KB位置後。不同的ld選項命令,這個值可能不同。
在這裏插入圖片描述

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