Linux中的邏輯位址,線性位址和實體地址轉換關係

2021-06-27 01:29:03 字數 4174 閱讀 5908

一、邏輯位址轉線性位址

機器語言指令中出現的記憶體位址,都是邏輯位址,需要轉換成線性位址,再經過

mmu(cpu

中的記憶體管理單元

)轉換成實體地址才能夠被訪問到。

我們寫個最簡單的

hello world

程式,用

gcc編譯,再反彙編後會看到以下指令:

mov    0x80495b0, %eax

這裡的記憶體位址

0x80495b0 

就是乙個邏輯位址,必須加上隱含的

ds 資料段的基位址,才能構成線性位址。也就是說

0x80495b0 

是當前任務的

ds資料段內的偏移。 在

x86保護模式下,段的資訊(段基線性位址、長度、許可權等)即段描述符佔8

個位元組,段資訊無法直接存放在段暫存器中(段暫存器只有

2位元組)。

intel

的設計是段描述符集中存放在

gdt或

ldt中,而段暫存器存放的是段描述符在gdt

或ldt

內的索引值(index)。

linux

中邏輯位址等於線性位址。為什麼這麼說呢?因為

linux

所有的段(使用者**段、使用者資料段、核心**段、核心資料段)的線性位址都是從

0x00000000 

開始,長度

4g,這樣

線性位址

=邏輯位址

+ 0x00000000

,也就是說邏輯位址等於線性位址了。

這樣的情況下linux

只用到了

gdt,不論是使用者任務還是核心任務,都沒有用到

ldt。

gdt的第12和

13項段描述符是

__kernel_cs 

和__kernel_ds

,第14和15

項段描述符是

__user_cs 

和__user_ds

。核心任務使用

__kernel_cs 

和__kernel_ds

,所有的使用者任務共用

__user_cs 

和__user_ds

,也就是說不需要給每個任務再單獨分配段描述符。核心段描述符和使用者段描述符雖然起始線性位址和長度都一樣,但

dpl(

描述符特權級

)是不一樣的。

__kernel_cs 

和__kernel_ds 

的dpl值為0

(最高特權),

__user_cs 

和__user_ds

的dpl值為3

。 用gdb

除錯程式的時候,用

info reg 

顯示當前暫存器的值:

cs             0x73     115

ss             0x7b     123

ds             0x7b     123

es             0x7b     123

可以看到

ds值為

0x7b, 

轉換成二進位制為

00000000 01111011,ti

字段值為

0,表示使用

gdt,

gdt索引值為

01111

,即十進位制

15,對應的就是

gdt內的

__user_ds

使用者資料段描述符。

從上面可以看到,

linux

在x86

的分段機制上執行,卻通過乙個巧妙的方式繞開了分段。

linux

主要以分頁的方式實現記憶體管理。

二、線性位址轉實體地址

前面說了

linux

中邏輯位址等於線性位址,那麼線性位址怎麼對應到實體地址呢?這個大家都知道,那就是通過分頁機制,具體的說,就是通過頁表查詢來對應實體地址。

準確的說分頁是

cpu提供的一種機制,

linux

只是根據這種機制的規則,利用它實現了記憶體管理。

在保護模式下,控制暫存器

cr0的最高位

pg位控制著分頁管理機制是否生效,如果

pg=1

,分頁機制生效,需通過頁表查詢才能把線性位址轉換實體地址。如果

pg=0

,則分頁機制無效,線性位址就直接做為實體地址。

分頁的基本原理是把記憶體劃分成大小固定的若干單元,每個單元稱為一頁(

page

),每頁包含

4k位元組的位址空間(為簡化分析,我們不考慮擴充套件分頁的情況)。這樣每一頁的起始位址都是

4k位元組對齊的。為了能轉換成實體地址,我們需要給

cpu提供當前任務的線性位址轉實體地址的查詢表,即頁表(page table)

。注意,為了實現每個任務的平坦的虛擬記憶體,每個任務都有自己的頁目錄表和頁表

為了節約頁表占用的記憶體空間,

