保護模式下的尋址


在匯編語言中,或者你有學習過諸如微機原理或計算機組成原理等課程的話,那么你很可能聽說過實模式和保護模式的概念。他們到底是什么,有什么不同,又如何尋址?

在王爽的《匯編語言》最后,有關於Intel微處理器的三種工作模式的介紹。

繼Intel 8086推出之后,Intel又推出了划時代的80386微處理器,它可以在實模式、保護模式和虛擬8086模式下工作,從那以后的微處理器都提供了這三種工作模式,直到現在。Intel系列微處理器的三種工作模式如下:

  • 實模式:工作模式相當於一個8086
  • 保護模式:提供支持多任務環境的工作方式,建立保護機制
  • 虛擬8086模式:可以從保護模式切換至其中的一種8086工作方式,這種方式的提供使用戶可以方便的在保護模式下運行一個或多個8086程序

當我們的系統開機時,cpu首先工作在實模式下完成一些工作,之后跳入保護模式,為我們的系統提供多任務環境的支持。而當我們需要在保護模式的系統上運行實模式下的程序時(比如學習匯編時所用的DOS系統),我們就需要在當前的保護模式下弄一個“假”的實模式,這就是虛擬8086模式。

GDT和描述符

在實模式下(可以理解為工作在8086上時),我們的CPU是16位的,提供了16位的寄存器,16位數據總線,20位的地址總線,可尋址范圍位1M。物理地址遵循下面的計算公式:

\[物理地址 = 段地址 * 16 + 偏移地址 \]

其中的段地址和偏移地址都是16位的。

從80386開始,Intel家族的CPU進入了32位時代,這時候CPU有32位的地址總線,所以可尋址范圍為4G。CPU同樣擁有的是32位的寄存器,一個寄存器即可尋址4GB的空間。

在實模式下,我們采用段地址:偏移地址的尋址方式是因為我們只有16為的寄存器,單個寄存器的尋址范圍達不到1MB,但現在我們擁有了32位的寄存器,單個寄存器的可尋址范圍已經可以達到4GB了,那么是不是就不需要段寄存器了?答案是否定的。在保護模式下,地址仍然采用“段地址:偏移地址”的方式來表示,只是段的概念發生了根本性的變化

實模式下,段值(段地址的值)還是地址的一部分。在保護模式下,雖然段值仍然由原來的16位的cs、ds等寄存器表示,但是此時它們僅僅是一個索引,這些個索引指向一個數據結構的表項,表項中詳細定義了一個段的起始地址、界限、屬性等內容,這個數據結構,叫做GDT(其實還可能是LDT,我們先討論大多數情況),GDT中的每一個表項,叫做描述符

尋址過程

我們在來看一下保護模式下的尋址過程。在此之前,有幾點要說明:

  • GDT是一個數據結構,它是保存在內存中的,所以它應該有一個起始地址,它是一系列描述符的集合
  • GDT的起始地址由一個專門的寄存器來存放 -- gdtr,gdtr寄存器是48位的,這個寄存器我們稍后在探討
  • GDT中的每一個描述符描述一個段,其中包括段的起始地址(基址)等屬性
  • 保護模式的偏移地址和實模式下的是相同的,只不過是32位

好了,下面有一張圖,我們可以看着這張圖過一遍保護模式下是如何尋址的。

  1. 尋址時,先找到gdtr寄存器,從中得到GDT的基址
  2. 有了GDT的基址,又有段寄存器中保存的索引,可以得到段寄存器“所指”的那個表項,既所指的那個描述符
  3. 得到了描述符,就可以從描述符中得到該描述符所描述的那個段的起始地址
  4. 有了段的起始地址,將偏移地址拿過來與之相加,便能得到最后的線性地址
  5. 有了線性地址(虛擬地址),經過變換,即可得到相應的物理地址

相信到這里,你已經對尋址過程有了個大概的了解,然后我們看看我們上面所未詳細提及的東西

gdtr寄存器

gdtr是一個48位的寄存器,其中保存了GDT的基地址和界限(或者說GDT的長度),高32位為GDT的基地址,低16位為界限。還記得保護模式中的段寄存器也是16位的嗎,它們和gdtr中的界限是對應的啊。

描述符

GDT中的每個描述符占8個字節,其結構如下

我們可以不用管其中的屬性,僅看段基址和段界限。是不是和上面的尋址聯系上了呢。

你可能會問,問什么段基址和段界限都被分開了,卻不放在一起?這主要還是歷史遺留問題,我們就不在探討了。

代碼

光看理論終究還是水中月,我們看一段簡單的代碼實際體會一下。

[SECTION .gdt]
; 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	     ; 顯存首地址
; GDT 結束

GdtLen		equ	$ - LABEL_GDT	; GDT長度
GdtPtr		dw	GdtLen - 1	; GDT界限
		dd	0		; GDT基地址

; GDT 選擇子
SelectorCode32		equ	LABEL_DESC_CODE32	- LABEL_GDT
SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT

上面的代碼中,我們定義了一個角.gdt的段,其中前三個LABLE_xxx后是我們用一個叫Descriptor宏定義了三個選擇子,其中的數值並不一定正確,因為我們只是定義了,還並沒有初始化。 Descriptor的作用是將段基址、段界限和屬性放在一個選擇子中相應的位置,其定義在文章末尾,感興趣的話可以看下。

GdtPtr是不是和gdtr中所放的內容一樣呢?沒錯,當我們在實模式進入保護模式之前,我們需要將GdtPtr的值加載到gdtr寄存器:使用指令lgdt [GdtPtr]

那最后兩個GDT選擇子又是什么呢?好像是描述符相對於GDT基地址的偏移,其實並不全對,它稍稍復雜一些,如下圖所示。

其中TI和RPL是選擇子的一些屬性,剩下的高13位表示的是描述符在描述符表的位置,即GDT中第幾個描述符

最后,我們看一下如何使用上面的東西吧

[SECTION .s32]; 32 位代碼段. 
[BITS	32]

LABEL_SEG_CODE32:
	mov	ax, SelectorVideo
	mov	gs, ax			; 視頻段選擇子(目的)

	mov	edi, (80 * 11 + 79) * 2	; 屏幕第 11 行, 第 79 列。
	mov	ah, 0Ch			; 0000: 黑底    1100: 紅字
	mov	al, 'P'
	mov	[gs:edi], ax

	; 到此停止
	jmp	$

SegCode32Len	equ	$ - LABEL_SEG_CODE32

上述代碼將一個字母P顯示在屏幕上。gs中保存的是顯存的選擇子,edi為偏移地址,然后使用mov [gs:edi], ax將ax的內容寫入到地址為gs所指的描述符中的段基址+edi的內存處,由於這里寫入的是顯存,所以將會將一個字母P顯示在屏幕上。

Descriptor宏的定義如下

; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
	dw	%2 & 0FFFFh				; 段界限1
	dw	%1 & 0FFFFh				; 段基址1
	db	(%1 >> 16) & 0FFh			; 段基址2
	dw	((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)	; 屬性1 + 段界限2 + 屬性2
	db	(%1 >> 24) & 0FFh			; 段基址3
%endmacro ; 共 8 字節

參考:

  • 《匯編語言》 王爽
  • 《一個操作系統的實現》 於淵


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM