天天看點

Linux 存儲管理2——記憶體管理

1、MMU(記憶體管理單元)

MMU是怎麼将邏輯位址轉換成實體位址?

MMU是一種硬體電路,它包含兩個部件,一個是分段部件,一個是分頁部件,通過分段機制(把一個邏輯位址轉換為線性位址,線性位址也是32位,其位址取值範圍為0x00000000~0xffffffff)和分頁機制(把一個線性位址轉換為實體位址),最終将邏輯位址映射為實體位址。如下圖:

Linux 存儲管理2——記憶體管理

1.1 分段機制

在作業系統原理關于分段的說明:段的配置設定時為了更好的滿足使用者,段的長度不固定,由使用者定義,每個段都有自己的位址空間(通過基址包含某實體記憶體的位址,和長度值來表示段的長度),表示一個位址需要給出段部分(選擇符)和偏移部分。如下圖,如果沒有分頁的話,那麼圖中的線性位址也就是實體位址,這種方式對于段的離散配置設定就很容易導緻碎片問題(進而使用分頁機制來提高記憶體使用率):

Linux 存儲管理2——記憶體管理

<!--[if !vml]--><!--[endif]-->

1.2 分頁機制

通過使用分頁機制可以很好的提高記憶體使用率。分頁機制把一個線性位址轉換為實體位址。Linux下一個頁大小為4K,把線性位址(32位)和實體位址(32位,2^32=4G實體記憶體)都按照4K(2^12)頁大小來進行劃分,那麼,一共有4G/4K=1M的頁面,如果隻是簡單的進行線性位址和實體位址一對一記錄映射的話,這樣在映射表建立的就會占用比較大的實體記憶體。這個時候就引入了頁表和頁目錄表,位址轉換過程見下圖:

Linux 存儲管理2——記憶體管理

(1) 通過對32位線性位址劃分:第31~22這10位(2^10=1024)定位頁目錄項,第21~12這10位定位頁表項,第11~0這12位(2^12=4K)為頁内偏移值。

(2) 對于頁目錄表,有1024個頁目錄項,每個頁目錄項(又含有1024個頁表項)指向下一級頁表的實體位址(32位=4個位元組),那麼一共需要1024*4(=4K)位元組,即隻要配置設定一頁就可以完全存放。

(3) 對于頁表,原理和頁目錄表一樣,那麼一共需要1024(1024個頁目錄項)*1024(每個頁目錄項含有1024個頁表項)*4(=1M)位元組。

(4) 對于頁目錄的實體位址,就存放在CR3寄存器中。

是以,這樣可以尋址1024*1024*4K=4G的實體記憶體。另外,在Linux的X86架構引入了3級頁表機制(包括了頁全局目錄、頁中間目錄和頁表)。

2、高端記憶體的映射方式

高端記憶體是指實體位址大于 896M 的記憶體。對于這樣的記憶體,無法在“核心直接映射空間”進行映射。

為什麼?

因為“核心直接映射空間”最多隻能從 3G 到 4G,隻能直接映射 1G 實體記憶體,對于大于 1G 的實體記憶體,無能為力。實際上,“核心直接映射空間”也達不到

1G, 還得留點線性空間給“核心動态映射空間” 呢。是以,Linux 規定“核心直接映射空間” 最多映射 896M 實體記憶體。

對 于高端記憶體,可以通過 alloc_page() 或者其它函數獲得對應的 page,但是要想通路實際實體記憶體,還得把 page 轉為線性位址才行(為什麼?想想 MMU 是如何通路實體記憶體的),也就是說,我們需要為高端記憶體對應的 page 找一個線性空間,這個過程稱為高端記憶體映射。

高端記憶體映射有三種方式:

1、映射到“核心動态映射空間”

這種方式很簡單,因為通過 vmalloc() ,在”核心動态映射空間“申請記憶體的時候,就可能從高端記憶體獲得頁面(參看 vmalloc 的實作),是以說高端記憶體有可能映射到”核心動态映射空間“ 中。

2、永久核心映射

如果是通過 alloc_page() 獲得了高端記憶體對應的 page,如何給它找個線性空間?

核心專門為此留出一塊線性空間,從 PKMAP_BASE 到 FIXADDR_START ,用于映射高端記憶體。在 2.4 核心上,這個位址範圍是 4G-8M 到 4G-4M 之間。這個空間起叫“核心永久映射空間”或者“永久核心映射空間”

這個空間和其它空間使用同樣的頁目錄表,對于核心來說,就是 swapper_pg_dir,對普通程序來說,通過 CR3 寄存器指向。通常情況下,這個空間是

4M 大小,是以僅僅需要一個頁表即可,核心通過來 pkmap_page_table 尋找這個頁表。