x86將線性位址通過頁目錄表和頁表兩級查詢轉換成實體地址。

32位的線性位址被分成

3個部分:

最高10位

directory 

頁目錄表偏移量,中間10位

table

是頁表偏移量,最低12位

offset

是物理頁內的位元組偏移量。

頁目錄表的大小為

4k(剛好是乙個頁的大小),包含

1024

項,每個項

4位元組(

32位),專案裡儲存的內容就是頁表的實體地址。如果頁目錄表中的頁表尚未分配,則實體地址填0。

頁表的大小也是

4k,同樣包含

1024

項,每個項

4位元組,內容為最終物理頁的物理記憶體起始位址。

每個活動的任務,必須要先分配給它乙個頁目錄表,並把頁目錄表的實體地址存入

cr3暫存器。頁表可以提前分配好,也可以在用到的時候再分配。

還是以mov    0x80495b0, %eax 中的位址為例分析一下線性位址轉實體地址的過程。

前面說到

linux

中邏輯位址等於線性位址,那麼我們要轉換的線性位址就是

0x80495b0

。轉換的過程是由

cpu自動完成的,

linux

所要做的就是準備好轉換所需的頁目錄表和頁表(假設已經準備好,給頁目錄表和頁表分配物理記憶體的過程很複雜,後面再分析)。

核心先將當前任務的頁目錄表的實體地址填入

cr3暫存器。

線性位址

0x80495b0 

轉換成二進位制後是

0000 1000 0000 0100 1001 0101 1011 0000

,最高10

位0000 1000 00

的十進位制是32,

cpu檢視頁目錄表第

32項,裡面存放的是頁表的實體地址。線性位址中間10位

00 0100 1001 

的十進位制是

73,頁表的第

73項儲存的是最終物理頁的物理起始位址。物理頁基位址加上線性位址中最低

12位的偏移量,

cpu就找到了線性位址最終對應的物理記憶體單元。

我們知道

linux

中使用者程序線性位址能定址的範圍是0 -

3g,那麼是不是需要提前先把這

3g虛擬記憶體的頁表都建立好呢?一般情況下,物理記憶體是遠遠小於

3g的,加上同時有很多程序都在執行,根本無法給每個程序提前建立

3g的線性位址頁表。

linux

利用cpu

的乙個機制解決了這個問題。程序建立後我們可以給頁目錄表的表項值都填0,

cpu在查詢頁表時,如果表項的內容為

0,則會引發乙個缺頁異常,程序暫停執行,

linux

核心這時候可以通過一系列複雜的演算法給分配乙個物理頁,並把物理頁的位址填入表項中,程序再恢復執行。當然程序在這個過程中是被蒙蔽的,它自己的感覺還是正常訪問到了物理記憶體。

邏輯位址,線性位址,實體地址

邏輯位址轉線性位址 機器語言指令中出現的記憶體位址,都是邏輯位址,需要轉換成線性位址,再經過 mmu cpu中的記憶體管理單元 轉換成實體地址才能夠被訪問到。我們寫個最簡單的 hello world 程式,用 gcc 編譯,再反彙編後會看到以下指令 mov 0x80495b0 eax 這裡的記憶體位...

虛擬位址 線性位址 邏輯位址

邏輯位址 logical address 是指由程式產生的與段相關的偏移位址部分 例如,你在進行c語言指標程式設計中,可以讀取指標變數本身值 操作 實際上這個值就是邏輯位址 它是相對於你當前程序資料段的位址,不和絕對實體地址相干 只有在intel實模式下,邏輯位址才和實體地址相等 因為實模式沒有分段...

實體地址 虛擬位址 邏輯位址 線性位址

實際計算機的物理記憶體的位址,為32位或者64位。常見的記憶體條就是一類ram 隨機儲存儲存器,特點就是加電狀態下可任意讀寫,斷電後資訊消失 現代os都提供一技術 虛擬記憶體 virtual memory 它可以使給使用者錯覺好像自己在使用比實際物理記憶體大得多的記憶體,實際上通過對映把虛擬記憶體的...