天天看點

保護模式彙編系列之一 - 初探保護模式

為了後面學習作業系統的需要,從今天開始我要研究x86的彙編了。是以我決定開始總結并連載x86的彙編系列,這是第一篇——初探保護模式。

我假定讀者接觸過16位的彙編語言,并了解彙編語言的基本概念、熟悉8086處理器采用的“段寄存器 * 16 + 偏移位址”的尋址方法。

我們從80386處理器入手。首先,到了80386時代,cpu有了三種運作模式,即實模式、保護模式和虛拟8086模式。

實模式指的是8086cpu的運作模式,不過這是後來提出的概念,在8086時代隻有當時的運作模式,自然也就沒有“實模式”這麼個提法。如果世界上隻有一種性别的人,也就沒有男人,女人這種名稱了。8086的彙編中,我們對于實模式的各種機制應該算是比較了解了,其大緻包括實模式1mb的線性位址空間、記憶體尋址方法、寄存器、端口讀寫以及中斷處理方法等内容。

不過到了80386時代,引進了一種沿用至今的cpu運作機制——保護模式(protected mode)。保護模式有一些新的特色,用來增強多工和系統穩定度,比如記憶體保護,分頁系統,以及硬體支援的虛拟記憶體等。大部分現今基于 x86的作業系統都在保護模式下運作,包括linux、freebsd、以及 微軟 windows 2.0 和之後版本 [都指32位作業系統] 。

虛拟8086模式用于在保護模式下運作原來實模式下的16位程式,我們不關心。

事實上,現在的64位處理器,擁有三種基本模式(保護模式、實模式、系統管理模式)和一種擴充模式(ia-32e模式(又分相容模式和64位模式)) 詳見這裡

我們先來研究保護模式,學校目前基本還處于隻講8086實模式的時代。至于現代cpu的模式……我們有精力再來研究吧。聲明下,我不是在吐槽我們的大學教育,真的。

80386首先擴充了8086的處理器(其實中間有個80286,不過這玩意感覺就是個過渡産品,我們不提了),原先的ax,bx,cx,dx,si,di,sp,bp從16位擴充(extend)到了32位,并改名eax,ebx,ecx,edx,esi,edi,esp,ebp,e就是extend的意思。當然,保留了原先的16位寄存器的使用習慣,就像在8086下能用ah和al通路ax的高低部分一樣,不過eax的低位部分能使用ax直接通路,高位卻沒有直接的方法,隻能通過資料右移16位之後再通路。另外,cs,ds,es,ss這幾個16位段寄存器保留,再增加fs,gs兩個段寄存器。另外還有其它很多新增加的寄存器,我們本着實用原則,到時候用到了我們再說。

我們知道,對cpu來講,系統中的所有儲存器中的儲存單元都處于一個統一的邏輯儲存器中,它的容量受cpu尋址能力的限制。這個邏輯儲存器就是我們所說的線性位址空間。8086有20位位址線,擁有1mb的線性位址空間。而80386有32位位址線,擁有4gb的線性位址空間。但是80386依舊保留了8086采用的位址分段的方式,隻是增加了一個折中的方案,即隻分一個段,段基址0x00000000,段長0xffffffff(4gb),這樣的話整個線性空間可以看作就一個段。這就是所謂的平坦模型(flat mode)。

保護模式彙編系列之一 - 初探保護模式

我們以前就知道,線性位址不僅僅是記憶體位址,還有其它的存儲器編址在裡面。對于80386,在保護模式下如果開啟分頁,記憶體實體位址的通路不一定就是線性位址了,而是需要根據頁映射轉換到實際的實體位址去。我們暫時還談不到分頁,是以我們目前計算出的線性位址就是實體位址。

我們先來看保護模式下的記憶體是如何分段管理的。為了便于了解,我們從一個設計者的角度來研究這個問題,順便試圖按我的了解對一些機制的設計原因做一些闡釋。

首先是對記憶體分段中每一個段的描述,内模式對于記憶體段并沒有通路控制,任意的程式可以修改任意位址的變量,而保護模式需要對記憶體段的性質和允許的操作給出定義,以實作對特定記憶體段的通路檢測和資料保護。考慮到各種屬性和需要設定的操作,32位保護模式下對一個記憶體段的描述需要8個位元組,其稱之為段描述符(segment descriptor)。段描述符分為資料段描述符、指令段描述符和系統段描述符三種,大緻相同,個體差異。

我們現在看一張這資料段8個位元組的分解圖吧,至于為什麼是這樣,以及每一個細節的含義請讀者自行查閱intel文檔,畢竟我寫的不是文檔…

保護模式彙編系列之一 - 初探保護模式

顯然,寄存器不足以存放n多個記憶體段的描述符集合,是以這些描述符的集合(稱之為描述符表)被放置在記憶體裡了。在很多描述符表中,最重要的就是所謂的全局描述符表(global descriptor table,gdt),它為整個軟硬體系統服務。

一個問題解決了,但是又引出了的其他問題。問題一、這些描述符表放置在記憶體哪裡?答案是沒有固定的說法,可以任由程式員安排在任意合适的位置。那麼問題二、既然沒有指定固定位置,cpu如何知道全局描述符表在哪?答案是intel幹脆設定了一個48位的專用的全局描述符表寄存器(gdtr)來儲存全局描述符表的資訊。那這48位怎麼配置設定呢?如圖所示,0-15位表示gdt的邊界位置(數值為表的長度-1,因為從0計算),16-47位這32位存放的就是gdt的基位址(恰似數組的首位址)。

保護模式彙編系列之一 - 初探保護模式

既然用16位來表示表的長度,那麼2的16次方就是65536位元組,除以每一個描述符的8位元組,那麼最多能建立8192個描述符。

貌似說了這麼多,我們一直還沒提cpu的預設工作方式。80386cpu加電的時候自動進入實模式(實際上不是實模式,剛加電的時刻是一個奇葩的混沌模式,具體說明詳見我的另外一篇文章《基于intel 80×86 cpu的ibm pc及其相容計算機的啟動流程》)。既然cpu加電後就一直工作在實模式下了。那怎麼進入保護模式呢?說來也簡單,80386cpu内部有5個32位的控制寄存器(control register,cr),分别是cr0到cr3,以及cr8。用來表示cpu的一些狀态,其中的cr0寄存器的pe位(protection enable,保護模式允許位),0号位,就表示了cpu的運作狀态,0為實模式,1為保護模式。通過修改這個位就可以立即改變cpu的工作模式。

保護模式彙編系列之一 - 初探保護模式

不過需要注意的是,一旦cr0寄存器的pe位被修改,cpu就立即按照保護模式去尋址了,是以這就要求我們必須在進入保護模式之前就在記憶體裡放置好gdt,然後設定好gdtr寄存器。我們知道實模式下隻有1mb的尋址空間,是以gdt就等于被限制在了這裡。即便是再不樂意我們也沒有辦法,隻得委屈就全的先安排在這裡。不過進入保護模式之後我們就可以在4g的空間裡設定并修改原來的gdtr了。

ok,現在有了描述符的數組了,也有了“數組指針”(gdtr)了,怎麼表示我們要通路哪個段呢?還記得8086時代的段寄存器吧?不過此時它們改名字了,叫段選擇器(段選擇子)。此時的cs等寄存器不再儲存段基址了,而是儲存其指向段的索引資訊,cpu會根據這些資訊在記憶體中擷取到段資訊。

我們上一張圖看看整個尋找和合成位址的過程吧:

保護模式彙編系列之一 - 初探保護模式

大緻的尋址我們就先說到這裡,其實有很多細節我們先做了隐藏處理。那麼在接下來的第二篇裡面,我們會對從實模式到保護模式時候的細節再次進行闡述,并給出相關的彙編代碼實作。

繼續閱讀