1)虛擬內(nèi)存的解釋: 虛擬內(nèi)存的核心概念是指代碼所用的內(nèi)存地址與物理地址沒有關(guān)系. 在用戶空間中,一個(gè)進(jìn)程的虛擬地址A指向不同的物理內(nèi)存,而不是另一個(gè)進(jìn)程的地址A. 任何時(shí)候CPU發(fā)送指令向內(nèi)存存取數(shù)據(jù)時(shí),通過軟件將虛擬地址的數(shù)據(jù)變?yōu)槲锢淼刂? 將虛擬地址變?yōu)槲锢淼刂纷優(yōu)槲锢淼刂返墓ぷ魇怯蓛?nèi)存管理單元(MMU)完成的. 虛擬內(nèi)存地址也可以稱為邏輯地址. 2)內(nèi)存管理單元: 內(nèi)存管理單元是CPU功能的一部份,如果CPU有cache,它將有一個(gè)內(nèi)存管理單元,反之亦然. 內(nèi)存管理單元可以將兩個(gè)進(jìn)程對(duì)同一內(nèi)存邏輯地址的訪問映射到不同的物理地址. 內(nèi)存管理單元同高速緩存密切協(xié)作,在RAM和高速緩存之間按要求傳遞內(nèi)存. 內(nèi)存管理單元將內(nèi)存分成許多頁,它是可利用物理內(nèi)存的最小單位,每頁包含4KB字節(jié)的地址空間. 3)虛擬內(nèi)存到物理內(nèi)存的映射: 3.1)映射的過程: 虛擬地址到物理地址的轉(zhuǎn)化是與體系結(jié)構(gòu)相關(guān)的,在X86 CPU上是以分段,分頁兩種方式轉(zhuǎn)化的. 虛擬地址(邏輯地址)---段式映射---線性地址---頁式映射---物理地址 Linux采用段頁式管理方式是由于intel的X86 CPU的硬件體系結(jié)構(gòu)決定的.這樣的雙重映射本身毫無必要,在Linux中段式映射不起什么作用. 可以理解為虛擬地址就是線性地址. 通過以下的程序來分析虛擬內(nèi)存到線性地址再到物理內(nèi)存的映射,我們還以X86為例: vi hello.c #include <stdio.h> int greeting(){ printf("Hello world!\n"); return 0; } int main (){ greeting(); return 0; } 編寫一個(gè)HELLO WORLD程序,用gcc hello.c -o hello編譯 objdump -xd hello 這里我們主要看main和greeting的調(diào)用: 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 函數(shù)main()通過call 8048354 <greeting>調(diào)用了greeting函數(shù). 首先可以看到ld給greeting分配的地址是0x08048354,在elf格式的可執(zhí)行代碼中,ld總是從0x08000000開始安排代碼段,對(duì)每個(gè)程序都這樣. 而程序在執(zhí)行時(shí)在物理內(nèi)存中的實(shí)際位置就要由內(nèi)核在為其建立內(nèi)存映射時(shí)臨時(shí)作出安排,具體地址則取決于當(dāng)時(shí)所分配的物理內(nèi)存頁面.這對(duì)于我們完全是透明的. 映射機(jī)制在程序運(yùn)行時(shí)就已經(jīng)建立起來了. 3.2)段式映射 從上例中,調(diào)用greeting()函數(shù)時(shí),當(dāng)前的地址是0x08048354,也就是EIP指針寄存器的值,那么CS的值是什么呢? CS寄存器存放的是段式映射的選擇碼,可以理解為這是一個(gè)索引. 在LINUX中,選擇碼只有4個(gè),也就是說只可能是以下4個(gè)其中1個(gè),這4個(gè)選擇碼分別是: 段寄存器類型 數(shù)值 索引 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 與上面的對(duì)照: __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 對(duì)選擇碼進(jìn)行解釋說明: 1)關(guān)于段寄存器的賦值,依據(jù)以下的原則: CS=__USER_CS DS=__USER_DS ES=__USER_DS SS=__USER_DS 因?yàn)槲覀兊某绦蛟谟脩艨臻g中運(yùn)行,所以無論是代碼段還是數(shù)據(jù)段都是__USER_XX 2)關(guān)于TI的值,TI可以是GDT(全局段描述表),也可以是LDT(局部段描述表). GDT對(duì)映的是0 LDT對(duì)映的是1 LINUX的TI幾乎都是0,LINUX內(nèi)核中基本上不使用局部描述表LDT,LDT只是在vm86模式中運(yùn)行wine以及其它在linux上模擬運(yùn)行windows 或DOS軟件的程序中才使用. 3)關(guān)于RPL,LINUX只用了0,3兩種級(jí)別. 0代表內(nèi)核進(jìn)程,3代表用戶進(jìn)程. 通過以上的分析,我們的程序顯然是用戶進(jìn)程,所以對(duì)映的就是__USER_CS, 最后CS寄存器的值就是0x23,而索引就是4.二進(jìn)制(100)=十進(jìn)制(4) 而在GDT全局描述表中4對(duì)映的是什么呢? 我們先來看看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*/ 現(xiàn)在把這4項(xiàng)描述符展開: 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 以下對(duì)描述符各位進(jìn)行解析: 描述符格式如下: 63-56位存放的是基地址31-24位,基地址都為0 55位也叫G位,在LINUX中都為1,等于1時(shí)段長以4k字節(jié)為單位,等于0時(shí)以字節(jié)為單位 54位也叫D位,在LINUX中都為1,等于1表示對(duì)該段的訪問為32位指令,等于0為16位指令 53位等于0 52位,CPU忽略該位,可由軟件使用. 51-48位存放的是段地址上限19-16位,都是1 47位也叫P位,在LINUX中都是1,表示4個(gè)段都在內(nèi)存中. 46-45位是DPL位,表示特權(quán)級(jí)別.分別有00(0級(jí))和11(3級(jí))兩種組合. 44位也叫S位,等于1時(shí)表示一般的代碼段或數(shù)據(jù)段,等于0時(shí)表示用于系統(tǒng)管理的系統(tǒng)段,如各類描述表. 43-41位叫做type位,因?yàn)楦魑恢g有著緊密聯(lián)系: 43位也叫E位,等于1時(shí)表示代碼段,這時(shí)第42位叫C位,C位等于0時(shí)會(huì)忽視特權(quán)級(jí)別,C位等于1時(shí)會(huì)依照特權(quán)級(jí)別.這時(shí)41位叫R位,等于1時(shí)為可讀,為0時(shí)不可讀. 43位等于0時(shí)表示數(shù)據(jù)段,這時(shí)第42位叫ED位,ED位等于0時(shí)向上伸(數(shù)據(jù)段),ED位等于1時(shí)向下伸(堆棧段),這時(shí)41位叫W位,等于1時(shí)為可寫,為0時(shí)不可寫. 40位叫A位,在LINUX中都是1,表示以被訪問過. 39-16位存放的是基地址23到0位,基地址都為0. 15-0位存放的是段地址上限15-0位,都是1 結(jié)論:每個(gè)段都是從0地址開始的整個(gè)4GB虛存空間,虛地址到線線地址的映射保持原值不變. 因此,LINUX內(nèi)核的頁式映射,可以直接將線性地址當(dāng)作虛擬地址.二者完全一致. 3.3)頁式映射 3.3.1)頁式映射的概念: 1)在I386 CPU中頁式存儲(chǔ)的基本思路是:通過頁面目錄和頁面表分兩個(gè)層次實(shí)現(xiàn)從線性地址到物理地址的映射. 2)在LINUX中要考慮到各種不同的CPU,它以一種假想的,虛擬的CPU和MMU為基礎(chǔ),設(shè)計(jì)出一種通用的模型,再把它分別落實(shí)到各種具體的CPU上. 因此,LINUX內(nèi)核的映射機(jī)制設(shè)計(jì)成三層,在頁面目錄和頁面表中間增設(shè)了一層"中間目錄". 邏輯上的三層映射對(duì)于i386 CPU和MMU就變成了二層映射,把中間目錄PMD這一層跳過了,但是軟件的結(jié)構(gòu)卻還保持著三層映射的框架. 3)頁面目錄稱為PGD,中間目錄稱為PMD,頁面表則稱為PT.PT的表項(xiàng)則稱為PTE. 頁面目錄,中間目錄,頁目表三者均為數(shù)組. 4)邏輯上把線性地址分成4個(gè)段位,分別用在頁面目錄PGD的偏移,中間目錄PMD中的偏移,頁表PT中的偏移以及物理頁面內(nèi)的偏移,而如果是I386的CPU則沒有中間目錄. 也就是被分成3個(gè)段位,分別是頁面目錄PGD的偏移,頁表pt中的偏移以及物理頁內(nèi)的偏移量. 5)每個(gè)進(jìn)程都有自己的頁目錄表和頁表,進(jìn)程的切換就是將當(dāng)前進(jìn)程的頁目錄表起始地址保存到CR3寄存器. 3.3.2)線性地址到物理地址的映射: 1)將一個(gè)進(jìn)程的頁面目錄起始地址裝入寄存器CR3. 2)用線性地址的第1個(gè)段位即PGD的偏移,找到頁面表的物理地址.頁目錄表的大小為4k,剛好一個(gè)頁的大小,包含1024項(xiàng),每項(xiàng)4個(gè)字節(jié)(32位) 3)用線性地址的第2個(gè)段位即PT的偏移,找到表項(xiàng),頁面表的大小也是4k,同樣包含1024項(xiàng),每項(xiàng)4個(gè)字節(jié)(32位) 4)得到表項(xiàng)的高20位+低12位0組成,這個(gè)高20就是物理地址的高20位,再加上線性地址的第3段位即12位的偏移就得到了最終的物理地址. 3.3.3)用實(shí)例來說明映射的過程: 第一步:通過頁目錄表找到頁面表 還是以上面的程序?yàn)槔? hello程序執(zhí)行后,調(diào)用函數(shù)grreeting,這里的虛擬地址也就是線性地址為0x08048354 call 08048354 <greeting> 分解后的結(jié)果是: 0000 1000 0000 0100 1000 0011 0101 0100 第1個(gè)段位(高10位): 0000 1000 00 對(duì)映十進(jìn)制的32,也就是在頁目錄表的偏移32找到其頁面表的物理地址,也就是頁面表的指針,它的低12位是0,因?yàn)轫撁姹硎?KB大小,所以肯定是邊界對(duì)齊了. 第二步:通過頁面表找到頁的起始物理地址高 接下來是線性地址的第二個(gè)段位(中間10位): 00 0100 10 00 對(duì)映十進(jìn)制的72,也就是在剛才找到的頁面表的偏移72找到目標(biāo)頁的起始物理地址,高20位有效的地址,低12位填充為0. 第三步:得到最終的物理地址 通過找到的頁起始物理地址,加上線性地址的第三個(gè)段位的偏移地址得到最終的物理地址. 例如: 第三個(gè)段位:0011 0101 0100 對(duì)映16進(jìn)制為0x354 如果目標(biāo)頁的起始物理地址為:0x740000,那么最終的物理地址就是: 0x740000+0x354=0x740354 本文來自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/wishfly/archive/2010/05/21/5613931.aspx 本文來自CSDN博客,,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/hshl1214/archive/2010/08/09/5799690.aspx
|
|