通過 kmap(), 可以把一個 page 映射到這個空間來

由于這個空間是 4M 大小,最多能同時映射 1024 個 page。是以,對于不使用的的 page,應該及時從這個空間釋放掉(也就是解除映射關系),通過

kunmap() ,可以把一個 page 對應的線性位址從這個空間釋放出來。

3、臨時映射

核心在 FIXADDR_START 到 FIXADDR_TOP 之間保留了一些線性空間用于特殊需求。這個空間稱為“固定映射空間”

在這個空間中,有一部分用于高端記憶體的臨時映射。

這塊空間具有如下特點:

1、 每個 CPU 占用一塊空間

2、 在每個 CPU 占用的那塊空間中,又分為多個小空間,每個小空間大小是 1 個 page,每個小空間用于一個目的,這些目的定義在 kmap_types.h 中的 km_type 中。

當要進行一次臨時映射的時候,需要指定映射的目的,根據映射目的,可以找到對應的小空間,然後把這個空間的位址作為映射位址。這意味着一次臨時映射會導緻以前的映射被覆寫。

通過 kmap_atomic() 可實作臨時映射。

下圖簡單簡單表達如何對高端記憶體進行映射

Linux 存儲管理2——記憶體管理

-------------------------

高端記憶體含義為:線性位址空間 PAGE_OFFSET + 896M 至4G的最後128M線性位址 <==映射==> 896M以上的實體頁框,非直接映射。有3種方法:非連續記憶體區映射,永久核心映射,臨時核心映射(固定映射)

   從 PAGE_OFFSET開始的線性位址區域為:

   PAGE_OFFSET(3G)|實體記憶體映射 --8M-- vmalloc區 --4K-- vmalloc區 --8K-- 永久核心映射--臨時核心映射(固定映射)|4G

1. 非連續區映射

1.1 每個非連續記憶體區都對應一個類型為 vm_struct的描述符,通過next字段,這些描述符被插入到一個vmlist連結清單中。

1.2 三種非連續區的類型:

   VM_ALLOC   -- 實體記憶體(調用alloc_page)和線性位址同時申請,實體記憶體是 __GFP_HIGHMEM類型(配置設定順序是HIGH, NORMAL, DMA )(可見vmalloc不僅僅可以映射__GFP_HIGHMEM頁框,它的主要目的是為了将零散的,不連續的頁框拼湊成連續的核心邏輯位址空間...)

   VM_MAP     -- 僅申請線性區,實體記憶體另外申請,是VM_ALLOC的簡化版

   VM_IOREMAP -- 僅申請線性區,實體記憶體另外申請(這裡的實體記憶體一般都是高端記憶體,大于896M的記憶體)

2. 永久核心映射

2.1 永久記憶體映射允許建立長期映射。使用主核心頁表中swapper_pg_dir的一個專門頁表。

    pkmap_page_table: 專門的頁表。頁表表項數由LAST_PKMAP(512或1024)産生。

    page_address_htable: 存放位址的

    pkmap_count: 包含LAST_PKMAP個計數器的數組。

    PKMAP_BASE: 頁表線性位址從PKMAP_BASE開始。

2.2 如果LAST_PKMAP個項都用完,則把目前程序置為 TASK_UNINTERRUPTIBLE,并調用schedule()

3. 臨時記憶體映射

3.1 可以用在中斷處理函數和可延遲函數的内部,從不阻塞。因為臨時記憶體映射是固定記憶體映射的一部分,一個位址固定給一個核心成分使用。

3.2 每個CPU都有自己的一個13個視窗(一個線性位址及頁表項)的集合。

enum km_type {

    KM_BOUNCE_READ,

    KM_SKB_SUNRPC_DATA,

    KM_SKB_DATA_SOFTIRQ,

    KM_USER0,

    KM_USER1,

    KM_BIO_SRC_IRQ,

    KM_BIO_DST_IRQ,

    KM_PTE0,

    KM_PTE1,

    KM_IRQ0,

    KM_IRQ1,

    KM_SOFTIRQ0,

    KM_SOFTIRQ1,

    KM_TYPE_NR

};

所有固定映射的固定線性位址

enum fixed_addresses {

    FIX_HOLE,

    FIX_VSYSCALL,

        ....

#ifdef CONFIG_HIGHMEM

    FIX_KMAP_BEGIN,    /* reserved pte's for temporary kernel mappings */

    FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,

#endif

        .......

    __end_of_permanent_fixed_addresses,

    /* temporary boot-time mappings, used before ioremap() is functional */

#define NR_FIX_BTMAPS    16

    FIX_BTMAP_END = __end_of_permanent_fixed_addresses,

    FIX_BTMAP_BEGIN = FIX_BTMAP_END + NR_FIX_BTMAPS - 1,

