保護(hù)模式, (Protected Mode,或有時(shí)簡寫為 pmode) 是一種80286系列和之后的 x86兼容 CPU 操作模式。保護(hù)模式有一些新的特色,設(shè)計(jì)用來增強(qiáng)多工和系統(tǒng)穩(wěn)定度,像是內(nèi)存保護(hù),分頁系統(tǒng),以及硬件支援的虛擬內(nèi)存。大部分的現(xiàn)今 x86操作系統(tǒng)都在保護(hù)模式下運(yùn)行,包含 Linux、FreeBSD、以及微軟 Windows 2.0和之后版本。
另外一種 286 和其之后 CPU 的操作模式是真實(shí)模式,一種向前兼容且關(guān)閉這些特色的模式。設(shè)計(jì)用來讓新的芯片可以執(zhí)行舊的軟件。依照設(shè)計(jì)的規(guī)格,所有的 x86 CPU 都是在真實(shí)模式下開機(jī)來確保傳統(tǒng)操作系統(tǒng)的向前兼容性。在任何保護(hù)模式的特色可用前,他們必須要由某些程序手動(dòng)地切換到保護(hù)模式。在現(xiàn)今的電腦,這種切換通常是由 操作系統(tǒng) 在開機(jī)時(shí)候必須完成的第一件工作的一個(gè)。它也可能當(dāng) CPU 在保護(hù)模式下運(yùn)行時(shí),使用 虛擬86模式 來執(zhí)行設(shè)計(jì)給真實(shí)模式的程序碼。
簡介
盡管用軟件的方式也有某些可能在真實(shí)模式的系統(tǒng)下使用多工,但保護(hù)模式下內(nèi)存保護(hù)的特色,可以避免有問題的程序破壞其他工作或是 操作系統(tǒng) 核心所擁有的內(nèi)存。保護(hù)模式也有中斷正在執(zhí)行程序的硬件支援,可以把 execution content 交給其他工作,得以實(shí)現(xiàn) 先占式多工。
大部分可以使用保護(hù)模式的 CPU 也擁有 32 位元暫存器 的特色 (例如 80386 系列和其后任何的芯片),導(dǎo)入了融合保護(hù)模式而成為 32 位元處理的概念。80286 芯片雖有支援保護(hù)模式,但是仍然只有 16 位元暫存器。Windows 2.0 和之后版本中的保護(hù)模式增強(qiáng)稱為 "386 增強(qiáng)模式",是因?yàn)樗麄兂吮Wo(hù)模式外,還需要 32 位元的暫存器,并且無法在 286 上面執(zhí)行 (即使 286 支援保護(hù)模式)。
即使在 32 位元芯片上已經(jīng)打開了保護(hù)模式,但是 1 MB 以上的內(nèi)存并無法存取,是由于一種仿照 IBM XT 系統(tǒng)設(shè)計(jì)特性的 memory wrap-around(內(nèi)存連續(xù)) 的因素。這種限制可以由打開 A20 line 來回避。
在保護(hù)模式下,前面 32 個(gè)中斷都是保留給 CPU 例外處理用。舉個(gè)例子,中斷 0D (十進(jìn)制 13) 是 一般保護(hù)模式錯(cuò)物 和 中斷 00 是 除以零。
在8086/8088時(shí)代,處理器只存在一種操作模式(Operation Mode),當(dāng)時(shí)由于不存在其它操作模式,因此這種模式也沒有被命名。自從80286到80386開始,處理器增加了另外兩種操作模式——保護(hù)模式PM (Protected Mode)和系統(tǒng)管理模式SMM(System Management Mode),因此,8086/8088的模式被命名為實(shí)地址模式RM(Real-address Mode)。
PM是處理器的native模式,在這種模式下,處理器支持所有的指令和所有的體系結(jié)構(gòu)特性,提供最高的性能和兼容性。對于所有的新型應(yīng)用程序和操作系統(tǒng)來說,建議都使用這種模式。為了保證PM的兼容性,處理器允許在受保護(hù)的,多任務(wù)的環(huán)境下執(zhí)行RM程序。這個(gè)特性被稱做虛擬8086模式(Virtual -8086 Mode),盡管它并不是一個(gè)真正的處理器模式。Virtual-8086模式實(shí)際上是一個(gè)PM的屬性,任何任務(wù)都可以使用它。
RM提供了英特爾 8086處理器的編程環(huán)境,另外有一些擴(kuò)展(比如切換到PM或SMM的能力)。當(dāng)主機(jī)被功率up或Reset后,處理器處于RM下。
SMM是一個(gè)對所有Intel處理器都統(tǒng)一的標(biāo)準(zhǔn)體系結(jié)構(gòu)特性。出現(xiàn)于Intel386 SL芯片。這個(gè)模式為OS實(shí)現(xiàn)平臺(tái)指定的功能(比如電源管理或系統(tǒng)安全)提供了一種透明的機(jī)制。當(dāng)外部的SMM interrupt pin(SMI#)被激活或者從APIC(Advanced Programming Interrupt Controller)收到一個(gè)SMI,處理器將進(jìn)入SMM。在SMM下,當(dāng)保存當(dāng)前正在運(yùn)行程序的整個(gè)上下文(Context)時(shí),處理器切換到一個(gè)分離的地址空間。然后SMM指定的代碼或許被透明的執(zhí)行。當(dāng)從SMM返回時(shí),處理器將回到被系統(tǒng)管理中斷之前的狀態(tài)。
由于機(jī)器在功率up或Reset之后,處理器處于RM狀態(tài),而對于英特爾 80386以及其后的芯片,只有使用PM才能發(fā)揮出最大的作用。所以就面臨著一個(gè)從RM切換到PM的問題。
本文不討論SMM,本節(jié)的重點(diǎn)集中于在Booting階段如何從RM切換到PM,這里不會(huì)過多的討論P(yáng)M的細(xì)節(jié),因?yàn)椤禝ntel Architecture Software Developer’s Manual Volume 3: System Programming》中有非常詳盡和準(zhǔn)確的介紹。
在Protected Mode下,一個(gè)重要的必不可少的數(shù)據(jù)結(jié)構(gòu)就是GDT(Global Descriptor Table)。
為什么要有GDT?首先考慮一下在Real Mode下的編程模型:
在Real Mode下,對一個(gè)內(nèi)存地址的訪問是通過Segment:Offset的方式來進(jìn)行的,其中Segment是一個(gè)段的base address,一個(gè)Segment的最大長度是64 KB,這是16-bit系統(tǒng)所能表示的最大長度。而Offset則是相對于此Segment Base Address的偏移量。Base Address+Offset就是一個(gè)內(nèi)存絕對地址。由此,可以看出,一個(gè)段具備兩個(gè)因素:Base Address和Limit(段的最大長度),而對一個(gè)內(nèi)存地址的訪問,則是需要指出:使用哪個(gè)段?以及相對于這個(gè)段Base Address的Offset,這個(gè)Offset應(yīng)該小于此段的Limit。當(dāng)然對于16-bit系統(tǒng),極限不要指定,默認(rèn)為最大長度64KB,而 16-bit的Offset也永遠(yuǎn)不可能大于此Limit。在實(shí)際編程的時(shí)候,使用16-bit段寄存器CS(Code Segment),DS(Data Segment),SS(Stack Segment)來指定Segment,CPU將段積存器中的數(shù)值向左偏移4-bit,放到20-bit的地址線上就成為20-bit的Base Address。
到了Protected Mode,內(nèi)存的管理模式分為兩種,段模式和頁模式,其中頁模式也是基于段模式的。也就是說,Protected Mode的內(nèi)存管理模式事實(shí)上是:純段模式和段頁式。進(jìn)一步說,段模式是必不可少的,而頁模式則是可選的——如果使用頁模式,則是段頁式;否則這是純段模式。
既然是這樣,就先不去考慮頁模式。對于段模式來講,訪問一個(gè)內(nèi)存地址仍然使用Segment:Offset的方式,這是很自然的。由于 Protected Mode運(yùn)行在32-bit系統(tǒng)上,那么Segment的兩個(gè)因素:Base Address和Limit也都是32位的。IA-32允許將一個(gè)段的Base Address設(shè)為32-bit所能表示的任何值(Limit則可以被設(shè)為32-bit所能表示的,以2^12為倍數(shù)的任何指),而不象Real Mode下,一個(gè)段的Base Address只能是16的倍數(shù)(因?yàn)槠涞?-bit是通過左移運(yùn)算得來的,只能為0,從而達(dá)到使用16-bit段寄存器表示20-bit Base Address的目的),而一個(gè)段的Limit只能為固定值64 KB。另外,Protected Mode,顧名思義,又為段模式提供了保護(hù)機(jī)制,也就說一個(gè)段的描述符需要規(guī)定對自身的訪問權(quán)限(Access)。所以,在Protected Mode下,對一個(gè)段的描述則包括3方面因素:【Base Address, 極限, Access】,它們加在一起被放在一個(gè)64-bit長的數(shù)據(jù)結(jié)構(gòu)中,被稱為段描述符。這種情況下,如果直接通過一個(gè)64-bit段描述符來引用一個(gè)段的時(shí)候,就必須使用一個(gè)64-bit長的段積存器裝入這個(gè)段描述符。但英特爾為了保持向后兼容,將段積存器仍然規(guī)定為16-bit(盡管每個(gè)段積存器事實(shí)上有一個(gè)64-bit長的不可見部分,但對于程序員來說,段積存器就是16-bit的),那么很明顯,無法通過16-bit長度的段積存器來直接引用64-bit的段描述符。
怎么辦?解決的方法就是把這些長度為64-bit的段描述符放入一個(gè)數(shù)組中,而將段寄存器中的值作為下標(biāo)索引來間接引用(事實(shí)上,是將段寄存器中的高13 -bit的內(nèi)容作為索引)。這個(gè)全局的數(shù)組就是GDT。事實(shí)上,在GDT中存放的不僅僅是段描述符,還有其它描述符,它們都是64-bit長,隨后再討論。
GDT可以被放在內(nèi)存的任何位置,那么當(dāng)程序員通過段寄存器來引用一個(gè)段描述符時(shí),CPU必須知道GDT的入口,也就是基地址放在哪里,所以英特爾的設(shè)計(jì)者門提供了一個(gè)寄存器GDTR用來存放GDT的入口地址,程序員將GDT設(shè)定在內(nèi)存中某個(gè)位置之后,可以通過LGDT指令將GDT的入口地址裝入此積存器,從此以后,CPU就根據(jù)此積存器中的內(nèi)容作為GDT的入口來訪問GDT了。
GDT是Protected Mode所必須的數(shù)據(jù)結(jié)構(gòu),也是唯一的——不應(yīng)該,也不可能有多個(gè)。另外,正象它的名字(Global Descriptor Table)所揭示的,它是全局可見的,對任何一個(gè)任務(wù)而言都是這樣。
除了GDT之外,IA-32還允許程序員構(gòu)建與GDT類似的數(shù)據(jù)結(jié)構(gòu),它們被稱作LDT(Local Descriptor Table),但與GDT不同的是,LDT在系統(tǒng)中可以存在多個(gè),并且從LDT的名字可以得知,LDT不是全局可見的,它們只對引用它們的任務(wù)可見,每個(gè)任務(wù)最多可以擁有一個(gè)LDT。另外,每一個(gè)LDT自身作為一個(gè)段存在,它們的段描述符被放在GDT中。
IA-32為LDT的入口地址也提供了一個(gè)寄存器LDTR,因?yàn)樵谌魏螘r(shí)刻只能有一個(gè)任務(wù)在運(yùn)行,所以LDT寄存器全局也只需要有一個(gè)。如果一個(gè)任務(wù)擁有自身的LDT,那么當(dāng)它需要引用自身的LDT時(shí),它需要通過LLDT將其LDT的段描述符裝入此寄存器。LLDT指令與LGDT指令不同的時(shí),LGDT指令的操作數(shù)是一個(gè)32-bit的內(nèi)存地址,這個(gè)內(nèi)存地址處存放的是一個(gè)32-bit GDT的入口地址,以及16-bit的GDT Limit。而LLDT指令的操作數(shù)是一個(gè)16-bit的選擇子,這個(gè)選擇子主要內(nèi)容是:被裝入的LDT的段描述符在GDT中的索引值——這一點(diǎn)和剛才所討論的通過段積存器引用段的模式是一樣的。
LDT只是一個(gè)可選的數(shù)據(jù)結(jié)構(gòu),你完全可以不用它。使用它或許可以帶來一些方便性,但同時(shí)也帶來復(fù)雜性,如果你想讓你的OS內(nèi)核保持簡潔性,以及可移植性,則最好不要使用它。
引用GDT和LDT中的段描述符所描述的段,是通過一個(gè)16-bit的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)的,這個(gè)數(shù)據(jù)結(jié)構(gòu)叫做Segment Selector——段選擇子。它的高13位作為被引用的段描述符在GDT/LDT中的下標(biāo)索引,bit 2用來指定被引用段描述符被放在GDT中還是到LDT中,bit 0和bit 1是RPL——請求特權(quán)等級(jí),被用來做保護(hù)目的,這里不詳細(xì)討論它。
前面所討論的裝入段寄存器中作為GDT/LDT索引的就是Segment Selector,當(dāng)需要引用一個(gè)內(nèi)存地址時(shí),使用的仍然是Segment:Offset模式,具體操作是:在相應(yīng)的段寄存器裝入Segment Selector,按照這個(gè)Segment Selector可以到GDT或LDT中找到相應(yīng)的Segment Descriptor,這個(gè)Segment Descriptor中記錄了此段的Base Address,然后加上Offset,就得到了最后的內(nèi)存地址。
安裝描述
由上一節(jié)的討論得知,GDT是Protected Mode所必須的數(shù)據(jù)結(jié)構(gòu),那么在進(jìn)入Protected Mode之前,必須設(shè)定好GDT,并通過LGDT將其裝入相應(yīng)的寄存器。
盡管GDT允許被放在內(nèi)存的任何位置,但由于GDT中的元素——描述符——都是64-bit長,也就是說都是8個(gè)字節(jié),所以為了讓CPU對GDT的訪問速度達(dá)到最快,應(yīng)該將GDT的入口地址放在以8個(gè)字節(jié)對齊,也就是說是8的倍數(shù)的地址位置。
GDT中第一個(gè)描述符必須是一個(gè)空描述符,也就是它的內(nèi)容應(yīng)該全部為0。如果引用這個(gè)描述符進(jìn)行內(nèi)存訪問,則是產(chǎn)生General Protection異常。
如果一個(gè)OS不使用虛擬內(nèi)存,段模式會(huì)是一個(gè)不錯(cuò)的選擇。但現(xiàn)代OS沒有不使用虛擬內(nèi)存的,而實(shí)現(xiàn)虛擬內(nèi)存的比較方便和有效的內(nèi)存管理方式是頁式管理。但是在IA-32上如果想使用頁式管理,只能使用段頁式——沒有方法可以完全禁止段模式。但可以盡力讓段的效果降低的最小。
IA-32提供了一種被稱作“Basic Flat Model”的分段模式可以達(dá)到這種效果。這種模式要求在GDT中至少要定義兩個(gè)段描述符,一個(gè)用來引用Data Segment,另一個(gè)用來引用Code Segment。這2個(gè)Segment都包含整個(gè)線性空間,即Segment Limit = 4 GB,即使實(shí)際的物理內(nèi)存遠(yuǎn)沒有那么多,但這個(gè)空間定義是為了將來由頁式管理來實(shí)現(xiàn)虛擬內(nèi)存。
在這里,只是處于Booting階段,所以只需要初步設(shè)置一下GDT,等真正進(jìn)入Protected Mode,啟動(dòng)了OS Kernel之后,具體OS打算如何設(shè)置GDT,使用何種內(nèi)存管理模式,由Kernel自身來設(shè)置,Booting只需要給Kernel的數(shù)據(jù)段和代碼段設(shè)置全部線性空間就可以了。
段描述符的格式
具體到代碼段和數(shù)據(jù)段,它們的格式如下圖所示:
下面就是在Booting階段為進(jìn)入Protected Mode而設(shè)置的臨時(shí)的gdt。這里定義了3個(gè)段描述符:第一個(gè)是系統(tǒng)規(guī)定的空描述符,第2個(gè)是引用4 GB線性空間的代碼段,第3個(gè)是引用4 GB線性空間的數(shù)據(jù)段。這是"Basic Flat Model"所要求的最下GDT設(shè)置,但就booting階段,只是為了進(jìn)入Protected Mode,并為內(nèi)核提供一個(gè)連續(xù)的,最大的線性空間這個(gè)目的而言,已經(jīng)足夠了。
# Descriptor tables
gdt:
.word 0, 0, 0, 0 # dummy
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
.word 0 # base address = 0
.word 0x9A00 # code read/exec
.word 0x00CF # Granularity = 4096, 386
# (+5th nibble of 極限)
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
.word 0 # base address = 0
.word 0x9200 # data read/write
.word 0x00CF # granularity = 4096, 386
# (+5th nibble of limit)
加載描述
設(shè)置好GDT之后,需要通過LGDT指令將設(shè)定的gdt的入口地址和gdt表的大小裝入GDTR寄存器。
GDTR寄存器包括兩部分:32-bit的線性基地址,以及16-bit的GDT大?。ㄒ?a href="/hebeideji/7243926663083704358.html">字節(jié)為單位)。需要注意的是,對于32-bit線性基地址,必須是32-bit絕對物理地址,而不是相對于某個(gè)段的偏移量。而在Booting階段,在進(jìn)入Protected Mode之前,CS和DS設(shè)置很可能不是0,所以必須計(jì)算出gdt的絕對物理地址。
為了執(zhí)行LGDT指令,你需要把這兩部分內(nèi)容放在內(nèi)存的某個(gè)位置,然后將這個(gè)位置的內(nèi)存地址作為操作數(shù)傳遞給LGDT指令。然后LGDT指令會(huì)自動(dòng)將保存在這個(gè)位置的這兩部分值裝入GDTR寄存器。
# 這是存放GDTR所需的兩部分內(nèi)容的位置
gdt_48:
.word 0x8000 # gdt 極限=2048,
# 256 GDT entries
.word 0, 0 # gdt base (filled in later)
# 下面這段代碼用來計(jì)算GDT的32-bit線性地址,并將其裝入GDTR寄存器。
xorl %eax, %eax # Compute gdt_base
movw %ds, %ax # (Convert %ds:gdt to a linear ptr)
shll , %eax
addl $gdt, %eax
movl %eax, (gdt_48+2)
lgdt gdt_48 # load gdt with whatever is appropriate
其他東西
在進(jìn)入Protected Mode之前,除了需要設(shè)置和裝入GDT之外,還需要做如下一些事情:
屏蔽所有可屏蔽中斷;
裝入IDTR;
所有協(xié)處理器被正確的Reset。
由于在Real Mode和Protected Mode下的中斷處理機(jī)制有一些不同,所以在進(jìn)入Protected Mode之前,務(wù)必禁止所有可屏蔽中斷,這可以通過下面兩種方法之一:
使用CLI指令;
對8259A可編程中斷控制器編程以屏蔽所有中斷。
即使當(dāng)進(jìn)入Protected Mode之后,也不能馬上將中斷打開,這時(shí)因?yàn)楸仨氃贠S Kernel中對相關(guān)的Protected Mode中斷處理所需的數(shù)據(jù)結(jié)構(gòu)正確的初始化之后,才能打開中斷,否則會(huì)產(chǎn)生處理器異常。
在Real Mode下,中斷處理使用IVT(Interrupt Vector Table),在Protected Mode下,中斷處理使用IDT(Interrupt Descriptor Table),所以,必須在進(jìn)入Protected Mode之前設(shè)置IDTR。
IDTR的格式和GDTR相同,IDTR的裝入方式和GDTR也相同。由于IDT中相關(guān)的中斷處理程序需要讓OS Kernel來設(shè)定,所以在Booting階段,只需要將IDTR中IDT的基地址和Size都設(shè)為0就可以了,隨后,等進(jìn)入Protected Mode之后,由OS Kernel來真正設(shè)置它。
關(guān)于中斷機(jī)制和中斷處理,請參考 Interrupt & Exception ,這里不再贅述。
#
# 這是存放IDTR所需的兩部分內(nèi)容的位置
#
idt_48:
.word 0 # idt 極限 = 0
.word 0, 0 # idt base = 0L
# 對于IDTR的處理,只需要這一條指令即可
lidt idt_48 # load idt with 0,0
#
# 通過設(shè)置8259A PIC,屏蔽所有可屏蔽中斷
#
movb xFF, %al # mask all interrupts for now
outb %al, xA1
call delay
movb xFB, %al # mask all irq's but irq2 which
outb %al, x21 # is cascaded
# 保證所有的協(xié)處理都被正確的Reset
xorw %ax, %ax
outb %al, xf0
call delay
outb %al, xf1
call delay
# Delay is needed after doing I/O
delay:
outb %al,x80
ret
好了,一切準(zhǔn)備就緒,F(xiàn)ire!:)
進(jìn)入Protected Mode,還是進(jìn)入Real Mode,完全靠CR0寄存器的PE標(biāo)志位來控制:如果PE=1,則CPU切換到PM,否則,則進(jìn)入RM。
設(shè)置CR0-PE位的方法有兩種:
第一種是80286所使用的LMSW指令,后來的80386及更高型號(hào)的CPU為了保持向后兼容,都保留了這個(gè)指令。這個(gè)指令只能影響最低的4 bit,即PE,MP,EM和TS,對其它的沒有影響。
#
#通過LMSW指令進(jìn)入Protected Mode
#
movw , %ax # protected mode (PE) bit
lmsw %ax # This is it!
第二種是英特爾所建議的在80386以后的CPU上使用的進(jìn)入PM的方式,即通過MOV指令。MOV指令可以設(shè)置CR0寄存器的所有域的值。
#
#通過MOV指令進(jìn)入Protected Mode
#
movl %cr0, %eax
xorb , %al # set PE = 1
movl %eax, %cr0 # go!!
OK,現(xiàn)在已經(jīng)進(jìn)入Protected Mode了。
很簡單,right?But It's not over yet!
啟動(dòng)內(nèi)核
已經(jīng)從Real Mode進(jìn)入Protected Mode,現(xiàn)在馬上就要啟動(dòng)OS Kernel了。
OS Kernel運(yùn)行在32-bit段模式,而當(dāng)前卻仍然處于16-bit段模式。這是怎么回事?為了了解這個(gè)問題,需要仔細(xì)探討一下IA-32的段模式的實(shí)現(xiàn)方法。
IA-32共提供了6個(gè)16-bit段寄存器:CS,DS,SS,ES,F(xiàn)S,GS。但事實(shí)上,這16-bit只是對程序員可見的部分,但每個(gè)寄存器仍然包括64-bit的不可見部分。
可見部分是為了供程序員裝載段寄存器,但一旦裝載完成,CPU真正使用的就只是不可見部分,可見部分就完全沒有用了。
不可見部分存放的內(nèi)容是什么?具體格式我沒有看到相關(guān)資料,但可以確定的是隱藏部分的內(nèi)容和段描述符的內(nèi)容是一致的(請參考段描述的格式),只不過格式可能不完全相同。但格式對理解這一點(diǎn)并不重要,因?yàn)槌绦騿T不可能能夠直接操作它。
以CS寄存器為例,對于其它寄存器也是一樣的:
在Real Mode下,當(dāng)執(zhí)行一個(gè)裝載CS寄存器的指令的時(shí)候(jmp,call,ret等),相關(guān)的值會(huì)被裝入CS寄存器的可見部分,但同時(shí)CPU也會(huì)根據(jù)可見部分的內(nèi)容來設(shè)置不可見部分。比如執(zhí)行"ljmp x1234, $go "之后,CS寄存器的可見部分的內(nèi)容就是1234h,同時(shí),不可見部分的32-bit Base Address域被設(shè)置為00001234h,20-bit的Limit域被設(shè)置為固定值10000h,也就是64 KB,Access Information部分的其它值不去考慮,只考慮其D/B位,由于執(zhí)行此指令時(shí)處于Real Mode模式,所以D/B被設(shè)置為0,表示此段是一個(gè)16-bit段。當(dāng)對CS寄存器的可見部分和不可見部分的內(nèi)容都被設(shè)置之后,CS寄存器的裝載工作完成。隨后當(dāng)CPU需要通過CS的內(nèi)容進(jìn)行地址運(yùn)算的時(shí)候,則僅僅引用不可見部分。
在Protected Mode下,當(dāng)執(zhí)行一個(gè)裝載CS寄存器的指令的時(shí)候,段選擇子(Segment Selector)被裝入CS寄存器的可見部分,同時(shí)CPU根據(jù)此選擇子到相應(yīng)的描述符表中(GDT或LDT)找到相應(yīng)的段描述符并將其內(nèi)容裝載入CS寄存器的不可見部分。隨后CPU當(dāng)需要通過CS的內(nèi)容進(jìn)行地址運(yùn)算的時(shí)候,也僅僅引用不可見部分。
從上面的描述可以看出,事實(shí)上CPU在引用段寄存器的內(nèi)容進(jìn)行地址運(yùn)算時(shí),Real Mode和Protected Mode是一致的。另外,也明白了為什么在Real Mode下設(shè)置的段寄存器的內(nèi)容到了Protected Mode下仍然引用的是16-bit段。
那么如何將CS設(shè)置為引用32-bit段?方法就像前面所討論的,使用JMP或call指令,引用一個(gè)段選擇子,到GDT中裝載一個(gè)引用32-bit段的段描述符。
需要注意的是,如果CS寄存器的內(nèi)容指出當(dāng)前是一個(gè)16-bit段,那么當(dāng)前的地址模式也就是16-bit地址模式,這與你當(dāng)前是出于Real Mode還是Protected Mode無關(guān)。而裝載32-bit段的jmp指令或call指令必須使用的是32-bit地址模式。而當(dāng)前的boot部分代碼是16-bit代碼,所以必須在此jmp/call指令前加上地址轉(zhuǎn)換前綴代碼66h。
下面的例子就是使用JMP指令裝入32-bit段。Jmpi指令的含義是段間跳轉(zhuǎn),其opcode為Eah,其格式為:jmpi Offset, Segment Selector。
# 由于當(dāng)前的代碼是16-bit代碼,而要執(zhí)行32-bit地址模式的指令,指令前
# 需要有地址模式切換前綴66h,如果直接寫jmp指令,由編譯器來生成代碼
# 的話,是無法作到這一點(diǎn)的,所以直接寫相關(guān)數(shù)據(jù)。
.字節(jié) 0x66, 0xea # prefix + jmpi-opcode
.long 0x1000 # Offset
.word __KERNEL_CS # CS segment selector
上面的代碼相當(dāng)于32-bit指令:
jmpi 0x1000,__KERNEL_CS
如果__KERNEL_CS段選擇子所引用的段描述符設(shè)置的段空間為線形地址【0,4 GB】,而將OS Kernel放在物理地址1000h,那么此jmpi指令就跳轉(zhuǎn)到OS Kernel的入口處,并開始執(zhí)行它。
此時(shí),Booting階段結(jié)束,OS正式開始運(yùn)行!
參考資料 >
計(jì)算機(jī)軟件整體保護(hù)模式之探討.中國知網(wǎng).2020-06-12