/*程序描述: ; boot.s程序編譯出的代碼共512字節(jié),將被存放在軟盤映像文件的第一個(gè)扇區(qū)中。PC在加電啟動(dòng)時(shí), ; BIOS程序會(huì)把啟動(dòng)盤上第一個(gè)扇區(qū)加載到物理內(nèi)存0x7c00位置開始處, ; 然后跳轉(zhuǎn)到0x7c00處開始執(zhí)行boot.s程序代碼。 ; 本程序(boot.s程序)將內(nèi)核代碼(head.s代碼)加載到0x10000處,然后再移動(dòng)到0x0處0, ; 注意;加載到0x0處是為了設(shè)置GDT表時(shí)可以簡單一些,因而也可以讓head.s程序盡量短一些, ; 但不能一開始就加載到0x0處是因?yàn)榧虞d操作需要使用BIOS提供的中斷過程,而BIOS使用的 ; 中斷向量表正處于內(nèi)存0開始的地方,并且在內(nèi)存1KB開始處是BIOS程序使用的數(shù)據(jù)區(qū),所以 ; 若直接將head代碼加載于此將導(dǎo)致BIOS中斷過程不能正常運(yùn)行。 ; 最后進(jìn)入保護(hù)模式,并轉(zhuǎn)到0x0處繼續(xù)運(yùn)行。 */ BOOTSEG = 0x07c0 /*前面講到PC加電啟動(dòng)時(shí)會(huì)加載本程序到0x7c00處,那為什么這里卻是0x7c0,而不 是0x7c00呢,因?yàn)?086CPU剛啟動(dòng)時(shí)是處在實(shí)地址模式,實(shí)模式下最多尋址1M, 并將1MB存儲(chǔ)空間分成許多邏輯段,每個(gè)段的長度被固定為64K。這樣每個(gè)存儲(chǔ) 單元就可以用“段基地址 段內(nèi)偏移地址”表示。段基地址由16位段寄存器 值左移4位表達(dá),段內(nèi)偏移表示相對于某個(gè)段起始位置的偏移量。所以這里的 0x07c0實(shí)際上是段基地址。 */ SYSSEG = 0x1000 //將head.s加載于此(這里為什么是0x1000而不是0x10000原因同上) SYSLEN = 17 /*內(nèi)核占用的最大磁盤扇區(qū)數(shù),為了簡化程序,這里只能加載長度不超過16個(gè)扇區(qū) 的內(nèi)核,這個(gè)作為BIOS的讀扇區(qū)功能的參數(shù)。問:既然僅限制16個(gè)扇區(qū),為何以 17作為讀入扇區(qū)數(shù)? 其實(shí)這里設(shè)置成16系統(tǒng)也能照常運(yùn)行,多拷貝一個(gè)扇區(qū)可 能出于安全考慮。 */ entry start //這個(gè)的作用是什么?匯編器匯編時(shí)必須有一個(gè)start指明入口地址,否則出現(xiàn)匯編錯(cuò)誤 start: jmpi go, #BOOTSEG /*jmpi是段間跳轉(zhuǎn)指令,執(zhí)行的結(jié)果是CS寄存器值變?yōu)?x7c0,接下來執(zhí) 行“BOOTSEG:go”處的指令。 為什么需要這句話呢?不寫不也是從下 面順序執(zhí)行嗎? 答:剛開機(jī)時(shí)所有段寄存器(包括CS)的值為0,數(shù)據(jù)傳 送指令是不能把數(shù)據(jù)傳送給CS的,因?yàn)镃S是代碼段寄存器,CS如果被修 改程序就無法執(zhí)行。所以必須用jmpi把cs改為0x7c0。 */ go: mov ax, cs //ax是屬于通用寄存器之一的累加寄存器 mov ds, ax mov ss, ax /*讓兩個(gè)段寄存器ds和ss指向0x7c0段,問:1.為何需要讓這兩個(gè)段寄存 器指向這里? 2.為何要通過ax間接傳遞數(shù)據(jù)而不能直接賦值呢?答: 1.這里是為了讓DS和SS指向和代碼段一致的段,ss里面存放堆棧段的 段地址,sp存放偏移地址,物理地址=ss* 10H sp。這樣結(jié)合下面一 句,堆棧從物理地址0x7c00 0x400開始,留1K的代碼空間 2. 80x86 中規(guī)定不能對段寄存器(CS,DS等)直接給立即數(shù) */ mov sp, #0x400 /*設(shè)置臨時(shí)棧指針。其值大于程序末端并有一定空間即可。問:1.為何需要 一個(gè)臨時(shí)棧指針? 2.這個(gè)值怎么定,程序末端在哪里如何計(jì)算?1.中斷 需要使用到堆棧 2.8086堆棧的生長方向?yàn)橄蛳略鲩L。boot.s占用512字節(jié) ,這里設(shè)置成遠(yuǎn)大于512的任意值就可以。 */ //現(xiàn)在加載內(nèi)核代碼(head.s程序)至0x10000開始處 load_system: /*問:標(biāo)號(hào)有沒有實(shí)際作用?標(biāo)號(hào)指明其所在位置的地址 首先介紹一下BIOS的0x13的0x02號(hào)功能 BIOS INT 0x13的0x02號(hào)功能 - 讀扇區(qū) INT 0x13/AH=0x02 - 將磁盤上的扇區(qū)讀入內(nèi)存 AH = 0x02 AL = 要讀入的扇區(qū)數(shù) CH = 柱面(磁道)號(hào)的低8位 CL = 位7、6是柱面(磁道號(hào))高2位,位5-0是讀入的起始扇區(qū)號(hào)(從1計(jì), 第一扇區(qū)存放的是boot.s,第二扇區(qū)開始放的是head.s,這里要 讀的是head.s所以從第二個(gè)扇區(qū)開始讀) DH = 磁頭號(hào) DL = 驅(qū)動(dòng)器號(hào) ES:BX = 緩沖區(qū)(用于保存讀入扇區(qū))的位置 返回值: AH = 狀態(tài)碼 AL = 讀到的扇區(qū)數(shù) CF = 失敗為1,成功為0 */ mov ch, #0x00 mov cl, #0x02 /*問:為什么是加載2號(hào)扇區(qū)?答:因?yàn)榇疟P的第一扇區(qū)放置的即是本程序 (引導(dǎo)啟動(dòng)程序boot.s),而緊鄰的第二扇區(qū)開始則放置內(nèi)核代碼head.s 。扇區(qū)號(hào)從1開始計(jì)算。 */ mov dh, #0x00 mov dl, #0x00 //問:驅(qū)動(dòng)器是指什么?這里的驅(qū)動(dòng)器號(hào)是0,表示floppya,即第一個(gè)軟盤驅(qū)動(dòng)器。 mov ax, #SYSSEG /*不能直接執(zhí)行mov es, #SYSSEG,編譯時(shí)會(huì)出現(xiàn)illegal immediate mode 錯(cuò)誤,因?yàn)?0x86中規(guī)定不能對段寄存器(CS,DS等)直接給值/立即數(shù) */ mov es, ax xor bx, bx //將內(nèi)核放置于1000:0000位置處 mov ah, #0x02 mov al, #SYSLEN int 0x13 //設(shè)置好各項(xiàng)參數(shù)后即可調(diào)用BIOS的0x13功能 jnc ok_load //jnc(jump not c)是一跳轉(zhuǎn)指令,當(dāng)進(jìn)位標(biāo)記C為0時(shí)跳轉(zhuǎn),為1時(shí)執(zhí)行后面的指令 die: jmp die //死循環(huán) /*到目前為止我們已將內(nèi)核代碼從磁盤讀入到內(nèi)存中指定位置了,下面就開始將內(nèi)核 代碼轉(zhuǎn)移到0x0這個(gè)內(nèi)存開始位置。共移動(dòng)8K字節(jié)((16個(gè)扇區(qū)*512B/每扇區(qū))/1024=8KB)。 */ ok_load: cli /*關(guān)中斷 問:為何在開始移動(dòng)時(shí)要關(guān)中斷,是為了防止什么事件的發(fā)生嗎? 若不關(guān)會(huì)怎樣? 在搬移之前先介紹一下REP指令及MOVW指令 REP:重復(fù)前綴,字符串操作本身每次只處理一個(gè)內(nèi)存值,但如果使用重復(fù)前 綴的話,該指令就會(huì)使用ECX作為計(jì)數(shù)器進(jìn)行重復(fù)。換句話說,就是可以用一 條指令處理整個(gè)數(shù)組。 MOVW:將DS:SI(源變址寄存器)的內(nèi)容送至ES(附加段數(shù)據(jù)寄存器):DI (目的變址寄存器),是復(fù)制過去,原來的代碼還在。 附:ES和DS的功能相同,程序中設(shè)有多個(gè)數(shù)據(jù)段時(shí),可以選用ES寄存器。一般 在串處理時(shí)用得比較多。比如將一段內(nèi)存空間存儲(chǔ)的數(shù)據(jù)復(fù)制到另一段空間,可 以分別設(shè)置DS:SI指向源存儲(chǔ)數(shù)據(jù)的地址,ES:DI指向目的存儲(chǔ)數(shù)據(jù)的地址 */ mov ax, #SYSSEG //移動(dòng)開始位置:DS:SI=0x1000:0;目的位置:ES:DI=0:0 mov ds, ax xor ax, ax mov es, ax mov cx, #0x1000 //設(shè)置共移動(dòng)4K次,每次1個(gè)字(即移動(dòng)16個(gè)扇區(qū)的代碼)。 sub si, si sub di, di rep movw //執(zhí)行重復(fù)移動(dòng)指令 //加載IDT和GDT基地址寄存器IDTR和GDTR mov ax, #BOOTSEG mov ds, ax /*讓DS重新指向0x7c0段(問:1.不一定要讓數(shù)據(jù)段指向這個(gè)位置吧? 答:這里必 須讓ds重新指向0x07c0段,因?yàn)閘idt和lgdt隱含的完整格式上是ds:idt_operand和 ds:gdt_operand,會(huì)在ds:operand這個(gè)位置去尋找它們的六字節(jié)操作數(shù)。2.這時(shí) 的情況是不是:0x0-0x2000:簡單內(nèi)核的代碼區(qū);0x7c00:數(shù)據(jù)段起始地址; */ lidt idt_48 //加載IDTR。6字節(jié)操作數(shù):2字節(jié)表長度,4字節(jié)線性基地址 lgdt gdt_48 //加載GDTR。6字節(jié)操作數(shù):2字節(jié)表長度,4字節(jié)線性基地址 /*設(shè)置好了中斷描述符表IDT和全局描述符表GDT,并且加載好IDTR和GDTR后,準(zhǔn)備進(jìn)入保護(hù)模式 設(shè)置控制寄存器CR0(即機(jī)器狀態(tài)字),進(jìn)入保護(hù)模式。段選擇符值8對應(yīng)GDT表中第2個(gè)段描述符 控制寄存器(CR0、CR1、CR2和CR3)用于控制和確定處理器的操作模式以及當(dāng)前執(zhí)行任務(wù)的特性 CR0中含有控制處理器操作模式和狀態(tài)的系統(tǒng)控制標(biāo)志 CR1保留不用 CR2含有導(dǎo)致頁錯(cuò)誤的線性地址 CR3含有頁目錄表物理內(nèi)存基地址(因此該寄存器也被稱為頁目錄基地址寄存器PDBR) */ mov ax, #0x0001 //(操作數(shù)的第四位是0x1=0001,將傳給CR0) /*先介紹一下LMSW指令:LMSW: Load Machine Status Word(置處理器狀態(tài)字) 只有操作數(shù)的低4 位被存入CR0,只有PE(位0),MP(位1)和EM(位2)和TS(位3)被改寫,CR0其他位不受影響。 */ lmsw ax //設(shè)置CR0,進(jìn)入保護(hù)模式。 /*先介紹下JMPI指令(段間跳轉(zhuǎn)指令),在實(shí)模式下JMPI B, A 是跳到以A為段基地址,以B為偏 移地址處執(zhí)行,在保護(hù)式下JMPI B,A是跳轉(zhuǎn)到以A為段選擇符,偏移為B處執(zhí)行。JMP是段內(nèi)的跳轉(zhuǎn) */ jmpi 0, 8 //然后跳轉(zhuǎn)至段選擇符指定的段中,偏移0處 /*注意此時(shí)段值已是段選擇符。該段的線性基地址是0。 這個(gè)8是怎么來的?這個(gè)8(實(shí)際是 0x0008 ,二進(jìn)制:
0000000000001 0 00)是段選擇符,由 段選擇符的定義可知,該選擇符選擇的是RPL為0,索引為1的GDT描述符,前面的lgdt指令已 經(jīng)在GDTR寄存器中存放了GDT表的位置跟長度,每個(gè)段描述符固定占用8字節(jié),所以根據(jù)索引 值就可以找到GDT表中的段描述1,可以看到該段基地址是0x0000000,加上偏移值0,由于這 里沒有開啟分頁保護(hù),所以是跳到物理地址0處執(zhí)行。 */ //下面是全局描述符表GDT的內(nèi)容。其中包含3個(gè)段描述符。第1個(gè)不用,另2個(gè)是代碼和數(shù)據(jù)段描述符 gdt: .word 0,0,0,0 //段描述符0,不用。每個(gè)描述符項(xiàng)占8字節(jié)。 .word 0x07FF //段描述符1,段限長8M,(2048*4096 = 8M) .word 0x0000 //段基地址是0x00000000 .word 0x9A00 //可讀可執(zhí)行的代碼段 .word 0x00C0 //顆粒度:4K .word 0x07FF //段描述符2 .word 0x0000 .word 0x9200 .word 0x00C0 //下面分別是LIDT和LGDT指令的6字節(jié)操作數(shù) idt_48: .word 0 .word 0,0 gdt_48: .word 0x7ff .word 0x7c00 gdt,0 .org 510 /*問:這是什么?答:.org指令表示以后的內(nèi)容從510字節(jié)開始存放,下面的AA55是引導(dǎo) 扇區(qū)的結(jié)束標(biāo)志,占二字節(jié),這就是為什么boot.s剛好占512字節(jié)的原因, 如果該標(biāo)志錯(cuò)誤系統(tǒng)就不能啟動(dòng)。 */ .word 0xAA55