Linux 記憶體管理 — 高端記憶體的映射方式

來源:互聯網
上載者:User

 

高端記憶體是指物理地址大於 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() 可實現臨時映射。

 

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

 

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

高端記憶體含義為:線性地址空間 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線性地址不僅僅是用在這些地方的,如果你要載入一個裝置,而這個裝置需要映射其記憶體到核心中,它也需要使用這段線性地址空間來完成,否則核心就不能訪問裝置上的記憶體空間了。

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

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

 

 

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

以下討論僅限i386平台,一般考慮典型情況

  1. linux核心對整個系統的實體記憶體是通過類型為struct page的數組mem_map來管理的。系統中的夥伴系統分配演算法最終是通過操作這個數組來記錄實體記憶體的分配、回收等操作。在這裡不要被系統的高端記憶體、低端記憶體等概念搞混淆了,高、低端記憶體的分類主要在於區分實體記憶體地址是否可以直接映射到核心線性地址空間中。

我們知道,linux的核心地址空間大小為1G(使用者空間0~3G,核心空間3G~4G,這種分法最常見),因此如果把這1G線性地址空間全部拿來直接一一映射實體記憶體的話,在核心態的所有進程(線程)能使用的實體記憶體總共最多隻有1G,為了能使在核心態的所有進程能使用更多的實體記憶體,linux採取了一種變通的形式:它將1G核心線性地址空間分為幾部分,第一部分為1G的前896M,這部分核心線性空間與實體記憶體的0~896M一一映射(相差一個為0xc0000000的常數),後面128M的線性空間拿來動態映射剩下的所有實體記憶體,由於動態映射的方法不一樣,後面的128M又分成了幾個部分,有興趣的可以查看相關資料。在這裡,前面896M線性空間對應的實體記憶體就是所謂的低端實體記憶體,剩下的實體記憶體就是高端實體記憶體。

從上面高、低端實體記憶體命名的由來我們可以知道,高、低端實體記憶體與具體的記憶體配置演算法無關,它們都是被mem_map數組控制起來,再由夥伴分配系統實施管理。

 

  1. 關於進程及其記憶體配置

首先要明白一個概念:進程中使用的所有地址都是虛地址,在linux下這個虛地址就是所謂的線性地址。linux中進程可運行在使用者態和核心態,(典型配置情況下)當進程運行在使用者態時,它使用的線性地址只能位於0~3G範圍內,當進程運行於核心態時,它使用的線性地址位址範圍為3G~4G。

為了把線性地址轉化為物理地址,每個進程都有自己私人的頁目錄和頁表。linux在建立進程頁目錄時,把使用者地址空間的頁目錄項(0~767項)清空而將核心頁目錄表(swapper_pg_dir)的第768項到1023項拷貝到進程的頁目錄表的第768項到1023項中。由於核心在初始化時也只映射了實體記憶體的前896M,我們可以知道核心也目錄表只能保證第768項開始的224項中有有效映射。從這裡我們可以知道,所有的進程都共用了其核心線性地址空間。

當一個進程在核心空間發生缺頁故障的時候,這主要發生在訪問核心空間動態映射區線性地址,在其處理常式中,就要通過0號進程的頁目錄(swapper_pg_dir)來同步本進程的核心頁目錄,實際上就是拷貝0號進程的核心頁目錄到本進程中(核心頁表與進程0共用,故不需要複製)。如果進程0的該地址處的核心頁目錄也不存在,則出錯,具體代碼可以參考vmalloc的實現源碼。

當進程運行於使用者態時,若其需要申請記憶體空間,核心首先會在其使用者線性空間中分配需要的線性地址空間,再通過夥伴分配系統分配實體記憶體並把分配的實體記憶體跟使用者空間線性地址映射起來,最後再修改進程的頁目錄項及頁表項寫入這些映射關係。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.