操作系统:GDT简介&实模式到保护模式的跳转
前言(瞎逼逼)
自從AFO之后就想著開一個什么新坑,但是包括但不限于大學申請之類的破事兒讓我的確忙活了一陣子。。。閑下來的時候突然想起來以前曾經開過一個小頭(當然很快就因為姿勢水平不夠就放棄了)的編寫操作系統,于是自己就想著把兒時的夢想繼續堅持下去吧
這篇博客主要是依靠于淵的《Orange's 一個操作系統的實現》里面的代碼來給出的解釋,所以可能會有很多的匯編。
實模式
CPU自啟動的時候一開始會首先進入實模式,此時我們只能訪問1MB的內存,并且任何程序都可以訪問內存段內的任意位置,這樣顯然是不安全的。但是現在的32位CPU可以訪問4GB的內存,我們主要是通過進入保護模式來達到這個目的的(保護模式帶來的好處并不限于訪問內存的擴大)
GDT
我們先來想一下,實模式下CPU的尋址方式是:
[段地址:段內偏移
]
這種方式在以前固然好,但是新的32位CPU提供了32位地址線。老的16位寄存器已經不夠用了,我們現在需要一種新的內存尋址方式,GDT就此誕生了
GDT本質上一個表,表內包含了一段內存的段基址,段界限,段屬性。實模式下我們使用段地址:偏移地址通過地址處理器來直接訪問內存,但是實模式下段地址的意義發生了根本的變化。段地址不再表示實際內存中的某一段,而是表示的我們要訪問的段在GDT表里面的偏移。通過訪問GDT表的特定表項來得出段基址,緊接著通過段內偏移來訪問內存。此時的段地址我們有另外一個名字——段選擇子。我們通過定義不同的選擇子來對應到不同的表項里面去,接著就可以訪問特定內存了。
總結起來就是這個樣子的:
當然,要使用GDT我們必須先有一個GDT,下面是GDT的定義部分:
[SECTION .gdt]
LABEL_GDT: Descriptor 0, 0, 0 ;空描述符
LABEL_DESC_CODE32: Descriptor 0,SegCode32Len-1, DA_C+DA_32 ;32位代碼段
LABEL_DESC_VIDEO: Descriptor 0B8000h,0ffffh ,DA_DRW ;顯存映射到內存中的地址
然后加上GDT的長度和界限
GdtLen equ $-LABEL_GDT ;GDT長度
GdtPtr dw GdtLen-1 ;GDT界限
dd 0
在這里面的 LABEL_DESC_CODE32 和 GdtPtr 都要-1,因為訪問指定表項所對應的段基址的時候都是包含基址的,長度自然要-1
再往后面就是定義段選擇子了,其實很簡單,就是表項相對于GDT表的偏移
SelectorCode32 equ LABEL_DESC_CODE32-LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO-LABEL_GDT
然后我們就要開始準備載入GDT了。這里 LABEL_DESC_CODE32 的基地址并不急著給出,因為你也不知道32位代碼段的起始位置。
接著就是在16位下初始化GDT了:
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0100h
mov ax,0003h
int 10h
這里除了一些基本的指令以外就是我調用了10號中斷來清了屏,因為bochs一開始屏幕上會有一堆BIOS提示。
且聲明了棧段和sp。
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32+2],ax
shr eax,16
mov byte [LABEL_DESC_CODE32+4],al
mov byte [LABEL_DESC_CODE32+7],ah
這里我們就要開始處理之前留下來的32位代碼段基地址的問題了。要注意的是,由于此時我們仍然處于16位下,并且GDT并沒有被載入到i內存中去,所以 LABEL_DESC_CODE32+2 指向的仍然是標號指向的位置,也就是表項內部(和之前在實模式下尋址方式一樣,不要搞混了)
xor eax,eax
mov ax,ds
shl eax,4
add eax,LABEL_GDT
mov dword [GdtPtr+2],eax
CPU是通過寄存器GDTR來得知GDT的基地址和界限的,GDTR的示意圖在這里:
我們要做到的就是在GdtPtr里面填充符合標準的數據然后使用后lgdt指令來加載GDTR寄存器,也就是上面的這段代碼所做的事情。
然后就可以快樂地加載GDTR了
lgdt [GdtPtr]
接著我們需要屏蔽掉所有16位中斷。因為保護模式下中斷處理的方式都是不同的(比如說尋址),如果計算機在保護模式之后又跑去處理實模式下的中斷代碼的化會發生錯誤
cli ;關中斷
再就是打開A20地址線了。關于A20地址線的歷史性問題這里就不再贅述,感興趣的化可以去《x86匯編語言 從實模式到保護模式》的11.5章節里面找到細節的描述。
具體來講,打開A20地址線的方法就是操作92號端口來實現的:
in al,92h
or al,00000010b
out 92h,al
接下來就是置控制寄存器cr0的PE(Protected Enable)位到1來開啟保護模式
mov eax,cr0
or eax,1
mov cr0,eax
然后就是跳轉到32位保護模式下的代碼段了:
jmp dword SelectorCode32:0
這里有一些細節要注意。因為此時GDTR已經獲得了GDT的位置,所以這里就已經是通過GDT來尋址了。并且此時偏移地址仍然是在16位下進行編譯的,所以如果偏移地址不是0而是一個很大的值,普通的 jmp SelectorCode32:0x12345678 可能會讓偏移位上的地址發生截斷。所以我們通過聲明dword類型的地址避免編譯器把偏移地址給自動截斷
接下來就是32位代碼段的代碼了,有一些之前使用過的標號是在這里聲明的,可以和之前的代碼結合起來看:
[SECTION .s32]
[BITS 32]
LABEL_SEG_CODE32:
mov ax,SelectorVideo
mov gs,ax
mov edi,(80*11+39)*2
mov ah,0ch
mov al,'p'
mov [gs:edi],ax
jmp $
SegCode32Len equ $-LABEL_SEG_CODE32
就是在屏幕中央打印一個紅色的 'p'
所有代碼:
%include "pm.inc"
org 07c00h
jmp LABEL_BEGIN
[SECTION .gdt]
LABEL_GDT: Descriptor 0, 0, 0
LABEL_DESC_CODE32: Descriptor 0,SegCode32Len-1, DA_C+DA_32
LABEL_DESC_VIDEO: Descriptor 0B8000h,0ffffh ,DA_DRW
GdtLen equ $-LABEL_GDT
GdtPtr dw GdtLen-1
dd 0
SelectorCode32 equ LABEL_DESC_CODE32-LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO-LABEL_GDT
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0100h
mov ax,0003h
int 10h
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32+2],ax
shr eax,16
mov byte [LABEL_DESC_CODE32+4],al
mov byte [LABEL_DESC_CODE32+7],ah
xor eax,eax
mov ax,ds
shl eax,4
add eax,LABEL_GDT
mov dword [GdtPtr+2],eax
lgdt [GdtPtr]
cli
in al,92h
or al,00000010b
out 92h,al
mov eax,cr0
or eax,1
mov cr0,eax
jmp dword SelectorCode32:0
[SECTION .s32]
[BITS 32]
LABEL_SEG_CODE32:
mov ax,SelectorVideo
mov gs,ax
mov edi,(80*11+39)*2
mov ah,0ch
mov al,'p'
mov [gs:edi],ax
jmp $
SegCode32Len equ $-LABEL_SEG_CODE32
運行截圖:
后續
很憋屈的是,這段代碼依然是放在引導扇區里面的。所以如果你用bximage新創建了一個1.44mb大小的img的化,需要在尾部寫入0xaa55以讓cpu識別到引導扇區,否則就會報 no bootable device的錯
第一次寫OS相關的博客,可能會有一些地方出錯。。。
總結
以上是生活随笔為你收集整理的操作系统:GDT简介&实模式到保护模式的跳转的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无所适从是什么意思(瑕不掩瑜是什么意思)
- 下一篇: 杭州端午节有什么风俗