我是如何学习写一个操作系统(三):操作系统的启动之保护模式
前言
上一篇其實(shí)已經(jīng)說(shuō)完了boot的大致工作,但是Linux在最后進(jìn)入操作系統(tǒng)之前還有一些操作,比如進(jìn)入保護(hù)模式。在我自己的FragileOS里進(jìn)入保護(hù)模式是在引導(dǎo)程序結(jié)束后完成的。
實(shí)模式到保護(hù)模式屬于操作系統(tǒng)的一個(gè)大坎,所以需要先提一下
從實(shí)模式到保護(hù)模式
實(shí)模式和保護(hù)模式都是CPU的工作模式,它們的主要區(qū)別就是尋址方式
實(shí)模式出現(xiàn)于早期8088CPU時(shí)期。當(dāng)時(shí)由于CPU的性能有限,一共只有20位地址線(xiàn)(所以地址空間只有1MB),以及8個(gè)16位的通用寄存器,以及4個(gè)16位的段寄存器。所以為了能夠通過(guò)這些16位的寄存器去構(gòu)成20位的主存地址,必須采取一種特殊的方式。訪(fǎng)問(wèn)內(nèi)存的就變成了:
物理地址 = 段基址 << 4 + 段內(nèi)偏移
隨著CPU的發(fā)展,可以訪(fǎng)問(wèn)的內(nèi)存空間也從1MB變?yōu)楝F(xiàn)在4GB,寄存器的位數(shù)也變?yōu)?2位。并且在實(shí)模式下,用戶(hù)程序?qū)?nèi)存的訪(fǎng)問(wèn)非常自由,沒(méi)有任何限制,隨隨便便就可以修改任何一個(gè)內(nèi)存單元。所以實(shí)模式已經(jīng)不能滿(mǎn)足時(shí)代的要求了,保護(hù)模式就應(yīng)運(yùn)而生了
保護(hù)模式的偏移值變成了32位,尋址方式仍然需要段寄存器,但是這些段寄存器存放的不再是段基址了,而是類(lèi)似一個(gè)數(shù)組的索引
而這個(gè)數(shù)組就是一個(gè)就做全局描述符表 (GDT)的東西,GDT中含有一個(gè)個(gè)表項(xiàng),每一個(gè)表項(xiàng)稱(chēng)為段描述符。
而我們通過(guò)段寄存器里的的這個(gè)索引,可以找到對(duì)應(yīng)的表項(xiàng)。段描述符存放了段基址、段界限、內(nèi)存段類(lèi)型屬性
處理器內(nèi)部有一個(gè) 48 位的寄存器,稱(chēng)為全局描述符表寄存器(GDTR)。也就是為了來(lái)記錄GDT的
段描述符
FragileOS里進(jìn)入保護(hù)模式
- 根據(jù)上面的描述,在進(jìn)入保護(hù)模式時(shí)就先需要構(gòu)造一個(gè)GDT
- 當(dāng)然中間還需要一些其它的初始化,在后面詳細(xì)提
- 然后再根據(jù)特定操作來(lái)讓CPU識(shí)別該進(jìn)入保護(hù)模式了
一部分代碼
[SECTION .gdt] ; 利用宏定義定義gdt; 段基址 段界限 屬性 LABEL_GDT: Descriptor 0, 0, 0 LABEL_DESC_CODE32: Descriptor 0, 0fffffh, DA_C | DA_32 | DA_LIMIT_4K LABEL_DESC_VIDEO: Descriptor 0B8000h, 0fffffh, DA_DRW LABEL_DESC_VRAM: Descriptor 0, 0fffffh, DA_DRW | DA_LIMIT_4Kin al, 92h ; 切換到保護(hù)模式 or al, 00000010b out 92h, almov eax, cr0 or eax , 1 mov cr0, eaxLinux啟動(dòng)前的最后準(zhǔn)備
現(xiàn)在來(lái)看看Linux在啟動(dòng)前最后還做了什么
獲得系統(tǒng)數(shù)據(jù)和進(jìn)入保護(hù)模式
setup.s主要的任務(wù)就是從BIOS拿到系統(tǒng)數(shù)據(jù)然后存放到一個(gè)內(nèi)存位置
獲取當(dāng)前光標(biāo)的位置
mov ax,#INITSEG ! this is done in bootsect already, but... mov ds,ax mov ah,#0x03 ! read cursor pos xor bh,bh int 0x10 ! save it in known place, con_init fetches mov [0],dx ! it from 0x90000.獲取內(nèi)存大小
mov ah,#0x88 int 0x15 mov [2],ax檢查現(xiàn)在的顯示方式
mov ah,#0x0f int 0x10 mov [4],bx ! bh = display page mov [6],ax ! al = video mode, ah = window width進(jìn)入保護(hù)模式
進(jìn)入保護(hù)模式的代碼也在setup中
首先先把內(nèi)核SYSTEM部分移動(dòng)到0位置,在之前它是被讀入在0x10000位置
mov ax,#0x0000cld ! 'direction'=0, movs moves forward do_move:mov es,ax ! destination segmentadd ax,#0x1000cmp ax,#0x9000jz end_movemov ds,ax ! source segmentsub di,disub si,simov cx,#0x8000repmovswjmp do_move然后就是加載上面說(shuō)的全局描述符表和中斷向量表
中斷向量表前面沒(méi)有提過(guò),但是比較簡(jiǎn)單,有點(diǎn)類(lèi)似GDT,就是 操作系統(tǒng)必須維護(hù)一份中斷向量表,每一個(gè)表項(xiàng)紀(jì)錄一個(gè)中斷處理程序(ISR,Interrupt Service Routine)的地址
end_move:mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)mov ds,axlidt idt_48 ! load idt with 0,0lgdt gdt_48 ! load gdt with whatever appropriate再接著就是打開(kāi)A20地址線(xiàn),如果不打開(kāi)A20地址線(xiàn),即使在保護(hù)模式下最大尋址還是1M
call empty_8042 mov al,#0xD1 ! command write out #0x64,al call empty_8042 mov al,#0xDF ! A20 on out #0x60,al call empty_8042初始化8259A芯片,8259A是專(zhuān)門(mén)為了對(duì)8085A和8086/8088進(jìn)行中斷控制而設(shè)計(jì)的芯片,它是可以用程序控制的中斷控制器。單個(gè)的8259A能管理8級(jí)向量?jī)?yōu)先級(jí)中斷。 對(duì)于對(duì)硬件的初始化其實(shí)就是依照CPU的固定套路
部分代碼
mov al,#0x11 ! initialization sequence out #0x20,al ! send it to 8259A-1 .word 0x00eb,0x00eb ! jmp $+2, jmp $+2 out #0xA0,al ! and to 8259A-2最后的最后,終于可以正式進(jìn)入保護(hù)模式,可以看到這里進(jìn)入保護(hù)模式的方法和我上面的move cr0 ax不太一樣,Linux之所以使用這種方法是為了兼容286之前的CPU,另外需要注意的是在進(jìn)入保護(hù)模式之后需要立馬執(zhí)行一條段間跳轉(zhuǎn)來(lái)讓CPU刷新指令隊(duì)列,這里跳轉(zhuǎn)的描述就已經(jīng)是用段值來(lái)描述了,段指的第三位到第十五位用來(lái)指向GDT里的索引(1000),也就是跳到第2個(gè)段描述符里記錄的地址
mov ax,#0x0001 ! protected mode (PE) bit lmsw ax ! This is it! jmpi 0,8 ! jmp offset 0 of segment 8 (cs)第二個(gè)GTD段描述符,所以上面也就是跳轉(zhuǎn)到內(nèi)存0處
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) .word 0x0000 ! base address=0 .word 0x9A00 ! code read/exec .word 0x00C0 ! granularity=4096, 386IDT和分頁(yè)管理機(jī)制
再往下就是正式進(jìn)入到了內(nèi)核部分,在此之前需要再提一下IDT和分頁(yè)管理機(jī)制
IDT
中斷描述符表把每個(gè)中斷或異常編號(hào)和一個(gè)指向中斷處理事件服務(wù)程序的描述符聯(lián)系起來(lái)。同GDT和LDT一樣,IDT是一個(gè)8-字節(jié)的描述符數(shù)組。和GDT、LDT不同的是,IDT的第一項(xiàng)可以包含一個(gè)描述符。為了形成一個(gè)在IDT內(nèi)的索引,處理器把中斷、異常標(biāo)識(shí)號(hào)乘以8以后來(lái)做為IDT的索引。因?yàn)橹挥?56個(gè)編號(hào),IDT不必包含超過(guò)256個(gè)描述符。它可以包含比256更少的項(xiàng),只是那些需要使用的中斷、異常的項(xiàng)。
IDT可以在內(nèi)存的任意位置。處理器通過(guò)IDT寄存器(IDTR)來(lái)定位IDT。指令LIDT和SIDT用來(lái)操作IDTR。
分頁(yè)機(jī)制
將用戶(hù)程序(進(jìn)程)的邏輯地址空間分成若干個(gè)頁(yè)(4KB)并編號(hào),同時(shí)將內(nèi)存的物理地址也分成若干個(gè)塊或頁(yè)框 4KB)并編號(hào),這樣也就是為了讓所有的應(yīng)用程序看都像是獨(dú)占一片內(nèi)存,起始地址都是為0,最后再建立一個(gè)頁(yè)表存儲(chǔ)著頁(yè)到頁(yè)框也就是真實(shí)內(nèi)存地址的映射
在內(nèi)存里有一個(gè)寄存器(PTR)來(lái)存儲(chǔ)頁(yè)表
映射的完成
- 進(jìn)程訪(fǎng)問(wèn)某個(gè)邏輯地址
- 由線(xiàn)性地址的頁(yè)號(hào),以及頁(yè)表寄存器中的始址,找到頁(yè)表并找到對(duì)應(yīng)的頁(yè)表項(xiàng)
- 由頁(yè)表項(xiàng)上的塊號(hào),找到物理內(nèi)存中的塊號(hào)
- 根據(jù)塊號(hào),和線(xiàn)性地址的頁(yè)內(nèi)地址,找到物理地址
我們通過(guò)設(shè)置CR0寄存器的PG位來(lái)開(kāi)啟分頁(yè)功能,而其它操作就都由CPU來(lái)完成,當(dāng)然前提是我們有一張頁(yè)表
兩級(jí)頁(yè)表結(jié)構(gòu)
為了減少內(nèi)存的占用量,80X86采用了分級(jí)頁(yè)表
頁(yè)目錄有2的十次方個(gè)4字節(jié)的表項(xiàng),這些表項(xiàng)指向?qū)?yīng)的二級(jí)表,線(xiàn)性地址的最高10位作為頁(yè)目錄用來(lái)尋找二級(jí)表的索引
二級(jí)頁(yè)表里的表項(xiàng)含有相關(guān)頁(yè)面的20位物理基地址,二級(jí)頁(yè)表使用線(xiàn)性地址中間10位來(lái)作為尋找表項(xiàng)的索引
- 進(jìn)程訪(fǎng)問(wèn)某個(gè)邏輯地址
- 由線(xiàn)性地址中的頁(yè)號(hào),以及外層頁(yè)表寄存器(CR3)中的外層頁(yè)表始址,找到二級(jí)頁(yè)表的始址
- 由二級(jí)頁(yè)表的始址,加上線(xiàn)性地址中的外層頁(yè)內(nèi)地址,找到對(duì)應(yīng)的二級(jí)頁(yè)表中的頁(yè)表項(xiàng)
- 由頁(yè)表項(xiàng)中的物理塊號(hào),加上線(xiàn)性地址中的頁(yè)內(nèi)地址,找到對(duì)物理地址
所以說(shuō)CPU尋址一共需要進(jìn)行兩步:
進(jìn)入到了內(nèi)核部分
head.s這部分其實(shí)已經(jīng)是進(jìn)入了內(nèi)核部分了,但是在Linux0.12里還是把它歸為Boot部分。這一部分的主要工作是重新設(shè)置GDT和IDT,然后在設(shè)置管理內(nèi)存的分頁(yè)處理機(jī)制 (在進(jìn)入保護(hù)模式后,Linux用的就是AT&T的匯編語(yǔ)法了,最顯著的差別就是源操作數(shù)和目的數(shù)的位置對(duì)調(diào)了)
- 設(shè)置IDT
- 設(shè)置GDT
- 這里就是已經(jīng)準(zhǔn)備跳入C語(yǔ)言的main部分了,也就是匯編里的函數(shù)調(diào)用,先把main的地址壓入棧中,當(dāng)下一個(gè)函數(shù)執(zhí)行完ret的時(shí)候,就會(huì)去執(zhí)行main了
- 最后就是設(shè)置分頁(yè)機(jī)制了
STOS指令:將AL/AX/EAX的值存儲(chǔ)到[EDI]指定的內(nèi)存單元
CLD清除方向標(biāo)志和STD設(shè)置方向標(biāo)志,當(dāng)方向標(biāo)志是0,該指令通過(guò)遞增的指針數(shù)據(jù)每一次迭代之后(直到ECX是零或一些其它條件,這取決于REP前綴的香味)工作,而如果該標(biāo)志是1,指針遞減。
小結(jié)
這一節(jié)主要是描述了保護(hù)模式和一些CPU需要的數(shù)據(jù)結(jié)構(gòu)。這幾篇文章相當(dāng)于講述了一臺(tái)計(jì)算機(jī)啟動(dòng)的時(shí)候都發(fā)生了什么。
- 通過(guò)引導(dǎo)程序boot來(lái)加載真正的內(nèi)核代碼
- 獲得一些硬件上的系統(tǒng)參數(shù)保存在一些內(nèi)存里供后面使用
- 最后是初始化像GDT、IDT等,然后設(shè)置分頁(yè)等等
轉(zhuǎn)載于:https://www.cnblogs.com/secoding/p/11407486.html
總結(jié)
以上是生活随笔為你收集整理的我是如何学习写一个操作系统(三):操作系统的启动之保护模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 我是如何学习写一个操作系统(二):操作系
- 下一篇: 我是如何学习写一个操作系统(四):操作系