從實模式到保護模式跳轉的詳解
三年前拿到《自己動手寫作業系統》第一版的時候,雖然很有興趣,但是沒有時間詳細地
看進去,直到前段時間又看到它的第二版出來,興趣又提上來了。正好此時的工作之餘可以
深入地研究一下。
第一遍看這段**,沒懂(我承認是個很不聰明的人),只看懂了下面中文描述,說是
從實模式跳到保護模式的。
第二遍看這段**,似乎懂了一些,大約20%吧。
第三遍,第四遍,第五遍...................第n遍,我懂了,我承認古代的大哥們是不騙
人的,他們都說書讀百遍,其義自見。
第n+1遍,我發現作者自己似乎沒有懂。我用似乎是說作者也許懂了,但他的描述讓別人
不好懂。
於是我這個覺得自己懂的人來做乙個詳細的解釋。
descriptor是乙個巨集,定義乙個8位元組大小的資料結構。然後把三個引數%1,%2,%3按一定規則
自動填充到這個結構中。這個比較簡單,沒有什麼好說的。
對於[section .gdt],其中第乙個descriptor是乙個佔位符,科學的說法是陣列的啞元(dummy ),用來作
為下面計算selector的基位址,下面會詳細說明。
很多個段描述符定義在一起,是否可以不連續呢?
當然可以,比如:
label_gdt:descriptor 0, 0, 0 ; 空描述符
label_desc_code32:descriptor 0, segcode32len - 1, da_c + da_32 ; 非一致**段, 32
other_label:dw 0;
label_desc_video:descriptor 0b8000h, 0ffffh, da_drw; 視訊記憶體首位址
但是,這樣你就要通過一種複雜的(心算?)方式計算出每個描述符在gdt表中的索引,然後很難用
一種自動的方式計算每個選擇子中的索引值,你要為個每個選擇子中索引位置手工填充它們的值。
選擇子到底是什麼?這一點作者說的一點也不清楚。
「在保護模式下,位址仍然用seg:offset這樣的形式來表示,但這時的段值是對段描述符的索引」,
那麼這個索引如何放到cs和ds中呢?在沒有進行頁管理之前,段基址的實體地址如何傳給ds,cs?
當然我們手工(心算)是可以的,gdt表中0是空著的,第乙個描述符label_desc_code32的索引是8
所以初始化cs時可以直接傳8作為索引部分(注意屬性部分沒有談),第二個描述符是8+2+8,因為中
間我們多了乙個other_label.所以我們可以直接把0x12(18)作為索引值傳給gs.但這是你在心算,
在複雜情況下,並不是每個人都有這樣心算的能力的。
選擇子其實就是表示對應的描述符在gdt表中的索引和屬性的乙個結構,或者說是乙個指標,指示它對
對應的描述符在gdt表中的位置。一共有兩個位元組,16位。
如果我們把描述符連續地排列,第0個描述符是乙個空的(浪費了8位元組)作為gdt基位址。那麼下面的
每個描述符對於gdt基位址來說就是8,16,24,32.......這樣的倍數。所以用
label_desc_code32-label_gdt得到的就是乙個偏移(作者非說它不是偏移),這個偏移值就是每個
描述符對於gdt基位址(label_gdt)的偏移,也就是選擇子中索引的值。
因為連續的描述符對於gdt基位址的偏移至少是8的倍數,所以後面3位肯定是0,即
00000000,00001000->8
00000000,00010000->16
00000000,00011000->24
00000000,00100000->32
所以後面三位用不著,我們只需要前面的13位就行了。計算的時候內部左移3就可以把後面的3個0
補上來,這樣在儲存表示的時候就可以省出三位來,簡單說是前13位表示的數字乘8表示描述符相對
gdt基位址的索引(8,16,24,32......),而前13位本身(不乘8)就是描述符在表中第幾位(1,2,3,4...),
而節省下來的3位正好用於表示ti和rpl。
下面gdtptr的定義:
gdtptr dw gdtlen - 1 ; gdt界限
dd 0 ; gdt基位址
gdtptr其實是乙個偽描述符,它的結構是:
dw :16位界限
所以下面的dd 0其實是gdtptr的後四個位元組。
開始進入**:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
因為這時剛開始執行,在實模式下為使各個段共用乙個段基址,所以把cs傳給了ds,es,ss。
sp的初值賦0x100是256,即棧頂的指標。
; 初始化 32 位**段描述符
xoreax, eax
movax, cs
shleax, 4
addeax, label_seg_code32
movword [label_desc_code32 + 2], ax
shreax, 16
movbyte [label_desc_code32 + 4], al
movbyte [label_desc_code32 + 7], ah
第2,3,4行是根據實模式下cs的值計算段首址(左移4就是乘16),加上描述符的相對位址初始化成保護模式下的
段基址。然後把這個位址拆開存幾個部分存到label_desc_code32描述符的對應位置。
xoreax, eax
movax, ds
shleax, 4
addeax, label_gdt; eax <- gdt 基位址
movdword [gdtptr + 2], eax; [gdtptr + 2] <- gdt 基位址
這段作用和上面初始化label_desc_code32一樣,先根據實模式下的ds值計算保護模式下對應的資料段基位址。
然後將gdt基位址傳給gdtptr+2.以供下面的lgdt呼叫,也就是那個無名的dd那個位置。
gdtptr一共有6個位元組,前兩個位元組存的是gdt界限(len-1)後4個位元組就是存的gdt的基址。所以要把這個位址放到
gdtptr + 2的位置。
pmtest2.asm:
[section .s16code]
align 32
[bits 16]
label_seg_code16:
; 跳回實模式:
mov ax, selectornormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, 11111110b
mov cr0, eax
label_go_back_to_real:
jmp 0:label_real_entry ; 段位址會在程式開始處被設定成正確的值
code16len equ $ - label_seg_code16
這段**和在實模式下構造保護模式的段一樣,要在保護模式下構造實模式的段以便跳回實模式.
在由保護模式向實模式跳轉時候,由於每個段暫存器都配有段描述符高速緩衝暫存器,其內容會由保護模式狀態下帶到實模式下,
但其中內容的"格式(其實就是實模式下的段屬性,在實模式下不能手工修改)"是保護模式下的格式,與實模式不匹配,所以要做兩件事:
1.載入乙個16位**的選擇子,即selectornormal,並將這個段的這個段描述符向ds,es,fs,gs,ss複製,其實並在用到這些寄存
器的值,只是為了把這些暫存器的高速緩衝暫存器中的內容重新整理成16位的實模式下的"格式".
因為cs暫存器不能直接填充,所以只能從保護模式的32位**跳轉到16位**由cpu自動去重新整理cs暫存器的高速緩衝暫存器.
jmp selectorcode16:0的作用是jmp [cs:ip],所以selectorcode16描述符被載入到cs段,完成對高速緩衝暫存器的重新整理.
2.因為cs段屬性已經正確,而開始時實模式下cs段的基址被儲存在label_go_back_to_real+3處,
即jmp 0:label_real_entry這條指令的段位址分部,所以jmp 0:label_real_entry可以正確地跳到實模式的label_real_entry處,並且將原來儲存的實模式下的段位址帶到label_real_entry中.
這樣完成了所有的暫存器的高速緩衝暫存器的內容的重新整理成實模式的"格式"後,再跳入實模式的**.所以從保護模式跳回實模式時
一定會借助乙個norma描述符和乙個帶有實模式的cs段位址的jmp指令.
其它的**很直觀,沒有什麼特別繞人的地方。
從實模式到保護模式
計算機啟動之後,bios會初始化計算機,然後計算機開始自動讀取磁碟,磁碟讀取乙個扇區 512位元組 當讀到某個磁碟0磁軌1扇區扇區的最後的結束位址是0xaa55,bios就會認為它是乙個引導扇區,然後就會把這512位元組的內容裝載到記憶體位址0000 7c00處,然後跳轉到0000 7c00處將控制...
保護模式到實模式
弄了幾天了,終於弄好了,呵呵,也得到了不少東西。從保護模式到是模式的轉換一定要注意的有 1 程式開始時在實模式下要有自己的堆疊段,進入保護模式前先暫存ss及sp的值至某記憶體處,以便從保護模式返回實模式後恢復到原先的堆疊。2 返回實模式前需把各段暫存器設定為規範段,包括ss也要設定 3 返回實模式前...
X86組合語言從實模式到保護模式(一)
不對請指正,歡迎交流 單位換算 1 byte 8 bit 1 kb 1024 byte 1 mb 1024 kb 1 gb 1024 mb8位暫存器可以容納8位元 bit 或者說1個位元組 1byte 8bit,8個二進位制數 1111 1111 0xff 16位暫存器可以存放2個位元組,也就是1個...