Linux 記憶體定址之分段機制

2021-07-30 00:08:34 字數 4513 閱讀 4107

最近在學習linux核心,讀到《深入理解linux核心》的記憶體定址一章。原本以為自己對分段分頁機制已經理解了,結果發現其實是一知半解。於是,查詢了很多資料,最終理順了記憶體定址的知識。現在把我的理解記錄下來,希望對核心學習者有一定幫助,也希望大家指出錯誤之處。

相信學過作業系統課程的人都知道分段分頁,但是奇怪的是書上基本沒提分段分頁是怎麼產生的,這就導致我們知其然不知其所以然。下面我們先扒一下分段機制產生的歷史。

在8086處理器誕生之前,記憶體定址方式就是直接訪問實體地址。8086處理器為了定址1m的記憶體空間,把位址匯流排擴充套件到了20位。但是,乙個尷尬的問題出現了,alu的寬度只有16位,也就是說,alu不能計算20位的位址。為了解決這個問題,分段機制被引入,登上了歷史舞台。

為了支援分段,8086處理器設定了四個段暫存器:cs, ds, ss, es.每個段暫存器都是16位的,同時訪問記憶體的指令中的位址也是16位的。但是,在送入位址匯流排之前,cpu先把它與某個段暫存器內的值相加。這裡要注意:段暫存器的值對應於20位位址匯流排的中的高16位,所以相加時實際上是16位記憶體位址(即段內偏移值)的高12位與段暫存器中的16位相加,而低4位保留不變,這樣就形成乙個20位的實際位址,也就實現了從16位記憶體位址到20位實際位址的轉換,或者叫「對映」。

上面關於分段機制計算記憶體位址的描述比較難理解,畫了乙個圖幫助理解

+-----------------+

| 20 | 20位位址匯流排

+-----------------+

+------------+

| 16 | 16位段位址

+------------+

+-------------+

| 12 | 4 | 16位記憶體位址(段內偏移量)

+--------+----+

