提要:本系列文章主要參考MIT 6.828課程
以及兩本書(shū)籍《深入理解Linux內(nèi)核》
《深入Linux內(nèi)核架構(gòu)》
對(duì)Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。
內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個(gè)領(lǐng)域:
- 內(nèi)存中的物理內(nèi)存頁(yè)的管理
- 分配大塊內(nèi)存的伙伴系統(tǒng)
- 分配較小內(nèi)存的slab、slub、slob分配器
- 分配非連續(xù)內(nèi)存塊的vmalloc分配器
- 進(jìn)程的地址空間
傳統(tǒng)的內(nèi)存管理主要包括段式存儲(chǔ)
、頁(yè)式存儲(chǔ)
、段頁(yè)式存儲(chǔ)
,這里我們會(huì)以這部分開(kāi)始,逐步介紹Linux內(nèi)核中的內(nèi)存管理,而要學(xué)習(xí)內(nèi)存管理,首先需要了解內(nèi)存尋址。所以本節(jié)內(nèi)容主要講解內(nèi)存尋址的相關(guān)知識(shí),并介紹Linux內(nèi)核中的段、頁(yè)式存儲(chǔ)。
內(nèi)存地址
在編程過(guò)程中,難免需要通過(guò)內(nèi)存地址來(lái)訪問(wèn)內(nèi)存中的某些內(nèi)容,那么這個(gè)過(guò)程中地址是如何映射到對(duì)應(yīng)的物理單元的呢?解決這一問(wèn)題首先要區(qū)分三種不同的地址:
- 邏輯地址:邏輯地址是包含在機(jī)器語(yǔ)言指令中用來(lái)指定一個(gè)操作數(shù)或者一條指令的地址。
每一個(gè)邏輯地址都由一個(gè)段和一個(gè)偏移量組成
,偏移量指明了從段開(kāi)始的地方到實(shí)際地址之間的距離。(這在分段結(jié)構(gòu)
中表現(xiàn)的極為明顯)。 - 線性地址:線性地址是一個(gè)32位的無(wú)符號(hào)數(shù),可以用來(lái)表示高達(dá)4GB的地址。線性地址通常使用十六進(jìn)制數(shù)字表示,值的范圍從0x00000000到0xffffffff(常用于頁(yè)式存儲(chǔ))。
- 物理地址:
用于內(nèi)存芯片級(jí)內(nèi)存單元尋址
。它們與微處理器的地址引腳發(fā)送到內(nèi)存總線上的電信號(hào)相對(duì)應(yīng)。物理地址由32位或36位無(wú)符號(hào)整數(shù)表示。
內(nèi)存控制單元(MMU)通過(guò)分段單元(硬件設(shè)備)將邏輯地址轉(zhuǎn)化為線性地址。使用分頁(yè)單元(硬件設(shè)備)把線性地址轉(zhuǎn)化為物理地址:
-------- -------
|邏輯地址| --> |分段單元| --> |線性地址| --> |分頁(yè)單元| --> |物理地址|
-------- -------
從格式上簡(jiǎn)單區(qū)別邏輯地址與線性地址,邏輯地址包含了兩部分:段和偏移量(注意這兩者是分開(kāi)的
),而線性地址知識(shí)一個(gè)32位無(wú)符號(hào)數(shù),雖然后期也會(huì)根據(jù)地址的位數(shù)再次進(jìn)行劃分(詳見(jiàn)分頁(yè)部分),但終歸只是一個(gè)線性的無(wú)符號(hào)數(shù)。
段式存儲(chǔ)
硬件將邏輯地址轉(zhuǎn)化為線性地址主要由分段單元完成該任務(wù)。邏輯地址由兩部分組成:
- 段標(biāo)識(shí)符:該字段是一個(gè)16位長(zhǎng)的字段,稱為
段選擇符
,負(fù)責(zé)從眾多段中選擇出正確的段
,因?yàn)?code>段信息會(huì)存儲(chǔ)在一張段描述符表中,因此需要通過(guò)段選擇符從段描述符表中索引找到正確的段描述符。 - 指定段內(nèi)相對(duì)地址的偏移量:32位長(zhǎng)的字段(因?yàn)橐粋€(gè)段可能很長(zhǎng),因此偏移量要足夠大)
段選擇符格式如下:
15 3 2 1 0
-------- ---- ---
段選擇符 | index | TL | RPL|
-------- ---- ---
對(duì)于每個(gè)字段的含義,后續(xù)在詳細(xì)講解通過(guò)段選擇符尋找對(duì)應(yīng)段
時(shí)會(huì)對(duì)每個(gè)字段給出解釋。
段選擇符被存放在段寄存器中,這使得可以方便快速地找到段選擇符,段寄存器主要包括6個(gè),分別為cs,ss,ds,es,fs,gs。其中有3個(gè)具有專門(mén)的用途:
段寄存器 | 描述 |
---|---|
cs | 代碼段寄存器,指向包含指令序列的段 |
ss | 棧段寄存器,指向包含當(dāng)前程序棧的段 |
ds | 數(shù)據(jù)段寄存器,指向包含靜態(tài)數(shù)據(jù)或者全局?jǐn)?shù)據(jù)段 |
其他3個(gè)段寄存器作一般用途,可以指向任意的數(shù)據(jù)段。
注意:cs寄存器還有一個(gè)很重要的功能:它含有一個(gè)兩位的字段,用以指明CPU的當(dāng)前特權(quán)級(jí)(GPL)。值為0代表最高優(yōu)先級(jí),而值為3代表最低優(yōu)先級(jí)。Linux只用0級(jí)和3級(jí),分別稱為內(nèi)核態(tài)和用戶態(tài)。
段描述符
剛才提到過(guò),每個(gè)段由一個(gè)8字節(jié)的段描述符表示
,它描述了段的特征。段描述符放在全局描述符表(GDT)
或者局部描述符表里(LDT)
中(前面提到過(guò))。通常只定義一個(gè)GDT
,而每個(gè)進(jìn)程除了存放在GDT中的段之外如果還需要?jiǎng)?chuàng)建附加段
,就會(huì)創(chuàng)建自己的LDT。GDT
在主存中的地址和大小存放在gdtr控制寄存器
中,當(dāng)前正被使用的LDT地址和大小存放在ldtr寄存器
中。如下給出一個(gè)全局段描述符例子:
在段描述符表中通常會(huì)使用如下幾種段描述符(簡(jiǎn)單了解各個(gè)段的作用即下面第一個(gè)表就好,具體字段的名稱可以用到再回來(lái)查):
描述符名稱 | 描述 |
---|---|
代碼段描述符 | 這個(gè)段描述符代表一個(gè)代碼段,它可以放在GDT或LDT中。該描述符置S標(biāo)志位1(非系統(tǒng)段) |
數(shù)據(jù)段描述符 | 這個(gè)段描述符代表一個(gè)數(shù)據(jù)段,它可以放在GDT或LDT中。該描述符置S標(biāo)志為1。棧段是通過(guò)一般的數(shù)據(jù)段實(shí)現(xiàn)的。 |
任務(wù)狀態(tài)段描述符(TSSD) | 這個(gè)段描述符代表一個(gè)任務(wù)狀態(tài)段(Task State Segment, TSS),也就是說(shuō)這個(gè)段用于保存處理器寄存器的內(nèi)容。它只能出現(xiàn)在GDT中。根據(jù)相應(yīng)的進(jìn)程是否正在CPU上運(yùn)行,其Type字段的值分別為11或9。這個(gè)描述符的S標(biāo)志置為0。 |
局部描述符表描述符(LDTD) | 這個(gè)段描述符代表一個(gè)包含LDT的段,它只出現(xiàn)在GDT中。相應(yīng)的Type字段的值為2,s標(biāo)志置為0 |
段描述符格式如下:
段描述符中各個(gè)字段含義如下:
字段表 | 描述 |
---|---|
Base | 包含段的首字節(jié)的線性地址 |
G | 粒度標(biāo)志:如果該位清0,則段大小以字節(jié)為單位,否則以4096的倍數(shù)計(jì) |
Limit | 存放段中最后一個(gè)內(nèi)存單元的偏移量,從而決定段的長(zhǎng)度。如果G被置為0,則一個(gè)段的大小在1個(gè)字節(jié)到1MB之間變化;否則,則在4KB到4GB之間變化。 |
S | 系統(tǒng)標(biāo)志:如果它被清0,則這是一個(gè)系統(tǒng)段,存儲(chǔ)諸如LDT這種關(guān)鍵的數(shù)據(jù)結(jié)構(gòu),否則它是一個(gè)普通的代碼段或者數(shù)據(jù)段。 |
Type | 描述了段的類型特征和他的存取權(quán)限 |
DPL | 描述符特權(quán)級(jí)(Descriptor Privilege Level)字段:用于限制對(duì)這個(gè)段的存取。它表示為訪問(wèn)這個(gè)段而要求的CPU最小的優(yōu)先級(jí)。因此,DPL設(shè)為0的段只能當(dāng)CPL為0時(shí)(即在內(nèi)核態(tài))才是可以訪問(wèn)的,而DPL設(shè)為3的段對(duì)任何CPL值都是可以訪問(wèn)的。 |
P | Segment-Present標(biāo)志:等于0表示段當(dāng)前不在主存中。Linux總是把這個(gè)標(biāo)志(第47位)設(shè)為1,因?yàn)樗鼜膩?lái)不把整個(gè)段交換到磁盤(pán)上去。 |
D或B | 成為D或B的標(biāo)志,取決于是代碼段還是數(shù)據(jù)段。D或B的含義在兩種情況下稍微有區(qū)別,但是如果段偏移量的地址是32位長(zhǎng),就基本上把它置為1,如果這個(gè)偏移量是16位長(zhǎng),它被清0。 |
AVL標(biāo)志 | 可以由操作系統(tǒng)使用,但是被Linux忽略 |
快速訪問(wèn)段描述符
在本節(jié)主要介紹分段單元將邏輯地址轉(zhuǎn)化為線性地址的過(guò)程。我們知道邏輯地址主要包括:16位的段選擇符和32位的段偏移量,段選擇符存放在段寄存器中。段選擇符格式如下:
15 3 2 1 0
-------- ---- ---
段選擇符 | index | TL | RPL|
-------- ---- ---
這里我們需要了解3個(gè)字段的含義:
字段名 | 描述 |
---|---|
index | 指定了放在GDT或者LDT中相應(yīng)的段描述符的入口 |
TI | TI(Table Indicator)標(biāo)志:指明段描述符是在GDT中(TI = 0)或在LDT中(TI=1) |
RPL | 請(qǐng)求者特權(quán)級(jí):當(dāng)相應(yīng)的段選擇符裝入到cs寄存器中時(shí)指示出CPU當(dāng)前的特權(quán)級(jí);它還可以用于在訪問(wèn)數(shù)據(jù)段時(shí)有選擇地削弱處理器的特權(quán)級(jí)。 |
由于一個(gè)段描述符是8個(gè)字節(jié)長(zhǎng),因此它在GDT或LDT內(nèi)的相對(duì)地址是由段選擇符的最高13位的值乘以8得到的(8=2^3,13+3=16)。例如如果GDT在0x00020000(這個(gè)值保存在gdtr寄存器中),切由段選擇符所指定的索引號(hào)為2,那么相應(yīng)的段描述符地址為0x00020000+(2*8)
即0x00020010
。
邏輯地址轉(zhuǎn)換為線性地址流程如下圖:
- 先檢查段選擇符的
TI
字段,以確定段描述符保存在哪一個(gè)描述符表(GDT、LDT)中。 - 從段選擇符的index字段計(jì)算段描述符的地址,index字段值乘以8(一個(gè)段描述符大?。?,這個(gè)結(jié)果與gdtr或ldtr寄存器中的內(nèi)容相加
- 把邏輯地址的偏移量與段描述符的
Base
字段的值相加就得到了線性地址。
操作系統(tǒng)課本中的介紹通常如下,可以與之進(jìn)行對(duì)比:
注意:GDT的第一項(xiàng)總是設(shè)置為0。這就確保空段選擇符的邏輯地址會(huì)被認(rèn)為是無(wú)效的
,因此引起一個(gè)處理器異常。能夠保存在GDT中的段描述符的最大數(shù)目是8191,即2^13-1。
最后,由于整個(gè)地址轉(zhuǎn)換過(guò)程中,前兩個(gè)過(guò)程,即訪問(wèn)GDT、LDT中段描述符的過(guò)程是比較耗時(shí)的
,為了加速該過(guò)程
,80x86處理器提供了一種附加的非編程的寄存器
供6個(gè)可編程的段寄存器使用。每個(gè)非編程寄存器含有8個(gè)字節(jié)的段描述符。通過(guò)這個(gè)非編程寄存器,達(dá)到了如下功能
:每當(dāng)一個(gè)段描述符被裝入段寄存器時(shí),相應(yīng)的段描述符就由內(nèi)存裝入到對(duì)應(yīng)的非編程CPU寄存器中,從那時(shí)起,針對(duì)那個(gè)段的邏輯地址轉(zhuǎn)換就可以不訪問(wèn)主存中的GDT或LDT,只有在段寄存器內(nèi)容改變時(shí),才有必要訪問(wèn)GDT或LDT。
Linux中的分段
Linux以非常有限的方式使用分段,分段可以給一個(gè)進(jìn)程分配不同的線性地址空間,而分頁(yè)可以把同一線性地址空間映射到不同的物理空間,與分段相比,Linux更喜歡使用分頁(yè)方式,因?yàn)椋?/p>
- 當(dāng)所有進(jìn)程使用相同的段寄存器時(shí),內(nèi)存管理變得更簡(jiǎn)單,也就是說(shuō)他們能共享同樣的一組線性地址。
- Linux設(shè)計(jì)目標(biāo)之一時(shí)可以把它移植到絕大多數(shù)的處理器平臺(tái)上。然而,RISC體系結(jié)構(gòu)對(duì)分段的支持很有限。
2.6版的Linux只有在80x86結(jié)構(gòu)下才需要使用分段。
運(yùn)行在用戶態(tài)的所有Linux進(jìn)程都使用一對(duì)相同的段來(lái)對(duì)指令和數(shù)據(jù)尋址
。這兩個(gè)段就是所謂的用戶代碼段
和用戶數(shù)據(jù)段
。類似的運(yùn)行在內(nèi)核態(tài)的所有Linux進(jìn)程都使用一對(duì)相同的段對(duì)指令和數(shù)據(jù)尋址,他們分別是內(nèi)核代碼段
和內(nèi)核數(shù)據(jù)段
。下表顯示了這四個(gè)重要段的段描述符字段的值。
段 | Base | G | Limit | S | Type | DPL | D/B | P |
---|---|---|---|---|---|---|---|---|
用戶代碼段 | 0x00000000 | 1 | 0xfffff | 1 | 10 | 3 | 1 | 1 |
用戶數(shù)據(jù)段 | 0x00000000 | 1 | 0xfffff | 1 | 2 | 3 | 1 | 1 |
內(nèi)核代碼段 | 0x00000000 | 1 | 0xfffff | 1 | 10 | 0 | 1 | 1 |
內(nèi)核數(shù)據(jù)段 | 0x00000000 | 1 | 0xfffff | 1 | 2 | 0 | 1 | 1 |
上面4個(gè)段,G標(biāo)志都設(shè)置為1,即limit以4096為單位。與段相關(guān)的線性地址從0開(kāi)始,達(dá)到2^32-1的尋址限長(zhǎng),這意味著在用戶態(tài)或內(nèi)核態(tài)下的所有進(jìn)程可以使用相同的邏輯地址。
所有段都從0x00000000開(kāi)始,表示Linux下邏輯地址和線性地址是一致的,即邏輯地址的偏移量字段的值與相應(yīng)的線性地址的值總是一致的。
那如何區(qū)分這4個(gè)段呢?
如前所述,CPU的當(dāng)前特權(quán)級(jí)
(CPL)反映了進(jìn)程是在用戶態(tài)還是內(nèi)核態(tài)
,并由存放在cs寄存器
中的段選擇符的RPL字段指定。只要當(dāng)前特權(quán)級(jí)被改變,一些段寄存器必須相應(yīng)地更新。例如,當(dāng)CPL=3時(shí)(用戶態(tài)),ds寄存器
必須含有用戶數(shù)據(jù)段的段選擇符,而當(dāng)CPL=0時(shí),ds寄存器必須含有內(nèi)核數(shù)據(jù)段的段選擇符。
頁(yè)式存儲(chǔ)
分頁(yè)單元(paging unit)把線性地址轉(zhuǎn)換成物理地址。其中的一個(gè)關(guān)鍵任務(wù)是把所請(qǐng)求的訪問(wèn)類型與線性地址的訪問(wèn)權(quán)限項(xiàng)比較,如果這次內(nèi)存訪問(wèn)是無(wú)效的
,就產(chǎn)生一個(gè)缺頁(yè)異常
。
為了效率起見(jiàn),線性地址被分成以固定長(zhǎng)度為單位的組,稱為頁(yè)
(page)。頁(yè)內(nèi)部連續(xù)的線性地址被映射到連續(xù)的物理地址中。這樣,內(nèi)核可以指定一個(gè)頁(yè)的物理地址和其存儲(chǔ)權(quán)限,而不用指定頁(yè)所包含的全部線性地址的存取權(quán)限。
分頁(yè)單元把所有的RAM分成固定長(zhǎng)度的頁(yè)框(有時(shí)叫做物理頁(yè))。每一個(gè)頁(yè)框包含一個(gè)頁(yè)(page),也就是說(shuō)一個(gè)頁(yè)框的長(zhǎng)度與一個(gè)頁(yè)的長(zhǎng)度一致。頁(yè)框是主存的一部分,因此也是一個(gè)存儲(chǔ)區(qū)域。
把線性地址映射到物理地址的數(shù)據(jù)結(jié)構(gòu)稱為頁(yè)表。頁(yè)表存放在主存中,并在啟動(dòng)分頁(yè)單元之前必須由內(nèi)核對(duì)頁(yè)表進(jìn)行適當(dāng)?shù)某跏蓟?/p>
從80386開(kāi)始,所有的80x86處理器都支持分頁(yè),它通過(guò)設(shè)置cr0寄存器的PG標(biāo)志啟用。當(dāng)PG=0時(shí),線性地址就被解釋成物理地址。
常規(guī)分頁(yè)
從80386起,Intel處理器的分頁(yè)單元處理4KB
的頁(yè)。
32位的線性地址被分為3個(gè)域(線性地址是根據(jù)位數(shù)劃分的,是隱式劃分的功能):
-
Directory(目錄)
:最高10位。 -
Table(頁(yè)表)
:中間10位。 -
Offset(偏移量)
:最低12位。
線性地址轉(zhuǎn)換為物理地址時(shí),需要依賴兩張表:頁(yè)目錄表和頁(yè)表。轉(zhuǎn)換過(guò)程如下圖:
每個(gè)活動(dòng)進(jìn)程必須有一個(gè)分配給它的頁(yè)目錄,正在使用的頁(yè)目錄的物理地址存放在控制寄存器cr3
中。線性地址內(nèi)的Directory字段
決定頁(yè)目錄表中
的目錄項(xiàng)
,而目錄項(xiàng)指向適當(dāng)?shù)捻?yè)表。地址的Table字段
依次又決定頁(yè)表
中的表項(xiàng)
,而表項(xiàng)含有頁(yè)所在頁(yè)框的物理地址。Offset字段
決定頁(yè)框
內(nèi)的相對(duì)位置
,由于它是12位長(zhǎng),故每一頁(yè)含有4096字節(jié)的數(shù)據(jù)。
頁(yè)目錄項(xiàng)表和頁(yè)表中的每項(xiàng)具有相同的結(jié)構(gòu)(可以先暫時(shí)跳過(guò),碰到使用的字段可以再回來(lái)看),每項(xiàng)字段如下:
標(biāo)志 | 描述 |
---|---|
Present標(biāo)志 | 如果被置為1,所指的頁(yè)(或頁(yè)表)就在主存中。如果該標(biāo)志為0,則這一頁(yè)不在主存中,此時(shí)這個(gè)表剩余的位可由操作系統(tǒng)用于自己的目的。如果執(zhí)行一個(gè)地址轉(zhuǎn)換所需的頁(yè)表項(xiàng)或頁(yè)目錄項(xiàng)中的Present標(biāo)志被清0,那么分頁(yè)單元就把該線性地址存放在控制寄存器cr2中,并產(chǎn)生14號(hào)異常:缺頁(yè)異常。 |
包含頁(yè)框物理地址最高20位的字段 | 由于每一個(gè)頁(yè)框有4KB的容量,它的物理地址必須是4096的倍數(shù),因此物理地址的最低12位總是為0。如果這個(gè)字段指向一個(gè)頁(yè)目錄,相應(yīng)的頁(yè)框就含有一個(gè)頁(yè)表,如果它指向一個(gè)頁(yè)表,相應(yīng)的頁(yè)框就含有一頁(yè)數(shù)據(jù)。 |
Accessed標(biāo)志 | 每當(dāng)分頁(yè)單元對(duì)應(yīng)頁(yè)框進(jìn)行尋址時(shí)就設(shè)置這個(gè)標(biāo)志。當(dāng)選出的頁(yè)被交換出去時(shí),這一標(biāo)志就可以由操作系統(tǒng)使用。分頁(yè)單元從來(lái)不重置這個(gè)標(biāo)志,而是必須由操作系統(tǒng)去做。 |
Dirty標(biāo)志 | 只應(yīng)用于頁(yè)表項(xiàng)中。每當(dāng)對(duì)一個(gè)頁(yè)框進(jìn)行寫(xiě)操作時(shí)就設(shè)置這個(gè)標(biāo)志。與Accessed標(biāo)志一樣,當(dāng)選中的頁(yè)被交換出去時(shí),這一標(biāo)志就可以由操作系統(tǒng)使用。分頁(yè)單元從來(lái)不重置這個(gè)標(biāo)志,而是必須由操作系統(tǒng)去做。 |
Read/Write標(biāo)志 | 含有頁(yè)或頁(yè)表的存取權(quán)限(Read/Write或Read)。 |
User/Superisor標(biāo)志 | 含有訪問(wèn)頁(yè)或頁(yè)表所需的特權(quán)級(jí)。 |
PCD和PWT標(biāo)志 | 控制硬件告訴緩存處理頁(yè)或頁(yè)表的方式。 |
Page Size標(biāo)志 | 只應(yīng)用于頁(yè)目錄項(xiàng)。如果設(shè)置為1,則頁(yè)目錄項(xiàng)指的是2MB或4MB的頁(yè)框。 |
Global標(biāo)志 | 只應(yīng)用于頁(yè)表項(xiàng)。這個(gè)標(biāo)志時(shí)在Pentium Pro中引入的,用來(lái)防止常用頁(yè)從TLB(快表)高速緩存中刷新出去。只有在cr4寄存器的頁(yè)全局啟動(dòng)(PGE)標(biāo)志置位時(shí)這個(gè)標(biāo)志才起作用。 |
這里再簡(jiǎn)單強(qiáng)調(diào)一個(gè)問(wèn)題,為何要使用多級(jí)頁(yè)表:
常規(guī)分頁(yè)的加速策略
當(dāng)今的微處理器時(shí)鐘頻率接近幾個(gè)GHz,而動(dòng)態(tài)RAM(DRAM)芯片
的存取時(shí)間是時(shí)鐘周期的數(shù)百倍。這意味著,當(dāng)從RAM中取操作數(shù)或向RAM中存放結(jié)果這樣的指令執(zhí)行時(shí),CPU可能等待很長(zhǎng)時(shí)間
。
硬件高速緩存
為了縮小CPU和RAM之間的速度不匹配,引入了硬件高速緩存內(nèi)存(hardware cache memory)
。硬件高速緩存基于著名的局部性原理(locality principle)
,它表明由于程序的循環(huán)結(jié)構(gòu)及相關(guān)數(shù)組可以組織成線性數(shù)組,最近最常用的相鄰地址在最近的將來(lái)又被用到的可能性極大
。80x86體系結(jié)構(gòu)中引入了一個(gè)叫行(line)
的新單位。行由幾十個(gè)連續(xù)的字節(jié)
組成,它們以脈沖突發(fā)模式(burst mode)在慢速DRAM和快速的用來(lái)實(shí)現(xiàn)告訴緩存的片上靜態(tài)RAM(SRAM)之間傳送,用來(lái)實(shí)現(xiàn)高速緩存。
高速緩存再被細(xì)分為行的子集。在一種極端的情況下,高速緩存可以是直接映射的(direct mapped),這時(shí)主存中的一個(gè)行總是存放在高速緩存中完全相同的位置。在另一種極端情況下,高速緩存是充分關(guān)聯(lián)的(fully associative),這意味著主存中的任意一個(gè)行可以存放在高速緩存中的任意位置。但是大多數(shù)高速緩存在某種程度上是N-路組關(guān)聯(lián)
的(N-way set associative),意味著主存中的任意一個(gè)行可以存放在高速緩存N行中的任意一行中
。例如,內(nèi)存中的一個(gè)行可以存放到一個(gè)2路組關(guān)聯(lián)高速緩存兩個(gè)不同的行中。
如下圖,硬件高速緩存由兩部分組成:
- 硬件高速緩存內(nèi)存:負(fù)責(zé)存放真正的行
- 高速緩存控制器:存放一個(gè)表項(xiàng)數(shù)組,每個(gè)表項(xiàng)對(duì)應(yīng)高速緩存內(nèi)存中的一個(gè)行。每個(gè)表項(xiàng)有一個(gè)標(biāo)簽(tag)和描述高速緩存行狀態(tài)的幾個(gè)標(biāo)志(flag)。這個(gè)標(biāo)簽由一些位組成,這些位讓高速緩存控制器能夠辨別由這個(gè)行當(dāng)前所映射的內(nèi)存單元。這種
內(nèi)存物理地址
通常分為3組:最高幾位對(duì)應(yīng)標(biāo)簽,中間幾位對(duì)應(yīng)高速緩存控制器的子集索引,最低幾位對(duì)應(yīng)行內(nèi)的偏移量。
這里引用鏈接中的一部分描述,對(duì)內(nèi)存物理地址
進(jìn)行描述(否則后續(xù)的高速緩存訪問(wèn)過(guò)程可能比較難以看懂):
訪問(wèn)cache時(shí),訪問(wèn)地址可分為3個(gè)部分:偏移量Offset、索引Index和標(biāo)簽Tag。
- Offset是塊內(nèi)地址,在地址的低幾位,因?yàn)閏ache塊一般比較大,如每個(gè)cache塊32字節(jié)或64字節(jié)。以32個(gè)字節(jié)為例,讀cache時(shí)把32個(gè)字節(jié)即256位作為一組一起都讀出來(lái),用Offset在32字節(jié)中選擇本次訪問(wèn)所需的字或雙字等;
- Index用來(lái)索引cache,訪問(wèn)時(shí)用Index作為訪問(wèn)cache的地址。
- 地址的高位是訪問(wèn)cache的Tag,由于cache的大小有限,每個(gè)cache行可能對(duì)應(yīng)內(nèi)存中的若干個(gè)存儲(chǔ)塊,cache中的每一行都要用tag來(lái)標(biāo)識(shí)當(dāng)前存的是哪個(gè)存儲(chǔ)塊,訪問(wèn)時(shí)用地址的Tag跟cache存的tag進(jìn)行比較,如果相等就給出命中信號(hào)hit。
了解了上面的內(nèi)容,原書(shū)中的高速緩存訪問(wèn)過(guò)程就可以很容易看懂了:
- 當(dāng)訪問(wèn)一個(gè)RAM存儲(chǔ)單元時(shí),CPU從物理地址中提取出子集的
索引號(hào)
并把子集中所有行的標(biāo)簽與物理地址的高幾位相比較。 - 如果發(fā)現(xiàn)某一個(gè)行的
標(biāo)簽
與這個(gè)物理地址的高位相同,則CPU命中一個(gè)高速緩存(cache hit);否則,高速緩存沒(méi)有命中(cache miss)。
最后給出了查找到高速緩存中具體內(nèi)容后的一些后操作:
-
當(dāng)命中一個(gè)高速緩存時(shí)
,高速緩存控制器進(jìn)行不同的操作,具體取決于存儲(chǔ)類型。
- 對(duì)于
讀操作
,控制器從高速緩存行中選取數(shù)據(jù)并送到CPU寄存器;不需要訪問(wèn)RAM而節(jié)約了CPU時(shí)間。因此高速緩存系統(tǒng)起到了其應(yīng)有的作用。 - 對(duì)于
寫(xiě)操作
,控制器可能采用以下兩個(gè)基本策略之一,分別稱之為通寫(xiě)和回寫(xiě)。- 在
通寫(xiě)
中,控制器總是既寫(xiě)RAM也寫(xiě)高速緩存行,為了提高寫(xiě)操作的效率關(guān)閉高速緩存。 -
回寫(xiě)方式
只更新高速緩存行,不改變RAM的內(nèi)容,提供了更快的功效。當(dāng)然,回寫(xiě)結(jié)束后,RAM最終必須被更新。只有當(dāng)CPU執(zhí)行一條要求刷新高速緩存表項(xiàng)的指令時(shí),或者當(dāng)一個(gè)FLUSH硬件信號(hào)產(chǎn)生時(shí)(通常在高速緩存不命中之后),高速緩存控制器才把高速緩存行寫(xiě)回到RAM中。
- 在
-
當(dāng)高速緩存沒(méi)有命中時(shí)
,高速緩存行被寫(xiě)回到內(nèi)存中,如果有必要的話,吧正確的行從RAM中取出放到高速緩存的表項(xiàng)中。
頁(yè)目錄項(xiàng)表和頁(yè)表中的每項(xiàng)具有相同的結(jié)構(gòu) 中PCD和PWT兩個(gè)標(biāo)志用于控制上述操作:
- PCD(Page Cache Disabit)標(biāo)志指明當(dāng)訪問(wèn)包含在這個(gè)頁(yè)框中的數(shù)據(jù)時(shí),高速緩存功能必須被啟用還是禁用。
- PWT(page Write-Through)標(biāo)志指明當(dāng)把數(shù)據(jù)寫(xiě)到頁(yè)框時(shí),必須使用的策略是回寫(xiě)策略還是通寫(xiě)策略。
最后給出一個(gè)鏈接,較為詳細(xì)的介紹了硬件高速緩存,如果感興趣可以繼續(xù)了解。
轉(zhuǎn)換后援緩沖器(TLB)
注意:這個(gè)TLB不是Thread Local Buffer。
除了通用硬件高速緩存之外,80x86處理器還包含了另一個(gè)稱為轉(zhuǎn)換后援緩沖器或TLB(Translation Lookaside Buffer)的高速緩存用于加快線性地址的轉(zhuǎn)換。當(dāng)一個(gè)線性地址被第一個(gè)使用時(shí),通過(guò)慢速訪問(wèn)RAM中的頁(yè)表計(jì)算出相應(yīng)的物理地址。同時(shí)物理地址被存放在一個(gè)TLB表項(xiàng)(TLB entry)中,以便以后對(duì)同一個(gè)線性地址的引用可以快速地得到轉(zhuǎn)換。
在多處理器系統(tǒng)中,每個(gè)CPU都有自己的TLB,這叫做該CPU的本地TLB。與硬件高速緩存相反,TLB中的對(duì)應(yīng)項(xiàng)不必同步,這是因?yàn)檫\(yùn)行在現(xiàn)有CPU上的進(jìn)程可以使同一線性地址與不同的物理地址發(fā)生聯(lián)系。
當(dāng)CPU的cr3控制寄存器被修改時(shí),硬件自動(dòng)使本地TLB中的所有項(xiàng)都無(wú)效,這是因?yàn)樾碌囊唤M頁(yè)表被啟用而TLB指向的是舊數(shù)據(jù)。
64位系統(tǒng)中的分頁(yè)
通過(guò)上面我們可以看到32位系統(tǒng)使用兩級(jí)分頁(yè)就可以滿足需求,那64位系統(tǒng)呢?
首先假設(shè)一個(gè)大小為4KB的標(biāo)準(zhǔn)頁(yè)
。因?yàn)?KB覆蓋210個(gè)地址的范圍,4KB覆蓋212個(gè)地址,所以offset字段是12位
。這樣線性地址就剩下52位
分配給Table和Directory字段
。如果我們現(xiàn)在決定僅僅使用64位中的48位來(lái)尋址(這個(gè)限制仍然使我們自在地?fù)碛?56TB的尋址空間!),剩下的48-12=36位將被分配給Table和Directory字段。如果我們現(xiàn)在決定為兩個(gè)字段各預(yù)留18位,那么每個(gè)進(jìn)程的頁(yè)目錄和頁(yè)表都含有2^18個(gè)項(xiàng),即超過(guò)256000個(gè)項(xiàng)
,而一頁(yè)無(wú)法放下如此多的頁(yè)目錄項(xiàng)和頁(yè)表項(xiàng)
。
因此,所有64位處理器的硬件分頁(yè)系統(tǒng)都使用了額外的分頁(yè)級(jí)別。使用的級(jí)別數(shù)量取決于處理器的類型。下表總結(jié)出了一些Linux支持64位平臺(tái)使用的硬件分頁(yè)系統(tǒng)的主要特征:
平臺(tái)名稱 | 頁(yè)大小 | 尋址使用的位數(shù) | 分頁(yè)級(jí)別數(shù) | 線性地址分級(jí) |
---|---|---|---|---|
alpha | 8KB | 43 | 3 | 10+10+10+13 |
ia64 | 4KB | 39 | 3 | 9+9+9+12 |
x86_64 | 4KB | 48 | 4 | 9+9+9+9+12 |
Linux本身提供了一種通用的分頁(yè)系統(tǒng),它適用于絕大多數(shù)所支持的硬件分頁(yè)系統(tǒng)。
Linux的分頁(yè)
Linux采用了一種同時(shí)適用于32位和64位系統(tǒng)的普通分頁(yè)模型。到2.6.10版本,Linux采用三級(jí)分頁(yè)的模型。從2.6.11版本開(kāi)始,采用了四級(jí)分頁(yè)模型,分為4種頁(yè)表如下:
- 頁(yè)全局目錄(Page Global Directory)
- 頁(yè)上級(jí)目錄(Page Upper Directory)
- 頁(yè)中間目錄(Page Middle Directory)
- 頁(yè)表(Page Table)
結(jié)構(gòu)如下圖:
頁(yè)全局目錄包含若干頁(yè)上級(jí)目錄的地址,頁(yè)上級(jí)目錄又依次包含若干頁(yè)中間目錄的地址,而頁(yè)中間目錄又包含若干頁(yè)表地址
。因此線性地址被分成五個(gè)部分。上圖沒(méi)有顯示位數(shù),因?yàn)槊恳徊糠值拇笮∨c具體的計(jì)算機(jī)體系結(jié)構(gòu)有關(guān)。
對(duì)于沒(méi)有啟用物理地址擴(kuò)展的32位系統(tǒng),兩級(jí)頁(yè)表已經(jīng)足夠了。Linux通過(guò)使“頁(yè)上級(jí)目錄”位和“頁(yè)中間目錄”位全為0
,從根本上取消了頁(yè)上集目錄和頁(yè)中間目錄字段
。不過(guò),頁(yè)上級(jí)目錄和頁(yè)中間目錄在指針序列中的位置被保留
,以便同樣的代碼在32位系統(tǒng)和64位系統(tǒng)下都能使用。內(nèi)核為頁(yè)上集目錄和頁(yè)中間目錄保留了一個(gè)位置,這是通過(guò)把它們的頁(yè)目錄項(xiàng)數(shù)設(shè)置為1,并把這兩個(gè)目錄項(xiàng)映射到全局目錄一個(gè)適當(dāng)?shù)哪夸浂鴮?shí)現(xiàn)的。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-635255.html
總結(jié)
本篇文章主要講解內(nèi)存地址轉(zhuǎn)換的主要內(nèi)容,并對(duì)Linux內(nèi)核對(duì)段式存儲(chǔ)和頁(yè)式存儲(chǔ)進(jìn)行了簡(jiǎn)單的了解,下一篇文章將開(kāi)始進(jìn)行真正內(nèi)存管理相關(guān)的內(nèi)容。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-635255.html
到了這里,關(guān)于深入理解Linux內(nèi)核——內(nèi)存管理(1)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!