一、bootloader代碼
#計算機啓動運行在實模式,主要經過以下幾個步驟進入保護模式 .text #.code16表示16位代碼段 .code16 .global start start: #1、將ds、es和ss段寄存器均設置成cs段寄存器的值,並將棧頂指針esp指向0x7c00,棧向低地址增長。這步操作其實也可省略,因爲在16位代碼段中還用不到其他段寄存器,在需要使用的時候再初始 #化也不遲。 movw %cs,%ax movw %ax,%ds # -> Data Segment movw %ax,%es # -> Extra Segment movw %ax,%ss # -> Stack Segment movl $0x7C00,%esp
#2、關中斷,在後面我們在內存中會建立中斷向量表,所以事先關好中斷,防止在建表過程中來了中斷,所以事先屏蔽,防止這種情況產生。 cli
#3、打開地址線A20。實際上若我們使用qemu跑這個程序時,A20默認已經打開了,但爲了兼容性,最好還是手動將A20地址線打開,讀者可以試一試將打開A20代碼刪去後,在保護模式(32位代碼段#)下用回滾機制測試時是否仍然顯示字符 #8042(鍵盤控制器)端口的P21和A20相連,置1則打開 #0x64端口 讀:位1=1 輸入緩衝器滿(0x60/0x64口有給8042的數據) #0x64端口 寫: 0xd1->寫8042的端口P2,比如位2控制P21 當寫時,0x60端口提供數據,比如0xdf,這裏面把P2置1 waitforbuffernull1: #先確定8042是不是爲空,如果不爲空,則一直等待 xorl %eax,%eax inb $0x64,%al testb $0x2,%al jnz waitforbuffernull1 #8042中沒有命令,則開始向0x64端口發出寫P2端口的命令 movb $0xd1,%al outb %al,$0x64 waitforbuffernull2: #再確定8042是不是爲空,如果不爲空,則一直等待 xorl %eax,%eax inb $0x64,%al testb $0x2,%al jnz waitforbuffernull2 #向0x60端口發送數據,即把P2端口設置爲0xdf movb $0xdf,%al outb %al,$0x60
#在實模式下,不能通過BIOS中斷輸出字符來判斷A20是否真的打開。因爲16位代碼段只能進行16位尋址,所以你無法訪問到第20位,也就不可能判斷A20是否爲1了。 /* movl $0x1,%eax movl %eax,0x136f20 # loop forever if it isn't cmpl %eax,0x036f20 je disperror dispsuccess: movw $success ,%ax movw %ax ,%bp # es:bp = 串地址 movw $12 ,%cx # cs = 串長度 movw $0x1301 ,%ax # ah=0x13:顯示字符串 ,al=0x1:顯示輸出方式 movw $0x000c ,%bx # bh=0 :第0頁, bl=0xc :高亮 黑底紅字 movb $0 ,%dl # 在0行0列顯示 int $0x10 # 調用BIOS提供的int服務0x10的0x13功能:顯示字符串 #ret disperror:*/
#4、加載gdtr,將內存中的gdt表結構讀入gdtr寄存器 lgdt gdt_48
#5、打開保護模式,將cr0的位0置爲1,一般而言BIOS中斷只在實模式下進行調用 movl %cr0,%eax orl $0x1,%eax movl %eax,%cr0
#6、進入到32位代碼段。0x8代表段選擇子(16位)——0000000000001000,其中最後2爲代表特權級,linux內核只使用了2個特權級(0和3),00代表0特權級(內核級),倒數第3位的代表是gdt(全局描述符表)還是idt(局部描述#符表),0代表全局描述符表,前13位代表gdt的項數(第1項),代碼段。所以0x8代表特權級爲0(內核級)的全局代碼段,promode代表偏移地址。 ljmp $0x8,$promode
#7、32位代碼段 promode: .code32 #注意、此時不能再像實模式下的16位代碼段一樣將ds、es、ss設置成cs的值了,因爲此時是32位保護模式,將代碼段和數據段分開了,儘管它們的基地址一樣,段限長一樣,即指向同一段地址空間,但 #兩個段的屬性不同,具體看IA32手冊中對gdt表中的描述符屬性。ds、es、ss均可看作數據段的內容,如果要設置的話,按如下代碼進行設置。同樣,此段程序中仍未用到這些段寄存器,所以這段代碼可#寫可不寫,0x10按照選擇子的描述——000000000001000,前13位代表gdt的項數(第2項),數據段。所以0x10代表特權級爲0(內核級)的全局數據段。 movw $0x10,%ax movw %ax,%ds # -> Data Segment movw %ax,%es # -> Extra Segment movw %ax,%ss # -> Stack Segment #8、用回滾機制判斷0x100000內存地址的數據和0x000000內存地址的數據是否相同,從而確定是否已經打開A20 movl $0x1,%eax movl %eax,0x100200 cmpl 0x000200,%eax #9、若兩個內存的數據不相同,則說明A20爲1,即A20地址線已經打開了,跳轉到display程序中在VGA中顯示字符'Y',若沒打開則跳轉到loop0死循環。 jne display jmp loop0 #10、此時處於保護模式,不能調用BIOS中斷,只能通過VGA來顯示字符從而判斷A20是否真的打開。 display: #11、0x18代表gdt的第3項,本程序中設置的是VGA段(顯示器內存) movw $0x18,%ax movw %ax,%gs movl $((80*1+1)*2),%edi/*第11行,79列*/ movb $0x0c,%ah/*高四位表示黑底,低四位表示紅字*/ movb $'Y',%al/*顯示的字符*/ movw %ax,%gs:(%edi) loop0: /* 無限循環 */ jmp loop0 /*success: .string "open A20 Success!" error: .string "open A20 Error!"*/ #在內存中做一塊GDT表 .align 2 gdt: .word 0,0,0,0
.word 0xFFFF #第1項 CS 基地址爲0 .word 0x0000 .word 0x9A00 .word 0x00C0
.word 0xFFFF #第2項 DS 基地址爲0 .word 0x0000 .word 0x9200 .word 0x00C0
.word 0xFFFF #第3項 VGA 基地址位0xb8000 .word 0x8000 .word 0x920b .word 0x0000 #將gdtr專用寄存器指向我們在內存中做的一塊GDT表,GDTR寄存器格式:48位(高32位地址+低16位限長),intel是小端方式 gdt_48: #gdt表限長 sizeof(gdt)-1 低地址,放在gdtr的低字節 .word 0x1f #gdt表基址 高地址,放在gdtr的高字節 .long gdt .org 0x1fe, 0x00 /* 0x1fe=510,表示從ret後的位置開始,直到510處結束的代碼/數據空間,填寫0x00 */ .word 0xaa55 /* 合法的主引導扇區標識 */ |
二、makefile代碼
all:bootsector bootsector.o:bootsector.S as -o bootsector.o bootsector.S bootsector:bootsector.o ld --oformat binary -e start -Ttext 0x7c00 -o bootsector bootsector.o #bootmain.o run:bootsector qemu-system-i386 -fda bootsector clean: rm bootsector.o bootsector |
三、安裝qemu環境,然後輸入以下命令
make run