1)虛擬內存的解釋:
虛擬內存的核心概念是指代碼所用的內存地址與物理地址沒(méi)有關(guān)系.
在用戶(hù)空間中,一個(gè)進(jìn)程的虛擬地址A指向不同的物理內存,而不是另一個(gè)進(jìn)程的地址A.
任何時(shí)候CPU發(fā)送指令向內存存取數據時(shí),通過(guò)軟件將虛擬地址的數據變?yōu)槲锢淼刂?
將虛擬地址變?yōu)槲锢淼刂纷優(yōu)槲锢淼刂返墓ぷ魇怯蓛却婀芾韱卧?MMU)完成的.
虛擬內存地址也可以稱(chēng)為邏輯地址.
2)內存管理單元:
內存管理單元是CPU功能的一部份,如果CPU有cache,它將有一個(gè)內存管理單元,反之亦然.
內存管理單元可以將兩個(gè)進(jìn)程對同一內存邏輯地址的訪(fǎng)問(wèn)映射到不同的物理地址.
內存管理單元同高速緩存密切協(xié)作,在RAM和高速緩存之間按要求傳遞內存.
內存管理單元將內存分成許多頁(yè),它是可利用物理內存的最小單位,每頁(yè)包含4KB字節的地址空間.
3)虛擬內存到物理內存的映射:
3.1)映射的過(guò)程:
虛擬地址到物理地址的轉化是與體系結構相關(guān)的,在X86 CPU上是以分段,分頁(yè)兩種方式轉化的.
虛擬地址(邏輯地址)---段式映射---線(xiàn)性地址---頁(yè)式映射---物理地址
Linux采用段頁(yè)式管理方式是由于intel的X86 CPU的硬件體系結構決定的.這樣的雙重映射本身毫無(wú)必要,在Linux中段式映射不起什么作用.
可以理解為虛擬地址就是線(xiàn)性地址.
通過(guò)以下的程序來(lái)分析虛擬內存到線(xiàn)性地址再到物理內存的映射,我們還以X86為例:
vi hello.c
#include <stdio.h>
int greeting(){
printf("Hello world!\n");
return 0;
}
int
main (){
greeting();
return 0;
}
編寫(xiě)一個(gè)HELLO WORLD程序,用gcc hello.c -o hello編譯
objdump -xd hello
這里我們主要看main和greeting的調用:
08048354 <greeting>:
8048354: 55 push %ebp
8048355: 89 e5 mov %esp,%ebp
8048357: 83 ec 08 sub $0x8,%esp
804835a: c7 04 24 70 84 04 08 movl $0x8048470,(%esp)
8048361: e8 2e ff ff ff call 8048294 <puts@plt>
8048366: b8 00 00 00 00 mov $0x0,%eax
804836b: c9 leave
804836c: c3 ret
0804836d <main>:
804836d: 8d 4c 24 04 lea 0x4(%esp),%ecx
8048371: 83 e4 f0 and $0xfffffff0,%esp
8048374: ff 71 fc pushl 0xfffffffc(%ecx)
8048377: 55 push %ebp
8048378: 89 e5 mov %esp,%ebp
804837a: 51 push %ecx
804837b: 83 ec 04 sub $0x4,%esp
804837e: e8 d1 ff ff ff call 8048354 <greeting>
8048383: b8 00 00 00 00 mov $0x0,%eax
8048388: 83 c4 04 add $0x4,%esp
804838b: 59 pop %ecx
804838c: 5d pop %ebp
804838d: 8d 61 fc lea 0xfffffffc(%ecx),%esp
8048390: c3 ret
函數main()通過(guò)call 8048354 <greeting>調用了greeting函數.
首先可以看到ld給greeting分配的地址是0x08048354,在elf格式的可執行代碼中,ld總是從0x08000000開(kāi)始安排代碼段,對每個(gè)程序都這樣.
而程序在執行時(shí)在物理內存中的實(shí)際位置就要由內核在為其建立內存映射時(shí)臨時(shí)作出安排,具體地址則取決于當時(shí)所分配的物理內存頁(yè)面.這對于我們完全是透明的.
映射機制在程序運行時(shí)就已經(jīng)建立起來(lái)了.
3.2)段式映射
從上例中,調用greeting()函數時(shí),當前的地址是0x08048354,也就是EIP指針寄存器的值,那么CS的值是什么呢?
CS寄存器存放的是段式映射的選擇碼,可以理解為這是一個(gè)索引.
在LINUX中,選擇碼只有4個(gè),也就是說(shuō)只可能是以下4個(gè)其中1個(gè),這4個(gè)選擇碼分別是:
段寄存器類(lèi)型 數值 索引 TI RPL
__KERNEL_CS 0x10 0000 0000 00010 0 00
__KERNEL_DS 0x18 0000 0000 00011 0 00
__USER_CS 0x23 0000 0000 00100 0 11
__USER_DS 0x2B 0000 0000 00101 0 11
與上面的對照:
__KERNEL_CS index=2 TI=0 RPL=0
__KERNEL_DS index=3 TI=0 RPL=0
__USER_CS index=4 TI=0 RPL=3
__USER_DS index=5 TI=0 RPL=3
對選擇碼進(jìn)行解釋說(shuō)明:
1)關(guān)于段寄存器的賦值,依據以下的原則:
CS=__USER_CS
DS=__USER_DS
ES=__USER_DS
SS=__USER_DS
因為我們的程序在用戶(hù)空間中運行,所以無(wú)論是代碼段還是數據段都是__USER_XX
2)關(guān)于TI的值,TI可以是GDT(全局段描述表),也可以是LDT(局部段描述表).
GDT對映的是0
LDT對映的是1
LINUX的TI幾乎都是0,LINUX內核中基本上不使用局部描述表LDT,LDT只是在vm86模式中運行wine以及其它在linux上模擬運行windows
或DOS軟件的程序中才使用.
3)關(guān)于RPL,LINUX只用了0,3兩種級別.
0代表內核進(jìn)程,3代表用戶(hù)進(jìn)程.
通過(guò)以上的分析,我們的程序顯然是用戶(hù)進(jìn)程,所以對映的就是__USER_CS,
最后CS寄存器的值就是0x23,而索引就是4.二進(jìn)制(100)=十進(jìn)制(4)
而在GDT全局描述表中4對映的是什么呢?
我們先來(lái)看看gdt全局描述表:
ENTRY(gdt_table)
.quad 0x0000000000000000 /*NULL desccriptor*/
.quad 0x0000000000000000 /*not used*/
.quad 0x00cf9a000000ffff /*0x10 kernel 4GB code at 0x00000000*/
.quad 0x00cf92000000ffff /*0x18 kernel 4GB code at 0x00000000*/
.quad 0x00cffa000000ffff /*0x23 user 4GB code at 0x00000000*/
.quad 0x00cff2000000ffff /*0x2b user 4GB code at 0x00000000*/
.quad 0x0000000000000000 /*not used*/
.quad 0x0000000000000000 /*not used*/
可以看到索引為4的GDT就是
.quad 0x00cffa000000ffff /*0x23 user 4GB code at 0x00000000*/
現在把這4項描述符展開(kāi):
63-60 59-56 55-52 51-48 47-44 43-40 39-36 35-32 31-28 27-24 23-20 19-16 15-12 11-8 7-4 3-0
Kernel_CS:0x00cf9a000000ffff -->0000 0000 1100 1111 1001 1010 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
Kernel_DS:0x00cf92000000ffff -->0000 0000 1100 1111 1001 0010 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
User_CS: 0x00cffa000000ffff -->0000 0000 1100 1111 1111 1010 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
User_DS: 0x00cff2000000ffff -->0000 0000 1100 1111 1111 0010 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
以下對描述符各位進(jìn)行解析:
描述符格式如下:
63-56位存放的是基地址31-24位,基地址都為0
55位也叫G位,在LINUX中都為1,等于1時(shí)段長(cháng)以4k字節為單位,等于0時(shí)以字節為單位
54位也叫D位,在LINUX中都為1,等于1表示對該段的訪(fǎng)問(wèn)為32位指令,等于0為16位指令
53位等于0
52位,CPU忽略該位,可由軟件使用.
51-48位存放的是段地址上限19-16位,都是1
47位也叫P位,在LINUX中都是1,表示4個(gè)段都在內存中.
46-45位是DPL位,表示特權級別.分別有00(0級)和11(3級)兩種組合.
44位也叫S位,等于1時(shí)表示一般的代碼段或數據段,等于0時(shí)表示用于系統管理的系統段,如各類(lèi)描述表.
43-41位叫做type位,因為各位之間有著(zhù)緊密聯(lián)系:
43位也叫E位,等于1時(shí)表示代碼段,這時(shí)第42位叫C位,C位等于0時(shí)會(huì )忽視特權級別,C位等于1時(shí)會(huì )依照特權級別.這時(shí)41位叫R位,等于1時(shí)為可讀,為0時(shí)不可讀.
43位等于0時(shí)表示數據段,這時(shí)第42位叫ED位,ED位等于0時(shí)向上伸(數據段),ED位等于1時(shí)向下伸(堆棧段),這時(shí)41位叫W位,等于1時(shí)為可寫(xiě),為0時(shí)不可寫(xiě).
40位叫A位,在LINUX中都是1,表示以被訪(fǎng)問(wèn)過(guò).
39-16位存放的是基地址23到0位,基地址都為0.
15-0位存放的是段地址上限15-0位,都是1
結論:每個(gè)段都是從0地址開(kāi)始的整個(gè)4GB虛存空間,虛地址到線(xiàn)線(xiàn)地址的映射保持原值不變.
因此,LINUX內核的頁(yè)式映射,可以直接將線(xiàn)性地址當作虛擬地址.二者完全一致.
3.3)頁(yè)式映射
3.3.1)頁(yè)式映射的概念:
1)在I386 CPU中頁(yè)式存儲的基本思路是:通過(guò)頁(yè)面目錄和頁(yè)面表分兩個(gè)層次實(shí)現從線(xiàn)性地址到物理地址的映射.
2)在LINUX中要考慮到各種不同的CPU,它以一種假想的,虛擬的CPU和MMU為基礎,設計出一種通用的模型,再把它分別落實(shí)到各種具體的CPU上.
因此,LINUX內核的映射機制設計成三層,在頁(yè)面目錄和頁(yè)面表中間增設了一層"中間目錄".
邏輯上的三層映射對于i386 CPU和MMU就變成了二層映射,把中間目錄PMD這一層跳過(guò)了,但是軟件的結構卻還保持著(zhù)三層映射的框架.
3)頁(yè)面目錄稱(chēng)為PGD,中間目錄稱(chēng)為PMD,頁(yè)面表則稱(chēng)為PT.PT的表項則稱(chēng)為PTE.
頁(yè)面目錄,中間目錄,頁(yè)目表三者均為數組.
4)邏輯上把線(xiàn)性地址分成4個(gè)段位,分別用在頁(yè)面目錄PGD的偏移,中間目錄PMD中的偏移,頁(yè)表PT中的偏移以及物理頁(yè)面內的偏移,而如果是I386的CPU則沒(méi)有中間目錄.
也就是被分成3個(gè)段位,分別是頁(yè)面目錄PGD的偏移,頁(yè)表pt中的偏移以及物理頁(yè)內的偏移量.
5)每個(gè)進(jìn)程都有自己的頁(yè)目錄表和頁(yè)表,進(jìn)程的切換就是將當前進(jìn)程的頁(yè)目錄表起始地址保存到CR3寄存器.
3.3.2)線(xiàn)性地址到物理地址的映射:
1)將一個(gè)進(jìn)程的頁(yè)面目錄起始地址裝入寄存器CR3.
2)用線(xiàn)性地址的第1個(gè)段位即PGD的偏移,找到頁(yè)面表的物理地址.頁(yè)目錄表的大小為4k,剛好一個(gè)頁(yè)的大小,包含1024項,每項4個(gè)字節(32位)
3)用線(xiàn)性地址的第2個(gè)段位即PT的偏移,找到表項,頁(yè)面表的大小也是4k,同樣包含1024項,每項4個(gè)字節(32位)
4)得到表項的高20位+低12位0組成,這個(gè)高20就是物理地址的高20位,再加上線(xiàn)性地址的第3段位即12位的偏移就得到了最終的物理地址.
3.3.3)用實(shí)例來(lái)說(shuō)明映射的過(guò)程:
第一步:通過(guò)頁(yè)目錄表找到頁(yè)面表
還是以上面的程序為例:
hello程序執行后,調用函數grreeting,這里的虛擬地址也就是線(xiàn)性地址為0x08048354
call 08048354 <greeting>
分解后的結果是:
0000 1000 0000 0100 1000 0011 0101 0100
第1個(gè)段位(高10位):
0000 1000 00
對映十進(jìn)制的32,也就是在頁(yè)目錄表的偏移32找到其頁(yè)面表的物理地址,也就是頁(yè)面表的指針,它的低12位是0,因為頁(yè)面表是4KB大小,所以肯定是邊界對齊了.
第二步:通過(guò)頁(yè)面表找到頁(yè)的起始物理地址高
接下來(lái)是線(xiàn)性地址的第二個(gè)段位(中間10位):
00 0100 10 00
對映十進(jìn)制的72,也就是在剛才找到的頁(yè)面表的偏移72找到目標頁(yè)的起始物理地址,高20位有效的地址,低12位填充為0.
第三步:得到最終的物理地址
通過(guò)找到的頁(yè)起始物理地址,加上線(xiàn)性地址的第三個(gè)段位的偏移地址得到最終的物理地址.
例如:
第三個(gè)段位:0011 0101 0100
對映16進(jìn)制為0x354
如果目標頁(yè)的起始物理地址為:0x740000,那么最終的物理地址就是:
0x740000+0x354=0x740354
本文來(lái)自CSDN博客,轉載請標明出處:http://blog.csdn.net/wishfly/archive/2010/05/21/5613931.aspx
本文來(lái)自CSDN博客,轉載請標明出處:http://blog.csdn.net/hshl1214/archive/2010/08/09/5799690.aspx