實際實體地址 = (段暫存器位址

80286處理器的位址匯流排為24位,定址空間達16m,同時引入了保護模式(記憶體段的訪問受到限制)

80386處理器是乙個32位處理器,alu和位址匯流排都是32位的,定址空間達 4g。也就是說它可以不通過分段機制,直接訪問4g的記憶體空間。雖然它是新時代的小王子,超越它的無數前輩,然而,它需要揹負家族的使命–相容前代的處理器。也就是說,它必須支援實模式和保護模式。所以,80386在段暫存器的基礎上構築保護模式,並且保留16位的段暫存器。

從80386之後的處理器,架構基本相似,統稱為ia32(32 bit intel architecture)。

在 8086 的實模式下,把某一段暫存器左移4位,然後與位址addr相加後被直接送到記憶體匯流排上,這個相加後的位址就是記憶體單元的實體地址,而程式中的這個位址就叫邏輯位址(或叫虛位址)。在ia32的保護模式下,這個邏輯位址不是被直接送到記憶體匯流排而是被送到記憶體管理單元(mmu)。mmu由乙個或一組晶元組成,其功能是把邏輯位址對映為實體地址,即進行位址轉換,如圖所示。

機器語言指令仍用這種位址指定乙個運算元的位址或一條指令的位址。 這種定址方式在intel的分段結構中表現得尤為具體,它使得ms-dos或windows程式設計師把程式分為若干段。每個邏輯位址都由乙個段和偏移量組成。

線性位址是乙個32位的無符號整數,可以表達高達232(4gb)的位址。通常用16進製表示線性位址,其取值範圍為0x00000000~0xffffffff。

也就是記憶體單元的實際位址,用於晶元級記憶體單元定址。 實體地址也由32位無符號整數表示。

mmu是一種硬體電路,它包含兩個部件,乙個是分段部件,乙個是分頁部件,在此,我們把它們分別叫做分段機制和分頁機制,以利於從邏輯的角度來理解硬體的實現機制。分段機制把乙個邏輯位址轉換為線性位址;接著,分頁機制把乙個線性位址轉換為實體地址。

ia32中有六個16位段暫存器:cs, ds, ss, es,fs, gs.跟8086的段暫存器不同的是,這些暫存器存放的不再是某個段的基位址,而是某個段的選擇符(selector)。

段是虛擬位址空間的基本單位,分段機制必須把虛擬位址空間的乙個位址轉換為線性位址空間的乙個線性位址。

段的保護屬性(attribute):表示段的特性。例如,該段是否可被讀出或寫入,或者該段是否作為乙個程式來執行,以及段的特權級等等。

上面的資料結構我們稱為段描述符,多個段描述符組成的表稱為段描述符表

所謂描述符(descriptor),就是描述段的屬性的乙個8位元組儲存單元。在實模式下,段的屬性不外乎是**段、堆疊段、資料段、段的起始位址、段的長度等等,而在保護模式下則複雜一些。ia32將它們結合在一起用乙個8位元組的數表示,稱為描述符 。

從圖可以看出,乙個段描述符指出了段的32位基位址和20位段界限(即段長)。這裡我們只關注基位址和段界限,其他的屬性略過。

各種各樣的使用者描述符和系統描述符,都放在對應的全域性描述符表、區域性描述符表和中斷描述符表中。描述符表(即段表)定義了ia32系統的所有段的情況。所有的描述符表本身都佔據乙個位元組為8的倍數的儲存器空間,空間大小在8個位元組(至少含乙個描述符)到64k位元組(至多含8k)個描述符之間。

全域性描述符表(gdt)

全域性描述符表gdt(global descriptor table),除了任務門,中斷門和陷阱門描述符外,包含著系統中所有任務都共用的那些段的描述符。 它的第乙個8位元組位置沒有使用。

中斷描述符表idt(interrupt descriptor table)

中斷描述符表idt(interrupt descriptor table),包含256個門描述符。idt中只能包含任務門、中斷門和陷阱門描述符,雖然idt表最長也可以為64k位元組,但只能訪問2k位元組以內的描述符,即256個描述符,這個數字是為了和8086保持相容。

區域性描述符表(ldt)

區域性描述符表ldt(local descriptor table),包含了與乙個給定任務有關的描述符,每個任務各自有乙個的ldt。 有了ldt,就可以使給定任務的**、 資料與別的任務相隔離。每乙個任務的區域性描述符表ldt本身也用乙個描述符來表示,稱為ldt描述符,它包含了有關區域性描述符表的資訊,被放在全域性描述符表gdt中。

ia32的記憶體定址機制完成從邏輯位址–線性位址–實體地址的轉換。其中,邏輯位址的段暫存器中的值提供段描述符,然後從段描述符中得到段基址和段界限,然後加上邏輯位址的偏移量,就得到了線性位址,線性位址通過分頁機制得到實體地址。

首先,我們要明確,分段機制是ia32提供的定址方式,這是硬體層面的。就是說,不管你是windows還是linux,只要使用ia32的cpu訪問記憶體,都要經過mmu的轉換流程才能得到實體地址,也就是說必須經過邏輯位址–線性位址–實體地址的轉換。

前面說了那麼多關於分段機制的實現,其實,對於linux來說,並沒有什麼卵用。因為,linux基本不使用分段的機制,或者說,linux中的分段機制只是為了相容ia32的硬體而設計的。

intel微處理器的段機制是從8086開始提出的, 那時引入的段機制解決了從cpu內部16位位址到20位實位址的轉換。為了保持這種相容性,386仍然使用段機制,但比以前複雜得多。因此,linux核心的設計並沒有全部採用intel所提供的段方案,僅僅有限度地使用了一下分段機制。這不僅簡化了linux核心的設計,而且為把linux移植到其他平台創造了條件,因為很多risc處理器並不支援段機制。但是,對段機制相關知識的了解是進入linux核心的必經之路。

從2.2版開始,linux讓所有的程序(或叫任務)都使用相同的邏輯位址空間,因此就沒有必要使用區域性描述符表ldt。但核心中也用到ldt,那只是在vm86模式中執行wine,因為就是說在linux上模擬執行winodws軟體或dos軟體的程式時才使用。

在 ia32 上任意給出的位址都是乙個虛擬位址,即任意乙個位址都是通過「選擇符:偏移量」的方式給出的,這是段機制存訪問模式的基本特點。所以在ia32上設計作業系統時無法迴避使用段機制。乙個虛擬位址最終會通過「段基位址+偏移量」的方式轉化為乙個線性位址。 但是,由於絕大多數硬體平台都不支援段機制,只支援分頁機制,所以為了讓 linux 具有更好的可移植性,我們需要去掉段機制而只使用分頁機制。但不幸的是,ia32規定段機制是不可禁止的,因此不可能繞過它直接給出線性位址空間的位址。萬般無奈之下,linux的設計人員乾脆讓段的基位址為0,而段的界限為4gb,這時任意給出乙個偏移量,則等式為「0+偏移量=線性位址」,也就是說「偏移量=線性位址」。

另外,由於ia32段機制還規定,必須為**段和資料段建立不同的段,所以linux必須為**段和資料段分別建立乙個基位址為0,段界限為4gb的段描述符。不僅如此,由於linux核心執行在特權級0,而使用者程式執行在特權級別3,根據ia32段保護機制規定,特權級3的程式是無法訪問特權級為0的段的,所以linux必須為核心使用者程式分別建立其**段和資料段。這就意味著linux必須建立4個段描述符——特權級0的**段和資料段,特權級3的**段和資料段。

分段機制是ia32架構cpu的特色,並不是作業系統定址方式的必然選擇。linux為了跨平台,巧妙的繞開段機制,主要使用分頁機制來定址。

參考資料

《深入分析linux核心原始碼》

Linux記憶體定址之分段機制

最近在學習linux核心,讀到 深入理解linux核心 的記憶體定址一章。原本以為自己對分段分頁機制已經理解了,結果發現其實是一知半解。於是,查詢了很多資料,最終理順了記憶體定址的知識。現在把我的理解記錄下來,希望對核心學習者有一定幫助,也希望大家指出錯誤之處。相信學過作業系統課程的人都知道分段分頁...

Linux記憶體定址 分段機制

一 實體地址 虛擬位址和線性位址 將主機板上的物理記憶體條所提供的記憶體空間定義為物理記憶體空間,其中每個記憶體單元的實際位址是實體地址 將應用程式設計師看到的記憶體空間定義為虛擬位址空間,其中的位址是虛擬位址。線性位址空間是指一段連續的,不分段的,範圍從0到4gb的位址空間,乙個線性位址就是線性位...

Linux記憶體定址之分頁機制

記憶體定址之分頁機制 分頁機制在段機制之後進行,以完成線性 實體地址的轉換過程。段機制把邏輯位址轉換為線性位址,分頁機制進一步把該線性位址再轉換為實體地址。80386使用4k位元組大小的頁。每一頁都有4k位元組長,並在4k位元組的邊界上對齊,即每一頁的起始位址都能被4k整除。因此,80386把4g位...