    FIX_WP_TEST,

    __end_of_fixed_addresses

3.3 注意 fixed_addresses 的位址從上至下是倒着的,FIX_HOLE的位址等于 0xfffff000,是一個洞

#define __fix_to_virt(x)    (FIXADDR_TOP - ((x) << PAGE_SHIFT))

#define __FIXADDR_TOP    0xfffff000

VMALLOC_RESERVE和896M

LINUX 核心虛拟位址空間到實體位址空間一般是固定連續影射的。

假定機器記憶體為512M,

從3G開始,到3G + 512M 為連續固定影射區。zone_dma, zone_normal為這個區域的。固定影射的VADDR可以直接使用(get a free page, then use pfn_to_virt()等宏定義轉換得到vaddr)或用kmalloc等配置設定. 這樣的vaddr的實體頁是連續的。得到的位址也一定在固定影射區域内。

如果記憶體緊張,連續區域無法滿足,調用vmalloc配置設定是必須的,因為它可以将實體不連續的空間組合後配置設定,是以更能滿足配置設定要求。vmalloc可以映射高端頁框,也可以映射底端頁框。vmalloc的作用隻是為了提供邏輯上連續的位址。。。

但vmalloc配置設定的vaddr一定不能與固定影射區域的vaddr重合。因為vaddr到實體頁的影射同時隻能唯一。是以vmalloc得到的 vaddr要在3G + 512m 以上才可以。也就是從VMALLOC_START開始配置設定。 VMALLOC_START比連續固定影射區大最大vaddr位址還多8-16M(2*VMALLOC_OFFSET)--有個鬼公式在

#define VMALLOC_OFFSET   8*1024

#define VMALLOC_START   (high_memory - 2*VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1)

high_memory 就是固定影射區域最高處。

空開8-16M做什麼? 為了捕獲越界的mm_fault.

同樣,vmalloc每次得到的VADDR空間中間要留一個PAGE的空(空洞),目的和上面的空開一樣。你vmalloc(100)2次,得到的2個位址中間相距8K。

如果連續配置設定無空洞,那麼比如

p1=vmalloc(4096);

p2=vmalloc(4096);

如果p1使用越界到p2中了,也不會mm_falut. 那不容易debug.

下面說明VMALLOC_RESERVE和896M的問題。

上面假設機器實體512M的case. 如果機器有1G實體記憶體如何是好?那vmalloc()的vaddr是不是要在3G + 1G + 8M 空洞以上配置設定?超過尋址空間了嗎。

這時,4G 下面保留的VMALLOC_RESERVE 128m 就派上用場了。

也就是說如果實體記憶體超過896M, high_memory也隻能在3G + 896地方。可尋址空間最高處要保留VMALLOC_RESREVE 128M給vmalloc用。

是以這128M的VADDR空間是為了vmalloc在實體超過了896M時候使用。如果實體僅僅有512M, 一般使用不到。因為VMALLOC_START很低了。如果vmalloc太多了才會用到。

high_memory在arch/i386/kernel, mm的初始化中設定。根據實體記憶體大小和VMALLOC_RESERVE得到數值.

是以說那128M的核心線性位址僅僅是為了影射1G以上的實體記憶體的不對的。如果實體記憶體2G,1G以下的vmalloc也用那空間影射。總之,核心的高端線性位址是為了通路核心固定映射以外的記憶體資源

看vmalloc配置設定的東西可以用

show_vmalloc()

{

struct vm_struct **p, *tmp;

for(p = &vmlist; (tmp = *p); p = &tmp->next) {

   printk("%p %p %d\n", tmp, tmp->addr, tmp->size

}

使用者空間當然可以使用高端記憶體,而且是正常的使用,核心在配置設定那些不經常使用的記憶體時,都用高端記憶體空間(如果有),所謂不經常使用是相對來說的,比如核心的一些資料結構就屬于經常使用的,而使用者的一些資料就屬于不經常使用的。

使用者在啟動一個應用程式時,是需要記憶體的,而每個應用程式都有3G的線性位址,給這些位址映射頁表時就可以直接使用高端記憶體。

而且還要糾正一點的是:那128M線性位址不僅僅是用在這些地方的,如果你要加載一個裝置,而這個裝置需要映射其記憶體到核心中,它也需要使用這段線性位址空間來完成,否則核心就不能通路裝置上的記憶體空間了。

總之,核心的高端線性位址是為了通路核心固定映射以外的記憶體資源

實際上高端記憶體是針對核心一段特殊的線性空間提出的概念,和實際的實體記憶體是兩碼事。程序在使用記憶體時,觸發缺頁異常,具體将哪些實體頁映射給使用者程序是核心考慮的事情。在使用者空間中沒有高端記憶體這個概念

繼續閱讀