linux內核中 邏輯地址、虛擬地址、線性地址和物理地址大掃盲

關注”技術簡說“(b站同名),帶你由淺入深學習linux內核源碼。linux內核開發100講免費教程,每天晚上9點準時更新,敬請收看。進我主頁點”視頻“欄目即可觀看。

很多講linux內核的書裡面都提到過這四個概念:邏輯地址、虛擬地址、線性地址和物理地址。物理地址比較好澄清,但是這些書裡都沒有明確的講清楚所謂的邏輯地址、虛擬地址、線性地址的區別到底是什麼?

那本文就來個大掃除吧。

中英文對應關係:

邏輯地址 --- logical address;

虛擬地址 --- virtual address;

線性地址 --- linear address;

物理地址 --- physical address;

這四個地址是體系相關的,我以x86 cpu為例進行解釋。

一、名詞解釋

先放張圖吧

linux內核中 邏輯地址、虛擬地址、線性地址和物理地址大掃盲

x86 cpu 段頁式內存管理機制

1.左上角的Logical Address,就是我們所說的邏輯地址。

邏輯地址,是由一個段選擇符加上一個指定段內相對地址的偏移量(Offset)組成的,表示為 [段選擇符:段內偏移量],例如:[CS:EIP]

2.虛擬地址,其實就是如上邏輯地址的段內偏移Offset。所以:

邏輯地址可以表示為 [段標識符:虛擬地址]

驅動代碼或者應用程序中所用的地址就是虛擬地址,比如以下程序中指針p的輸出:

<code> 
 

void

main

(

void

)

{

int

*p; p = (

int

*)

malloc

(

sizeof

(

int

));

printf

(

"addres is %p\n"

, p);

free

(p);

return

; }/<code>

3.Linear Address, 也就是我們所說的線性地址

線性地址是平坦的統一地址空間。

intel x86 中,線性地址是由邏輯地址經過段頁式轉換得到的。

4.最右邊的Physical Address, 也就是我們所說的物理地址。

物理地址就是物理內存的地址。但是注意在做頁錶轉換的時候,這裡存的可不是真正的物理地址,而是物理內存塊的編號。

內核把物理內存按照4K大小編號,考慮k到物理內存的起始地址是固定的,所以從內存編號的序號就可以算出該編號對應的物理內存塊的起始地址了。例如:

物理內存起始地址為0x50000000, 那麼編號為0的物理塊的起始地址為:0x50000000

編號為1的物理塊的起始地址為 0x50001000

以此類推。。。

所以,根據物理內存塊的編號,就可以轉換得到該物理內存塊的起始地址,也叫做物理內存的基地址。瞭解這一點非常重要,因為後續做頁表映射的時候會用到。

二、x86的段頁式內存管理機制

還是上面的那張圖,除了表達了這三個地址之外,我們還可以看出從邏輯地址轉換成最終的物理地址,要經歷兩個過程:

1.邏輯地址轉換為線性地址

在 Intel 平臺下,邏輯地址(logical address)是 selector:offset 這種形式,selector 可以是代碼段或者數據段,offset 是段內偏移。如果用 selector 去 GDT( 全局描述符表 ) 裡拿到 segment base address(段基址) 然後加上 offset(段內偏移),這就得到了 linear address。我們把這個過程稱作段式內存管理

總結來看:邏輯地址轉換為線性地址的詳細過程是這樣的:

(1)先從段選擇符(selector)中得到段描述符;

(2)從段描述符中得到段基地址;

(3)線性地址=上一步得到的段基地址+段內偏移(也就是上文說的虛擬地址)

我們來看看端選擇符(selector)的組成:

linux內核中 邏輯地址、虛擬地址、線性地址和物理地址大掃盲

13位的索引號對應到段描述符表中的位置,而T1字段表示使用的是哪個段描述符表。

Intel設計的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每個進程自己的,就放在所謂的“局部段描述符表(LDT)”中。所以,通過T1字段就可以選擇是使用
GDT,還是使用LDT了。GDT或者LDT裡的內容就是一個一個的段描述符,段描述符的組成如下:

linux內核中 邏輯地址、虛擬地址、線性地址和物理地址大掃盲

這些東東很複雜,不過,我這裡只關心一樣,就是Base字段,它描述了一個段的的基地址。

得到了這個基地址,然後再加上段內偏移offset,就得到了最終的線性地址。

2.線性地址轉換為物理地址

如果再把線性地址切成三段,用前兩段分別作為索引去PGD、Page Table裡查表,會先得到一個頁目錄表項、然後會得到一個頁表項(Page Table Entry),那裡面的值就是一個物理內存塊的起始地址(其實就是是物理內存編號),把它加上 linear address 切分之後第三段的內容(又叫頁內偏移)就得到了最終的 physical address。我們把這個過程稱作頁式內存管理

所以,x86採用的是段頁式內存管理的方式。

三、linux內核中邏輯地址、虛擬地址和線性地址的關係

上一部分提到了x86採用了段頁式內存管理。

按照 Intel 的設計,段式內存管理中的段類型分為三種:代碼段、數據段、系統段(TSS之類的),實在是太麻煩了。我們只靠頁式內存管理就已經可以完成Linux內核需要的所有功能,根本不需要段映射,但是段映射這玩意兒又關不掉,那就只能上點手段了。

於是,Linux內核將所有類型的段的 segment base address 都設成0(包括內核數據段、內核代碼段、用戶數據段、用戶代碼段等)。那麼這樣一來所有段都重合了,也就是不分段了,此外由於段限長是地址總線的尋址限度,所以這也就相當於所有段內空間跟整個線性空間重合了。

這樣邏輯地址也就簡化為了段內的偏移量(邏輯地址=虛擬地址)。

由於段基地址變為了0,那麼線性地址=邏輯地址=虛擬地址。

所以,在x86 linux內核裡,邏輯地址、虛擬地址、線性地址,這是三個地址是一致的。

親愛的小夥伴們,你們搞清楚了嗎?

關注”技術簡說“(b站同名),帶你由淺入深學習linux內核源碼。linux內核開發100講免費教程,每天晚上9點準時更新,敬請收看。進我主頁點”視頻“欄目即可觀看。


分享到:


相關文章: