一文帶你深入理解Linux內(nèi)核之內(nèi)存尋址
一、內(nèi)存地址
1.1 邏輯地址 (logic address)
在機(jī)器語(yǔ)言指令中用來(lái)指定一個(gè)操作數(shù)或一條指令的地址,每一個(gè)邏輯地址都由以下兩部分組成
段 (segment)指明段位置
偏移量 (offset)指明段開(kāi)始處到實(shí)際地址的距離
1.2 虛擬地址 (virtual address)
根據(jù)機(jī)器的位數(shù)不同而不同,32位機(jī)器即32位無(wú)符號(hào)整數(shù)、64位即64位無(wú)符號(hào)整數(shù),這里取32位為例。
可用于表達(dá) 即 4GB 的地址空間
通常用16進(jìn)制表示,值的范圍從 0x00000000 ~ 0xffffffff
1.3 物理地址 (physical address)
內(nèi)存芯片級(jí)的內(nèi)存單元尋址,從CPU的地址引腳發(fā)送到內(nèi)存總線上的電信號(hào)相對(duì)應(yīng),由 32 位或 36 位無(wú)符號(hào)整數(shù)表示
1.4 內(nèi)存控制單元 MMU
內(nèi)存控制單元以下簡(jiǎn)稱MMU,其集成在CPU上進(jìn)行地址翻譯,轉(zhuǎn)換過(guò)程為兩階段
分段:由邏輯地址到虛擬地址
分頁(yè):由虛擬地址到物理地址

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ? ??


在多核系統(tǒng)中,所有的CPU核心共享同一內(nèi)存,則代表著CPU可以并發(fā)的訪問(wèn)內(nèi)存。而內(nèi)存的讀寫必須是串行執(zhí)行,所以需要專用元器件對(duì)內(nèi)存訪問(wèn)進(jìn)行排序,其稱為內(nèi)存仲裁器。
內(nèi)存仲裁器是在內(nèi)存總線和RAM芯片之間的硬件電路
若內(nèi)存空閑:允許訪問(wèn)
若內(nèi)存被占用:延遲CPU訪問(wèn)
注:由于單處理器上存在一個(gè)叫做DMA控制器的特殊處理器,因此其實(shí)單處理器上也有內(nèi)存仲裁器
1.6 分段和分頁(yè)的意義
分段和分頁(yè)是用于劃分進(jìn)程的物理地址空間的
分段:每個(gè)進(jìn)程分配不同的虛擬地址空間
分頁(yè):把同一虛擬地址空間映射到不同的物理地址
Linux更多使用分頁(yè)的方式
不同進(jìn)程共享同一組虛擬地址空間,內(nèi)存管理簡(jiǎn)單
跨平臺(tái),因?yàn)镽ISC體系結(jié)構(gòu)對(duì)分段支持有限
二、內(nèi)存分段
2.1 硬件分段
2.1.1 實(shí)模式和保護(hù)模式
從 80286 模型開(kāi)始,Intel處理器采用兩種不同方式進(jìn)行地址轉(zhuǎn)換,稱為實(shí)模式(real mode)和保護(hù)模式(protected mode)
實(shí)模式其作用是為維持處理器和早期模型的兼容,因?yàn)樵缙诩拇嫫魑粩?shù)太少,物理地址有20位,最多1MB的內(nèi)存空間。而段基址寄存器有16位,最多只能訪問(wèn)64kb。為了訪問(wèn)64kb以上的空間,需要對(duì)內(nèi)存進(jìn)行分段,使用段基址+段偏移的模式尋址。 通過(guò) 物理地址 = 段基址 << 4 + 段內(nèi)偏移 的方式表示物理地址。這個(gè)實(shí)模式的 “實(shí)” 體現(xiàn)在其反應(yīng)的是真實(shí)物理地址。 但是由于實(shí)模式?jīng)]有區(qū)分代碼和數(shù)據(jù),如果用戶程序的一個(gè)指針如果指向了系統(tǒng)程序區(qū)域或其他用戶程序區(qū)域,并修改了內(nèi)容,那么后果就很可能是災(zāi)難性的。
保護(hù)模式
隨著寄存器硬件的擴(kuò)展,地址位數(shù)和寄存器位數(shù)都變成了32/64位,現(xiàn)代CPU已經(jīng)不需要使用上述實(shí)模式了,當(dāng)然為了兼容老版本所以還是得支持實(shí)模式。
同時(shí)由于實(shí)模式不安全,我們通過(guò)一些手段來(lái)實(shí)現(xiàn)比較安全的尋址,這也是保護(hù)模式的命名的由來(lái)。
地址保護(hù):程序內(nèi)部的地址(虛擬地址)要由操作系統(tǒng)轉(zhuǎn)化為物理地址去訪問(wèn),程序?qū)Υ艘粺o(wú)所知
邊界保護(hù): 段寄存器中不再儲(chǔ)存的是段地址而是段索引。我們將數(shù)據(jù)放在一個(gè)叫做全局描述符表(GDT) 的結(jié)構(gòu)中,其中表項(xiàng)稱為段描述符,段描述符存放了段基址、段界限、內(nèi)存段類型屬性,用來(lái)索引段地址和標(biāo)記段邊界。
2.1.2 段選擇符和段寄存器
段選擇符邏輯地址 = 段標(biāo)識(shí)符 (16位) + 段偏移量 (32位),我們又將段標(biāo)識(shí)符稱為段選擇符,其結(jié)構(gòu)如下圖所示

index 描述符的入口,在2.1.4節(jié)中會(huì)詳細(xì)講解
TI (Table Indicator)標(biāo)明段在GDT還是LDT中,在GDT中為0,LDT中為1
RPL 請(qǐng)求特權(quán)級(jí),cs寄存器改變時(shí)指示出CPU當(dāng)前特權(quán)級(jí)
段寄存器段寄存器存放段選擇符,段寄存器共有 cs,ss,ds,es,fs和gs六個(gè),其作用如圖三所示。 注:cs寄存器中還有一個(gè)兩位的字段,指明CPU當(dāng)前特權(quán)級(jí)別(CPL)0~3,Linux只用0和3,代表內(nèi)核態(tài)和用戶態(tài) ?


2.1.3 段描述符
每個(gè)段被一個(gè)8字節(jié)的段描述符(Segment Descriptor)表示,描述了段的特征。
其放在 全局描述符表(GDT - Global Descriptor Table) 或 局部描述符表 (LDT - Local Descriptor Table) 中
全局描述符表(GDT - Global Descriptor Table)- 特點(diǎn):進(jìn)程共享 ? - 存放:gdtr寄存器(基址+大?。?/p>
局部描述符表 (LDT - Local Descriptor Table)
特點(diǎn):進(jìn)程獨(dú)享
存放:ldtr寄存器(基址+大?。?/p>
段描述符字段

描述符段分類

代表任務(wù)狀態(tài)段(TSS)用于保存寄存器內(nèi)容,僅在GDT中
代碼段描述符
數(shù)據(jù)段描述符
任務(wù)狀態(tài)段描述符 (TSSD)
2.1.4 快速訪問(wèn)段描述符
段描述符的索引規(guī)則:段基址 + 段選擇符 index [13位] << 3 因此描述符最大數(shù)目為2^13-1
2.1.5 分段單元
圖六已經(jīng)較為清楚的展示了分段單元把邏輯地址轉(zhuǎn)為虛擬地址的過(guò)程,段選擇符在段寄存器中,offset存儲(chǔ)在ip寄存器中

邏輯地址翻譯
2.2 Linux 分段
2.2.1 Linux中的段結(jié)構(gòu)
2.6版的Linux只有在 80x86 結(jié)構(gòu)下才進(jìn)行分段,下圖為L(zhǎng)inux的分段結(jié)構(gòu)

Linux分段
所有段都從0x00000000開(kāi)始,所以在Linux下,邏輯地址和虛擬地址相同
相應(yīng)端選擇符由宏 __USER_CS、__USER_DS、__KERNEL_CS、__KERNEL_DS 定義
CPU的CPL存儲(chǔ)在 cs 寄存器的 RPL 字段中,特權(quán)級(jí)別改變,某些段寄存器必須更新
例如當(dāng)CPL由 3 變?yōu)?0 時(shí) ds寄存器必須從含有用戶態(tài)數(shù)據(jù)段的段選擇符變?yōu)楹袃?nèi)核數(shù)據(jù)段的段選擇符,ss類似
2.2.2 Linux GDT
每個(gè)CPU對(duì)應(yīng)一個(gè)GDT,所有的GDT都存放在 cpu_gdt_table 里,所有的GDT地址和大小被存放在 cpu_gdt_descr數(shù)組中。
這些符號(hào)在 arch/i386/kernel/head.S 中被定義
每個(gè)GDT包含 18個(gè)段描述符和14個(gè)空的保留項(xiàng),保留項(xiàng)保證了常用的描述符可以在同一個(gè)32字節(jié)的 Cache 中,防止 Cache 抖動(dòng)。

Linux GDT結(jié)構(gòu)
三、內(nèi)存分頁(yè)
3.1 硬件分頁(yè)
分頁(yè)單元(Paging Unit)是將虛擬地址轉(zhuǎn)化為物理地址
關(guān)鍵任務(wù):是將所請(qǐng)求的訪問(wèn)類型和虛擬地址訪問(wèn)權(quán)限相比較,如果訪問(wèn)無(wú)效,則產(chǎn)生缺頁(yè)異常
頁(yè):一組虛擬地址,又指包含在這組地址中的數(shù)據(jù)。把RAM分成固定長(zhǎng)度的頁(yè)框(Page Frame)每個(gè)頁(yè)框(結(jié)構(gòu))包含一個(gè)頁(yè)(數(shù)據(jù))。
頁(yè)表:將虛擬地址映射到物理地址的數(shù)據(jù)結(jié)構(gòu)
cr0寄存器:PG標(biāo)志為0,虛擬地址就解釋為物理地址,否則如果 PG = 1 代表啟用分頁(yè)。
3.2 Linux 分頁(yè)
Linux采用4級(jí)分頁(yè)模式,節(jié)省內(nèi)存空間花費(fèi),頁(yè)表基址寄存器cr3
頁(yè)全局目錄 (Page Global Directory)
頁(yè)上級(jí)目錄(Page Upper DIrectory)
頁(yè)中間目錄(Page Middle Directory)
頁(yè)表 (Page Table)

Linux多級(jí)分頁(yè)
如圖所示,虛擬地址翻譯過(guò)程,其將虛擬地址分為五部分,標(biāo)準(zhǔn)頁(yè)大小4kb,所以offset占12位,剩下 52 位 每 13 位代表相應(yīng)目錄偏移量,先取出cr3寄存器中頁(yè)全局目錄的基址,和偏移量相加,索引到下級(jí)頁(yè)上級(jí)目錄的極致,如此重復(fù),直到索引到頁(yè)表取出 PPN,由于物理地址偏移量和虛擬地址相同,所以直接和虛擬地址偏移量 VPO 拼接得到物理地址.
為什么要使用多級(jí)頁(yè)表來(lái)完成映射呢?
用來(lái)將虛擬地址映射到物理地址的數(shù)據(jù)結(jié)構(gòu)稱為頁(yè)表, 實(shí)現(xiàn)兩個(gè)地址空間的關(guān)聯(lián)最容易的方式是使用數(shù)組, 對(duì)虛擬地址空間中的每一頁(yè), 都分配一個(gè)數(shù)組項(xiàng). 該數(shù)組指向與之關(guān)聯(lián)的頁(yè)幀, 但這會(huì)引發(fā)一個(gè)問(wèn)題, 例如, IA-32體系結(jié)構(gòu)使用4KB大小的頁(yè), 在虛擬地址空間為4GB的前提下, 則需要包含100萬(wàn)項(xiàng)的頁(yè)表. 這個(gè)問(wèn)題在64位體系結(jié)構(gòu)下, 情況會(huì)更加糟糕. 而每個(gè)進(jìn)程都需要自身的頁(yè)表, 這回導(dǎo)致系統(tǒng)中大量的所有內(nèi)存都用來(lái)保存頁(yè)表.
設(shè)想一個(gè)典型的32位的X86系統(tǒng),它的虛擬內(nèi)存用戶空間(user space)大小為3G, 并且典型的一個(gè)頁(yè)表項(xiàng)(page table entry, pte)大小為4 bytes,每一個(gè)頁(yè)(page)大小為4k bytes。那么這3G空間一共有(3G/4k=)786432個(gè)頁(yè)面,每個(gè)頁(yè)面需要一個(gè)pte來(lái)保存映射信息,這樣一共需要786432個(gè)pte!
如何存儲(chǔ)這些信息呢?一個(gè)直觀的做法是用數(shù)組來(lái)存儲(chǔ),這樣每個(gè)頁(yè)能存儲(chǔ)(4k/4=)1K個(gè),這樣一共需要(786432/1k=)768個(gè)連續(xù)的物理頁(yè)面(phsical page)。而且,這只是一個(gè)進(jìn)程,如果要存放所有N個(gè)進(jìn)程,這個(gè)數(shù)目還要乘上N! 這是個(gè)巨大的數(shù)目,哪怕內(nèi)存能提供這樣數(shù)量的空間,要找到連續(xù)768個(gè)連續(xù)的物理頁(yè)面在系統(tǒng)運(yùn)行一段時(shí)間后碎片化的情況下,也是不現(xiàn)實(shí)的。
假設(shè)每個(gè)進(jìn)程都占用了4G的線性地址空間,頁(yè)表共含1M個(gè)表項(xiàng),每個(gè)表項(xiàng)占4個(gè)字節(jié),那么每個(gè)進(jìn)程的頁(yè)表要占據(jù)4M的內(nèi)存空間。為了節(jié)省頁(yè)表占用的空間,我們使用兩級(jí)頁(yè)表。每個(gè)進(jìn)程都會(huì)被分配一個(gè)頁(yè)目錄,但是只有被實(shí)際使用頁(yè)表才會(huì)被分配到內(nèi)存里面。一級(jí)頁(yè)表需要一次分配所有頁(yè)表空間,兩級(jí)頁(yè)表則可以在需要的時(shí)候再分配頁(yè)表空間,而Linux根據(jù)實(shí)際情況(64位CPU,內(nèi)存大小,查詢效率),選擇4級(jí)頁(yè)表。
3.2.1 分頁(yè)機(jī)制的優(yōu)勢(shì)
虛擬地址到物理地址的自動(dòng)轉(zhuǎn)換使得下述設(shè)計(jì)目標(biāo)變得現(xiàn)實(shí)
給每個(gè)進(jìn)程分配不同的物理地址空間,防止尋址錯(cuò)誤
區(qū)別頁(yè)和頁(yè)框不同,允許頁(yè)被裝入不同的頁(yè)框中,是虛擬內(nèi)存機(jī)制的基本要素
每個(gè)進(jìn)程有自己的頁(yè)全局目錄和頁(yè)表集合,每次進(jìn)行進(jìn)程上下文切換時(shí),Linux內(nèi)核把前一個(gè)進(jìn)程的cr3寄存器值存入在前一個(gè)進(jìn)程的進(jìn)程描述符中,并載入新進(jìn)程的全局目錄基址進(jìn)入cr3寄存器中。
3.2.2 進(jìn)程頁(yè)表
進(jìn)程的虛擬內(nèi)存空間被分為兩部分
用戶態(tài)尋址部分:0x00000000 ~ 0xbfffffff
內(nèi)核態(tài)尋址部分:0xc0000000 ~ 0xffffffff
進(jìn)程運(yùn)行在用戶態(tài)時(shí),其產(chǎn)生的線性地址小于 0xc0000000,在內(nèi)核態(tài)則隨意
3.2.3 內(nèi)核頁(yè)表
內(nèi)核有自己的一組頁(yè)表,存放在主內(nèi)核頁(yè)全局目錄中。主內(nèi)核頁(yè)全局目錄的最高目錄項(xiàng)部分作為參考模型,為系統(tǒng)中每個(gè)普通進(jìn)程對(duì)應(yīng)的頁(yè)全局目錄項(xiàng)提供參考模型。
