1. 程式人生 > >Linux記憶體描述之記憶體區域zone--Linux記憶體管理(三)

Linux記憶體描述之記憶體區域zone--Linux記憶體管理(三)

1 記憶體管理域zone

為了支援NUMA模型,也即CPU對不同記憶體單元的訪問時間可能不同,此時系統的實體記憶體被劃分為幾個節點(node), 一個node對應一個記憶體簇bank,即每個記憶體簇被認為是一個節點

  • 首先, 記憶體被劃分為結點. 每個節點關聯到系統中的一個處理器, 核心中表示為pg_data_t的例項. 系統中每個節點被連結到一個以NULL結尾的pgdat_list連結串列中,而其中的每個節點利用pg_data_tnode_next欄位連結到下一節.而對於PC這種UMA結構的機器來說, 只使用了一個成為contig_page_data的靜態pg_data_t結構.
  • 接著各個節點又被劃分為記憶體管理區域, 一個管理區域通過struct zone_struct描述, 其被定義為zone_t, 用以表示記憶體的某個範圍, 低端範圍的16MB被描述為ZONE_DMA, 某些工業標準體系結構中的(ISA)裝置需要用到它, 然後是可直接對映到核心的普通記憶體域ZONE_NORMAL,最後是超出了核心段的實體地址域ZONE_HIGHMEM, 被稱為高階記憶體. 是系統中預留的可用記憶體空間, 不能被核心直接對映.

下面我們就來詳解講講記憶體管理域的內容zone

2 為什麼要將記憶體node分成不同的區域zone

NUMA結構下, 每個處理器CPU與一個本地記憶體直接相連, 而不同處理器之前則通過匯流排進行進一步的連線, 因此相對於任何一個CPU訪問本地記憶體的速度比訪問遠端記憶體的速度要快, 而Linux為了相容NUMAJ結構, 把實體記憶體相依照CPU的不同node分成簇, 一個CPU-node對應一個本地記憶體pgdata_t.

這樣已經很好的表示實體記憶體了, 在一個理想的計算機系統中, 一個頁框就是一個記憶體的分配單元, 可用於任何事情:存放核心資料, 使用者資料和緩衝磁碟資料等等. 任何種類的資料頁都可以存放在任頁框中, 沒有任何限制.

但是Linux核心又把各個實體記憶體節點分成個不同的管理區域zone, 這是為什麼呢?

因為實際的計算機體系結構有硬體的諸多限制, 這限制了頁框可以使用的方式. 尤其是, Linux核心必須處理80x86體系結構的兩種硬體約束.

  • ISA匯流排的直接記憶體儲存DMA處理器有一個嚴格的限制 : 他們只能對RAM的前16MB進行定址
  • 在具有大容量RAM的現代32位計算機中, CPU不能直接訪問所有的實體地址, 因為線性地址空間太小, 核心不可能直接對映所有實體記憶體到線性地址空間, 我們會在後面典型架構(x86)上記憶體區域劃分詳細講解x86_32上的記憶體區域劃分

因此Linux核心對不同區域的記憶體需要採用不同的管理方式和對映方式, 因此核心將實體地址或者成用zone_t表示的不同地址區域

3 記憶體管理區型別zone_type

前面我們說了由於硬體的一些約束, 低端的一些地址被用於DMA, 而在實際記憶體大小超過了核心所能使用的現行地址的時候, 一些高地址處的實體地址不能簡單持久的直接對映到核心空間. 因此核心將記憶體的節點node分成了不同的記憶體區域方便管理和對映.

Linux使用enum zone_type來標記核心所支援的所有記憶體區域

3.1 記憶體區域型別zone_type

zone_type結構定義在include/linux/mmzone.h, 其基本資訊如下所示

enum zone_type
{
#ifdef CONFIG_ZONE_DMA
    ZONE_DMA,
#endif

#ifdef CONFIG_ZONE_DMA32

    ZONE_DMA32,
#endif

    ZONE_NORMAL,

#ifdef CONFIG_HIGHMEM
    ZONE_HIGHMEM,
#endif
    ZONE_MOVABLE,
#ifdef CONFIG_ZONE_DEVICE
    ZONE_DEVICE,
#endif
    __MAX_NR_ZONES

};

不同的管理區的用途是不一樣的,ZONE_DMA型別的記憶體區域在實體記憶體的低端,主要是ISA裝置只能用低端的地址做DMA操作。ZONE_NORMAL型別的記憶體區域直接被核心對映到線性地址空間上面的區域(line address space),ZONE_HIGHMEM將保留給系統使用,是系統中預留的可用記憶體空間,不能被核心直接對映。

3.2 不同的記憶體區域的作用

在記憶體中,每個簇所對應的node又被分成的稱為管理區(zone)的塊,它們各自描述在記憶體中的範圍。一個管理區(zone)由struct zone結構體來描述,在linux-2.4.37之前的核心中是用typedef struct zone_struct zone_t資料結構來描述)

管理區的型別用zone_type表示, 有如下幾種

管理記憶體域 描述
ZONE_DMA 標記了適合DMA的記憶體域. 該區域的長度依賴於處理器型別. 這是由於古老的ISA裝置強加的邊界. 但是為了相容性, 現代的計算機也可能受此影響
ZONE_DMA32 標記了使用32位地址字可定址, 適合DMA的記憶體域. 顯然, 只有在53位系統中ZONE_DMA32才和ZONE_DMA有區別, 在32位系統中, 本區域是空的, 即長度為0MB, 在Alpha和AMD64系統上, 該記憶體的長度可能是從0到4GB
ZONE_NORMAL 標記了可直接對映到記憶體段的普通記憶體域. 這是在所有體系結構上保證會存在的唯一記憶體區域, 但無法保證該地址範圍對應了實際的實體地址. 例如, 如果AMD64系統只有兩2G記憶體, 那麼所有的記憶體都屬於ZONE_DMA32範圍, 而ZONE_NORMAL則為空
ZONE_HIGHMEM 標記了超出核心虛擬地址空間的實體記憶體段, 因此這段地址不能被核心直接對映
ZONE_MOVABLE 核心定義了一個偽記憶體域ZONE_MOVABLE, 在防止實體記憶體碎片的機制memory migration中需要使用該記憶體域. 供防止實體記憶體碎片的極致使用
ZONE_DEVICE 為支援熱插拔裝置而分配的Non Volatile Memory非易失性記憶體
MAX_NR_ZONES 充當結束標記, 在核心中想要迭代系統中所有記憶體域, 會用到該常亮

根據編譯時候的配置, 可能無需考慮某些記憶體域. 如在64位系統中, 並不需要高階記憶體, 因為AM64的linux採用4級頁表,支援的最大實體記憶體為64TB, 對於虛擬地址空間的劃分,將0x0000,0000,0000,0000 – 0x0000,7fff,ffff,f000這128T地址用於使用者空間;而0xffff,8000,0000,0000以上的128T為系統空間地址, 這遠大於當前我們系統中的記憶體空間, 因此所有的實體地址都可以直接對映到核心中, 不需要高階記憶體的特殊對映. 可以參見Documentation/x86/x86_64/mm.txt

ZONE_MOVABLE和ZONE_DEVICE其實是和其他的ZONE的用途有異,

  • ZONE_MOVABLE在防止實體記憶體碎片的機制中需要使用該記憶體區域,
  • ZONE_DEVICE筆者也第一次知道了,理解有錯的話歡迎大家批評指正, 這個應該是為支援熱插拔裝置而分配的Non Volatile Memory非易失性記憶體,

關於ZONE_DEVICE, 具體的資訊可以參見[ATCH v2 3/9mm: ZONE_DEVICE for “device memory”

While pmem is usable as a block device or via DAX mappings to userspace
there are several usage scenarios that can not target pmem due to its
lack of struct page coverage. In preparation for “hot plugging” pmem
into the vmemmap add ZONE_DEVICE as a new zone to tag these pages
separately from the ones that are subject to standard page allocations.
Importantly “device memory” can be removed at will by userspace
unbinding the driver of the device.

3.3 典型架構(x86)上記憶體區域劃分

對於x86機器,管理區(記憶體區域)型別如下分佈

型別 區域
ZONE_DMA 0~15MB
ZONE_NORMAL 16MB~895MB
ZONE_HIGHMEM 896MB~實體記憶體結束

而由於32位系統中, Linux核心虛擬地址空間只有1G, 而0~895M這個986MB被用於DMA和直接對映, 剩餘的實體記憶體被成為高階記憶體. 那核心是如何藉助剩餘128MB高階記憶體地址空間是如何實現訪問可以所有實體記憶體?

當核心想訪問高於896MB實體地址記憶體時,從0xF8000000 ~ 0xFFFFFFFF地址空間範圍內找一段相應大小空閒的邏輯地址空間,借用一會。借用這段邏輯地址空間,建立對映到想訪問的那段實體記憶體(即填充核心PTE頁面表),臨時用一會,用完後歸還。這樣別人也可以借用這段地址空間訪問其他實體記憶體,實現了使用有限的地址空間,訪問所有所有實體記憶體

關於高階記憶體的內容, 我們後面會專門抽出一章進行講解

因此, 傳統和X86_32位系統中, 前16M劃分給ZONE_DMA, 該區域包含的頁框可以由老式的基於ISAS的裝置通過DMA使用”直接記憶體訪問(DMA)”, ZONE_DMA和ZONE_NORMAL區域包含了記憶體的常規頁框, 通過把他們線性的對映到現行地址的第4個GB, 核心就可以直接進行訪問, 相反ZONE_HIGHME包含的記憶體頁不能由核心直接訪問, 儘管他們也線性地對映到了現行地址空間的第4個GB. 在64位體系結構中, 線性地址空間的大小遠遠好過了系統的實際實體地址, 核心可知直接將所有的實體記憶體對映到線性地址空間, 因此64位體系結構上ZONE_HIGHMEM區域總是空的.

4 管理區結構zone_t

一個管理區(zone)由struct zone結構體來描述(linux-3.8~目前linux4.5),而在linux-2.4.37之前的核心中是用struct zone_struct資料結構來描述), 他們都通過typedef被重定義為zone_t型別

zone物件用於跟蹤諸如頁面使用情況的統計數, 空閒區域資訊和鎖資訊

裡面儲存著記憶體使用狀態資訊,如page使用統計, 未使用的記憶體區域,互斥訪問的鎖(LOCKS)等.

4.1 struct zone管理域資料結構

struct zone在linux/mmzone.h中定義, 在linux-4.7的核心中可以使用include/linux/mmzone.h來檢視其定義

struct zone
{
    /* Read-mostly fields */

    /* zone watermarks, access with *_wmark_pages(zone) macros */
    unsigned long watermark[NR_WMARK];

    unsigned long nr_reserved_highatomic;

    /*
     * We don't know if the memory that we're going to allocate will be
     * freeable or/and it will be released eventually, so to avoid totally
     * wasting several GB of ram we must reserve some of the lower zone
     * memory (otherwise we risk to run OOM on the lower zones despite
     * there being tons of freeable ram on the higher zones).  This array is
     * recalculated at runtime if the sysctl_lowmem_reserve_ratio sysctl
     * changes.
     * 分別為各種記憶體域指定了若干頁
     * 用於一些無論如何都不能失敗的關鍵性記憶體分配。
     */
    long lowmem_reserve[MAX_NR_ZONES];

#ifdef CONFIG_NUMA
    int node;
#endif

    /*
     * The target ratio of ACTIVE_ANON to INACTIVE_ANON pages on
     * this zone's LRU.  Maintained by the pageout code.
     * 不活動頁的比例,
     * 接著是一些很少使用或者大部分情況下是隻讀的欄位:
     * wait_table wait_table_hash_nr_entries wait_table_bits
     * 形成等待列隊,可以等待某一頁可供程序使用  */
    unsigned int inactive_ratio;

    /*  指向這個zone所在的pglist_data物件  */
    struct pglist_data      *zone_pgdat;
    /*/這個陣列用於實現每個CPU的熱/冷頁幀列表。核心使用這些列表來儲存可用於滿足實現的“新鮮”頁。但冷熱頁幀對應的快取記憶體狀態不同:有些頁幀很可能在快取記憶體中,因此可以快速訪問,故稱之為熱的;未快取的頁幀與此相對,稱之為冷的。*/
    struct per_cpu_pageset __percpu *pageset;

    /*
     * This is a per-zone reserve of pages that are not available
     * to userspace allocations.
     * 每個區域保留的不能被使用者空間分配的頁面數目
     */
    unsigned long       totalreserve_pages;

#ifndef CONFIG_SPARSEMEM
    /*
     * Flags for a pageblock_nr_pages block. See pageblock-flags.h.
     * In SPARSEMEM, this map is stored in struct mem_section
     */
    unsigned long       *pageblock_flags;
#endif /* CONFIG_SPARSEMEM */

#ifdef CONFIG_NUMA
    /*
     * zone reclaim becomes active if more unmapped pages exist.
     */
    unsigned long       min_unmapped_pages;
    unsigned long       min_slab_pages;
#endif /* CONFIG_NUMA */

    /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT
     * 只記憶體域的第一個頁幀 */
    unsigned long       zone_start_pfn;

    /*
     * spanned_pages is the total pages spanned by the zone, including
     * holes, which is calculated as:
     *      spanned_pages = zone_end_pfn - zone_start_pfn;
     *
     * present_pages is physical pages existing within the zone, which
     * is calculated as:
     *      present_pages = spanned_pages - absent_pages(pages in holes);
     *
     * managed_pages is present pages managed by the buddy system, which
     * is calculated as (reserved_pages includes pages allocated by the
     * bootmem allocator):
     *      managed_pages = present_pages - reserved_pages;
     *
     * So present_pages may be used by memory hotplug or memory power
     * management logic to figure out unmanaged pages by checking
     * (present_pages - managed_pages). And managed_pages should be used
     * by page allocator and vm scanner to calculate all kinds of watermarks
     * and thresholds.
     *
     * Locking rules:
     *
     * zone_start_pfn and spanned_pages are protected by span_seqlock.
     * It is a seqlock because it has to be read outside of zone->lock,
     * and it is done in the main allocator path.  But, it is written
     * quite infrequently.
     *
     * The span_seq lock is declared along with zone->lock because it is
     * frequently read in proximity to zone->lock.  It's good to
     * give them a chance of being in the same cacheline.
     *
     * Write access to present_pages at runtime should be protected by
     * mem_hotplug_begin/end(). Any reader who can't tolerant drift of
     * present_pages should get_online_mems() to get a stable value.
     *
     * Read access to managed_pages should be safe because it's unsigned
     * long. Write access to zone->managed_pages and totalram_pages are
     * protected by managed_page_count_lock at runtime. Idealy only
     * adjust_managed_page_count() should be used instead of directly
     * touching zone->managed_pages and totalram_pages.
     */
    unsigned long       managed_pages;
    unsigned long       spanned_pages;             /*  總頁數,包含空洞  */
    unsigned long       present_pages;              /*  可用頁數,不包哈空洞  */

    /*  指向管理區的傳統名字, "DMA", "NROMAL"或"HIGHMEM" */
    const char          *name;

#ifdef CONFIG_MEMORY_ISOLATION
    /*
     * Number of isolated pageblock. It is used to solve incorrect
     * freepage counting problem due to racy retrieving migratetype
     * of pageblock. Protected by zone->lock.
     */
    unsigned long       nr_isolate_pageblock;
#endif

#ifdef CONFIG_MEMORY_HOTPLUG
    /* see spanned/present_pages for more description */
    seqlock_t           span_seqlock;
#endif

    /*
     * wait_table       -- the array holding the hash table
     * wait_table_hash_nr_entries   -- the size of the hash table array
     * wait_table_bits      -- wait_table_size == (1 << wait_table_bits)
     *
     * The purpose of all these is to keep track of the people
     * waiting for a page to become available and make them
     * runnable again when possible. The trouble is that this
     * consumes a lot of space, especially when so few things
     * wait on pages at a given time. So instead of using
     * per-page waitqueues, we use a waitqueue hash table.
     *
     * The bucket discipline is to sleep on the same queue when
     * colliding and wake all in that wait queue when removing.
     * When something wakes, it must check to be sure its page is
     * truly available, a la thundering herd. The cost of a
     * collision is great, but given the expected load of the
     * table, they should be so rare as to be outweighed by the
     * benefits from the saved space.
     *
     * __wait_on_page_locked() and unlock_page() in mm/filemap.c, are the
     * primary users of these fields, and in mm/page_alloc.c
     * free_area_init_core() performs the initialization of them.
     */
    /*  程序等待佇列的散列表, 這些程序正在等待管理區中的某頁  */
    wait_queue_head_t       *wait_table;
    /*  等待佇列散列表中的排程實體數目  */
    unsigned long       wait_table_hash_nr_entries;
    /*  等待佇列散列表陣列大小, 值為2^order  */
    unsigned long       wait_table_bits;

    ZONE_PADDING(_pad1_)

    /* free areas of different sizes
       頁面使用狀態的資訊,以每個bit標識對應的page是否可以分配
       是用於夥伴系統的,每個陣列元素指向對應階也表的陣列開頭
       以下是供頁幀回收掃描器(page reclaim scanner)訪問的欄位
       scanner會跟據頁幀的活動情況對記憶體域中使用的頁進行編目
       如果頁幀被頻繁訪問,則是活動的,相反則是不活動的,
       在需要換出頁幀時,這樣的資訊是很重要的:   */
    struct free_area    free_area[MAX_ORDER];

    /* zone flags, see below 描述當前記憶體的狀態, 參見下面的enum zone_flags結構 */
    unsigned long       flags;

    /* Write-intensive fields used from the page allocator, 儲存該描述符的自旋鎖  */
    spinlock_t          lock;

    ZONE_PADDING(_pad2_)

    /* Write-intensive fields used by page reclaim */

    /* Fields commonly accessed by the page reclaim scanner */
    spinlock_t          lru_lock;   /* LRU(最近最少使用演算法)活動以及非活動連結串列使用的自旋鎖  */
    struct lruvec       lruvec;

    /*
     * When free pages are below this point, additional steps are taken
     * when reading the number of free pages to avoid per-cpu counter
     * drift allowing watermarks to be breached
     * 在空閒頁的數目少於這個點percpu_drift_mark的時候
     * 當讀取和空閒頁數一樣的記憶體頁時,系統會採取額外的工作,
     * 防止單CPU頁數漂移,從而導致水印被破壞。
     */
    unsigned long percpu_drift_mark;

#if defined CONFIG_COMPACTION || defined CONFIG_CMA
    /* pfn where compaction free scanner should start */
    unsigned long       compact_cached_free_pfn;
    /* pfn where async and sync compaction migration scanner should start */
    unsigned long       compact_cached_migrate_pfn[2];
#endif

#ifdef CONFIG_COMPACTION
    /*
     * On compaction failure, 1<<compact_defer_shift compactions
     * are skipped before trying again. The number attempted since
     * last failure is tracked with compact_considered.
     */
    unsigned int        compact_considered;
    unsigned int        compact_defer_shift;
    int                       compact_order_failed;
#endif

#if defined CONFIG_COMPACTION || defined CONFIG_CMA
    /* Set to true when the PG_migrate_skip bits should be cleared */
    bool            compact_blockskip_flush;
#endif

    bool            contiguous;

    ZONE_PADDING(_pad3_)
    /* Zone statistics 記憶體域的統計資訊, 參見後面的enum zone_stat_item結構 */
    atomic_long_t       vm_stat[NR_VM_ZONE_STAT_ITEMS];
} ____cacheline_internodealigned_in_smp;
欄位 描述
watermark 每個 zone 在系統啟動時會計算出 3 個水位值, 分別為 WMAKR_MIN, WMARK_LOW, WMARK_HIGH 水位, 這在頁面分配器和 kswapd 頁面回收中會用到
lowmem_reserve[MAX_NR_ZONES] zone 中預留的記憶體, 為了防止一些程式碼必須執行在低地址區域,所以事先保留一些低地址區域的記憶體
pageset page管理的資料結構物件,內部有一個page的列表(list)來管理。每個CPU維護一個page list,避免自旋鎖的衝突。這個陣列的大小和NR_CPUS(CPU的數量)有關,這個值是編譯的時候確定的
lock 對zone併發訪問的保護的自旋鎖
free_area[MAX_ORDER] 頁面使用狀態的資訊,以每個bit標識對應的page是否可以分配
lru_lock LRU(最近最少使用演算法)的自旋鎖
wait_table 待一個page釋放的等待佇列雜湊表。它會被wait_on_page(),unlock_page()函式使用. 用雜湊表,而不用一個等待佇列的原因,防止程序長期等待資源
wait_table_hash_nr_entries 雜湊表中的等待佇列的數量
zone_pgdat 指向這個zone所在的pglist_data物件
zone_start_pfn 和node_start_pfn的含義一樣。這個成員是用於表示zone中的開始那個page在實體記憶體中的位置的present_pages, spanned_pages: 和node中的類似的成員含義一樣
name zone的名字,字串表示: “DMA”,”Normal” 和”HighMem”
totalreserve_pages 每個區域保留的不能被使用者空間分配的頁面數目
ZONE_PADDING 由於自旋鎖頻繁的被使用,因此為了效能上的考慮,將某些成員對齊到cache line中,有助於提高執行的效能。使用這個巨集,可以確定zone->lock,zone->lru_lock,zone->pageset這些成員使用不同的cache line.
managed_pages zone 中被夥伴系統管理的頁面數量
spanned_pages zone 中包含的頁面數量
present_pages zone 中實際管理的頁面數量. 對一些體系結構來說, 其值和 spanned_pages 相等
lruvec LRU 連結串列集合
vm_stat zone 計數

4.2 ZONE_PADDING將資料儲存在高速緩衝行

該結構比較特殊的地方是它由ZONE_PADDING分隔的幾個部分. 這是因為堆zone結構的訪問非常頻繁.在多處理器系統中, 通常會有不同的CPU試圖同時訪問結構成員. 因此使用鎖可以防止他們彼此干擾, 避免錯誤和不一致的問題. 由於核心堆該結構的訪問非常頻繁, 因此會經常性地獲取該結構的兩個自旋鎖zone->lockzone->lru_lock

由於 struct zone 結構經常被訪問到, 因此這個資料結構要求以 L1 Cache 對齊. 另外, 這裡的 ZONE_PADDING( )zone->lockzone_lru_lock 這兩個很熱門的鎖可以分佈在不同的 Cahe Line 中.一個記憶體 node 節點最多也就幾個 zone, 因此 zone 資料結構不需要像 struct page 一樣關心資料結構的大小,

那麼資料儲存在CPU快取記憶體中, 那麼會處理得更快速. 高速緩衝分為行, 每一行負責不同的記憶體區.核心使用ZONE_PADDING巨集生成”填充”欄位新增到結構中, 以確保每個自旋鎖處於自身的快取行中

ZONE_PADDING巨集定義在include/linux/mmzone.h?v4.7, line 105

/*
 * zone->lock and zone->lru_lock are two of the hottest locks in the kernel.
 * So add a wild amount of padding here to ensure that they fall into separate
 * cachelines.  There are very few zone structures in the machine, so space
 * consumption is not a concern here.
     */
#if defined(CONFIG_SMP)
    struct zone_padding
    {
            char x[0];
    } ____cacheline_internodealigned_in_smp;
    #define ZONE_PADDING(name)      struct zone_padding name;

#else
    #define ZONE_PADDING(name)
 #endif

核心還用了____cacheline_internodealigned_in_smp,來實現最優的快取記憶體行對其方式.

該巨集定義在include/linux/cache.h

#if !defined(____cacheline_internodealigned_in_smp)
    #if defined(CONFIG_SMP)
        #define ____cacheline_internodealigned_in_smp \
        __attribute__((__aligned__(1 << (INTERNODE_CACHE_SHIFT))))
    #else
        #define ____cacheline_internodealigned_in_smp
    #endif
#endif

4.3 水印watermark[NR_WMARK]與kswapd核心執行緒

Zone的管理排程的一些引數watermarks水印, 水存量很小(MIN)進水量,水存量達到一個標準(LOW)減小進水量,當快要滿(HIGH)的時候,可能就關閉了進水口

WMARK_LOW, WMARK_LOW, WMARK_HIGH就是這個標準

enum zone_watermarks
{
        WMARK_MIN,
        WMARK_LOW,
        WMARK_HIGH,
        NR_WMARK
};


#define min_wmark_pages(z) (z->watermark[WMARK_MIN])
#define low_wmark_pages(z) (z->watermark[WMARK_LOW])
#define high_wmark_pages(z) (z->watermark[WMARK_HIGH])

在linux-2.4中, zone結構中使用如下方式表示水印, 參照include/linux/mmzone.h?v=2.4.37, line 171

typedef struct zone_watermarks_s
{
    unsigned long min, low, high;
} zone_watermarks_t;


typedef struct zone_struct {
    zone_watermarks_t       watermarks[MAX_NR_ZONES];

在Linux-2.6.x中標準是直接通過成員pages_min, pages_low and pages_high定義在zone結構體中的, 參照include/linux/mmzone.h?v=2.6.24, line 214

當系統中可用記憶體很少的時候,系統程序kswapd被喚醒, 開始回收釋放page, 水印這些引數(WMARK_MIN, WMARK_LOW, WMARK_HIGH)影響著這個程式碼的行為

每個zone有三個水平標準:watermark[WMARK_MIN], watermark[WMARK_LOW], watermark[WMARK_HIGH],幫助確定zone中記憶體分配使用的壓力狀態

標準 描述
watermark[WMARK_MIN] 當空閒頁面的數量達到page_min所標定的數量的時候, 說明頁面數非常緊張, 分配頁面的動作和kswapd執行緒同步執行.
WMARK_MIN所表示的page的數量值,是在記憶體初始化的過程中呼叫free_area_init_core中計算的。這個數值是根據zone中的page的數量除以一個>1的係數來確定的。通常是這樣初始化的ZoneSizeInPages/12
watermark[WMARK_LOW] 當空閒頁面的數量達到WMARK_LOW所標定的數量的時候,說明頁面剛開始緊張, 則kswapd執行緒將被喚醒,並開始釋放回收頁面
watermark[WMARK_HIGH] 當空閒頁面的數量達到page_high所標定的數量的時候, 說明記憶體頁面數充足, 不需要回收, kswapd執行緒將重新休眠,通常這個數值是page_min的3倍
  • 如果空閒頁多於pages_high = watermark[WMARK_HIGH], 則說明記憶體頁面充足, 記憶體域的狀態是理想的.
  • 如果空閒頁的數目低於pages_low = watermark[WMARK_LOW], 則說明記憶體頁面開始緊張, 核心開始將頁回收到硬碟.
  • 如果空閒頁的數目低於pages_min = watermark[WMARK_MIN], 則記憶體頁面非常緊張, 頁回收工作的壓力就比較大

4.3 記憶體域標誌

記憶體管理域zone_t結構中的flags欄位描述了記憶體域的當前狀態

//  http://lxr.free-electrons.com/source/include/linux/mmzone.h#L475
struct zone
{
    /* zone flags, see below */
    unsigned long           flags;
}

它允許使用的標識用enum zone_flags標識, 該列舉標識定義在include/linux/mmzone.h?v4.7, line 525, 如下所示

enum zone_flags
{
    ZONE_RECLAIM_LOCKED,         /* prevents concurrent reclaim */
    ZONE_OOM_LOCKED,               /* zone is in OOM killer zonelist 記憶體域可被回收*/
    ZONE_CONGESTED,                 /* zone has many dirty pages backed by
                                                    * a congested BDI
                                                    */
    ZONE_DIRTY,                           /* reclaim scanning has recently found
                                                   * many dirty file pages at the tail
                                                   * of the LRU.
                                                   */
    ZONE_WRITEBACK,                 /* reclaim scanning has recently found
                                                   * many pages under writeback
                                                   */
    ZONE_FAIR_DEPLETED,           /* fair zone policy batch depleted */
};
flag標識 描述
ZONE_RECLAIM_LOCKED 防止併發回收, 在SMP上系統, 多個CPU可能試圖併發的回收億i個記憶體域. ZONE_RECLAIM_LCOKED標誌可防止這種情況: 如果一個CPU在回收某個記憶體域, 則設定該標識. 這防止了其他CPU的嘗試
ZONE_OOM_LOCKED 用於某種不走運的情況: 如果程序消耗了大量的記憶體, 致使必要的操作都無法完成, 那麼核心會使徒殺死消耗記憶體最多的程序, 以獲取更多的空閒頁, 該標誌可以放置多個CPU同時進行這種操作
ZONE_CONGESTED 標識當前區域中有很多髒頁
ZONE_DIRTY 用於標識最近的一次頁面掃描中, LRU演算法發現了很多髒的頁面
ZONE_WRITEBACK 最近的回收掃描發現有很多頁在寫回
ZONE_FAIR_DEPLETED 公平區策略耗盡(沒懂)

4.4 記憶體域統計資訊vm_stat

記憶體域struct zone的vm_stat維護了大量有關該記憶體域的統計資訊. 由於其中維護的大部分資訊曲面沒有多大意義

//  http://lxr.free-electrons.com/source/include/linux/mmzone.h#L522
struct zone
{
      atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
}

vm_stat的統計資訊由enum zone_stat_item列舉變數標識, 定義在include/linux/mmzone.h?v=4.7, line 110

enum zone_stat_item
{
    /* First 128 byte cacheline (assuming 64 bit words) */
    NR_FREE_PAGES,
    NR_ALLOC_BATCH,
    NR_LRU_BASE,
    NR_INACTIVE_ANON = NR_LRU_BASE, /* must match order of LRU_[IN]ACTIVE */
    NR_ACTIVE_ANON,         /*  "     "     "   "       "         */
    NR_INACTIVE_FILE,       /*  "     "     "   "       "         */
    NR_ACTIVE_FILE,         /*  "     "     "   "       "         */
    NR_UNEVICTABLE,         /*  "     "     "   "       "         */
    NR_MLOCK,               /* mlock()ed pages found and moved off LRU */
    NR_ANON_PAGES,  /* Mapped anonymous pages */
    NR_FILE_MAPPED, /* pagecache pages mapped into pagetables.
                       only modified from process context */
    NR_FILE_PAGES,
    NR_FILE_DIRTY,
    NR_WRITEBACK,
    NR_SLAB_RECLAIMABLE,
    NR_SLAB_UNRECLAIMABLE,
    NR_PAGETABLE,           /* used for pagetables */
    NR_KERNEL_STACK,
    /* Second 128 byte cacheline */
    NR_UNSTABLE_NFS,        /* NFS unstable pages */
    NR_BOUNCE,
    NR_VMSCAN_WRITE,
    NR_VMSCAN_IMMEDIATE,    /* Prioritise for reclaim when writeback ends */
    NR_WRITEBACK_TEMP,      /* Writeback using temporary buffers */
    NR_ISOLATED_ANON,       /* Temporary isolated pages from anon lru */
    NR_ISOLATED_FILE,       /* Temporary isolated pages from file lru */
    NR_SHMEM,               /* shmem pages (included tmpfs/GEM pages) */
    NR_DIRTIED,             /* page dirtyings since bootup */
    NR_WRITTEN,             /* page writings since bootup */
    NR_PAGES_SCANNED,       /* pages scanned since last reclaim */
#ifdef CONFIG_NUMA
    NUMA_HIT,               /* allocated in intended node */
    NUMA_MISS,              /* allocated in non intended node */
    NUMA_FOREIGN,           /* was intended here, hit elsewhere */
    NUMA_INTERLEAVE_HIT,    /* interleaver preferred this zone */
    NUMA_LOCAL,             /* allocation from local node */
    NUMA_OTHER,             /* allocation from other node */
#endif
    WORKINGSET_REFAULT,
    WORKINGSET_ACTIVATE,
    WORKINGSET_NODERECLAIM,
    NR_ANON_TRANSPARENT_HUGEPAGES,
    NR_FREE_CMA_PAGES,
    NR_VM_ZONE_STAT_ITEMS
};

核心提供了很多方式來獲取當前記憶體域的狀態資訊, 這些函式大多定義在include/linux/vmstat.h?v=4.7

4.5 Zone等待隊列表(zone wait queue table)

struct zone中實現了一個等待佇列, 可用於等待某一頁的程序, 核心將程序排成一個列隊, 等待某些條件. 在條件變成真時, 核心會通知程序恢復工作.

struct zone
{
    wait_queue_head_t       *wait_table;
    unsigned long               wait_table_hash_nr_entries;
    unsigned long               wait_table_bits;
}
欄位 描述
wait_table 待一個page釋放的等待佇列雜湊表。它會被wait_on_page(),unlock_page()函式使用. 用雜湊表,而不用一個等待佇列的原因,防止程序長期等待資源
wait_table_hash_nr_entries 雜湊表中的等待佇列的數量
wait_table_bits 等待佇列散列表陣列大小, wait_table_size == (1 << wait_table_bits)

當對一個page做I/O操作的時候,I/O操作需要被鎖住,防止不正確的資料被訪問。程序在訪問page前,wait_on_page_locked函式,使程序加入一個等待佇列

訪問完成後,UnlockPage函式解鎖其他程序對page的訪問。其他正在等待佇列中的程序被喚醒。每個page都可以有一個等待佇列,但是太多的分離的等待佇列使得花費太多的記憶體訪問週期。替代的解決方法,就是將所有的佇列放在struct zone資料結構中

也可以有一種可能,就是struct zone中只有一個佇列,但是這就意味著,當一個page unlock的時候,訪問這個zone裡記憶體page的所有休眠的程序將都被喚醒,這樣就會出現擁堵(thundering herd)的問題。建立一個雜湊表管理多個等待佇列,能解決這個問題,zone->wait_table就是這個雜湊表。雜湊表的方法可能還是會造成一些程序不必要的喚醒。但是這種事情發生的機率不是很頻繁的。下面這個圖就是程序及等待佇列的執行關係:

等待佇列的雜湊表的分配和建立在free_area_init_core函式中進行。雜湊表的表項的數量在wait_table_size() 函式中計算,並且保持在zone->wait_table_size成員中。最大4096個等待佇列。最小是NoPages / PAGES_PER_WAITQUEUE的2次方,NoPages是zone管理的page的數量,PAGES_PER_WAITQUEUE被定義256

zone->wait_table_bits用於計算:根據page 地址得到需要使用的等待佇列在雜湊表中的索引的演算法因子. page_waitqueue()函式負責返回zone中page所對應等待佇列。它用一個基於struct page虛擬地址的簡單的乘法雜湊演算法來確定等待佇列的.

page_waitqueue()函式用GOLDEN_RATIO_PRIME的地址和“右移zone→wait_table_bits一個索引值”的一個乘積來確定等待佇列在雜湊表中的索引的。

Zone的初始化, 在kernel page table通過paging_init()函式完全建立起z來以後,zone被初始化。下面章節將描述這個。當然不同的體系結構這個過程肯定也是不一樣的,但它們的目的卻是相同的:確定什麼引數需要傳遞給free_area_init()函式(對於UMA體系結構)或者free_area_init_node()函式(對於NUMA體系結構)。這裡省略掉NUMA體系結構的說明。

free_area_init()函式的引數:

unsigned long *zones_sizes: 系統中每個zone所管理的page的數量的陣列。這個時候,還沒能確定zone中那些page是可以分配使用的(free)。這個資訊知道boot memory allocator完成之前還無法知道。

4.6 冷熱頁與Per-CPU上的頁面快取記憶體

核心經常請求和釋放單個頁框. 為了提升效能, 每個記憶體管理區都定義了一個每CPU(Per-CPU)的頁面快取記憶體. 所有”每CPU快取記憶體”包含一些預先分配的頁框, 他們被定義滿足本地CPU發出的單一記憶體請求.

struct zone的pageset成員用於實現冷熱分配器(hot-n-cold allocator)

struct zone
{
    struct per_cpu_pageset __percpu *pageset;
};

核心說頁面是熱的, 意味著頁面已經載入到CPU的快取記憶體, 與在記憶體中的頁相比, 其資料訪問速度更快. 相反, 冷頁則不再快取記憶體中. 在多處理器系統上每個CPU都有一個或者多個告訴快取. 各個CPU的管理必須是獨立的.

儘管記憶體域可能屬於一個特定的NUMA結點, 因而關聯到某個特定的CPU。 但其他CPU的告訴快取仍然可以包含該記憶體域中的頁面. 最終的效果是, 每個處理器都可以訪問系統中的所有頁, 儘管速度不同. 因而, 特定於記憶體域的資料結構不僅要考慮到所屬NUMA結點相關的CPU, 還必須照顧到系統中其他的CPU.

pageset是一個指標, 其容量與系統能夠容納的CPU的數目的最大值相同.

陣列元素型別為per_cpu_pageset, 定義在include/linux/mmzone.h?v4.7, line 254 如下所示

struct per_cpu_pageset {
       struct per_cpu_pages pcp;
#ifdef CONFIG_NUMA
       s8 expire;
#endif
#ifdef CONFIG_SMP
       s8 stat_threshold;
       s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS];
#endif
};

該結構由一個per_cpu_pages pcp變數組成, 該資料結構定義如下, 位於include/linux/mmzone.h?v4.7, line 245

struct per_cpu_pages {
    int count;              /* number of pages in the list 列表中的頁數  */
    int high;               /* high watermark, emptying needed 頁數上限水印, 在需要的情況清空列表  */
    int batch;              /* chunk size for buddy add/remove,  新增/刪除多頁塊的時候, 塊的大小  */

    /* Lists of pages, one per migrate type stored on the pcp-lists 頁的連結串列*/
       struct list_head lists[MIGRATE_PCPTYPES];
};
欄位 描述
count 記錄了與該列表相關的頁的數目
high 是一個水印. 如果count的值超過了high, 則表明列表中的頁太多了
batch 如果可能, CPU的快取記憶體不是用單個頁來填充的, 而是歐諾個多個頁組成的塊, batch作為每次新增/刪除頁時多個頁組成的塊大小的一個參考值
list 一個雙鏈表, 儲存了當前CPU的冷頁或熱頁, 可使用核心的標準方法處理

在核心中只有一個子系統會積極的嘗試為任何物件維護per-cpu上的list連結串列, 這個子系統就是slab分配器.

  • struct per_cpu_pageset具有一個欄位, 該欄位
  • struct per_cpu_pages則維護了連結串列中目前已有的一系列頁面, 高極值和低極值決定了何時填充該集合或者釋放一批頁面, 變數決定了一個塊中應該分配多少個頁面, 並最後決定在頁面前的實際連結串列中分配多少各頁面

4.7 記憶體域的第一個頁幀zone_start_pfn

struct zone中通過zone_start_pfn成員標記了記憶體管理區的頁面地址.

然後核心也通過一些全域性變數標記了實體記憶體所在頁面的偏移, 這些變數定義在mm/nobootmem.c?v4.7, line 31

unsigned long max_low_pfn;
unsigned long min_low_pfn;
unsigned long max_pfn;
unsigned long long max_possible_pfn;

PFN是實體記憶體以Page為單位的偏移量

變數 描述
max_low_pfn x86中,max_low_pfn變數是由find_max_low_pfn函式計算並且初始化的,它被初始化成ZONE_NORMAL的最後一個page的位置。這個位置是kernel直接訪問的實體記憶體, 也是關係到kernel/userspace通過“PAGE_OFFSET巨集”把線性地址記憶體空間分開的記憶體地址位置
min_low_pfn 系統可用的第一個pfn是min_low_pfn變數, 開始與_end標號的後面, 也就是kernel結束的地方.在檔案mm/bootmem.c中對這個變數作初始化
max_pfn 系統可用的最後一個PFN是max_pfn變數, 這個變數的初始化完全依賴與硬體的體系結構.
max_possible_pfn

x86的系統中, find_max_pfn函式通過讀取e820表獲得最高的page frame的數值, 同樣在檔案mm/bootmem.c中對這個變數作初始化。e820表是由BIOS建立的

This is the physical memory directly accessible by the kernel and is related to the kernel/userspace split in the linear address space marked by PAGE OFFSET.

我理解為這段地址kernel可以直接訪問,可以通過PAGE_OFFSET巨集直接將kernel所用的虛擬地址轉換成實體地址的區段。在檔案mm/bootmem.c中對這個變數作初始化。在記憶體比較小的系統中max_pfn和max_low_pfn的值相同

min_low_pfn, max_pfn和max_low_pfn這3個值,也要用於對高階記憶體(high memory)的起止位置的計算。在arch/i386/mm/init.c檔案中會對類似的highstart_pfn和highend_pfn變數作初始化。這些變數用於對高階記憶體頁面的分配。後面將描述。

5 管理區表zone_table與管理區節點的對映

核心在初始化記憶體管理區時, 首先建立管理區表zone_table. 參見mm/page_alloc.c?v=2.4.37, line 38

/*
 *
 * The zone_table array is used to look up the address of the
 * struct zone corresponding to a given zone number (ZONE_DMA,
 * ZONE_NORMAL, or ZONE_HIGHMEM).
 */
zone_t *zone_table[MAX_NR_ZONES*MAX_NR_NODES];
EXPORT_SYMBOL(zone_table);

MAX_NR_ZONES是一個節點中所能包容納的管理區的最大數, 如3個, 定義在include/linux/mmzone.h?v=2.4.37, line 25, 與zone區域的型別(ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM)定義在一起. 當然這時候我們這些標識都是通過巨集的方式來實現的, 而不是如今的列舉型別

MAX_NR_NODES是可以存在的節點的最大數.

函式EXPORT_SYMBOL使得核心的變數或者函式可以被載入的模組(比如我們的驅動模組)所訪問.

該表處理起來就像一個多維陣列, 在函式free_area_init_core中, 一個節點的所有頁面都會被初始化.

6 zonelist記憶體域儲存層次

6.1 記憶體域之間的層級結構

當前結點與系統中其他結點的記憶體域之前存在一種等級次序

我們考慮一個例子, 其中核心想要分配高階記憶體.

  1. 它首先企圖在當前結點的高階記憶體域找到一個大小適當的空閒段. 如果失敗, 則檢視該結點的普通記憶體域. 如果還失敗, 則試圖在該結點的DMA記憶體域執行分配.
  2. 如果在3個本地記憶體域都無法找到空閒記憶體, 則檢視其他結點. 在這種情況下, 備選結點應該儘可能靠近主結點, 以最小化由於訪問非本地記憶體引起的效能損失.

核心定義了記憶體的一個層次結構, 首先試圖分配”廉價的”記憶體. 如果失敗, 則根據訪問速度和容量, 逐漸嘗試分配”更昂貴的”記憶體.

高階記憶體是最廉價的, 因為核心沒有任何部份依賴於從該記憶體域分配的記憶體. 如果高階記憶體域用盡, 對核心沒有任何副作用, 這也是優先分配高階記憶體的原因.

其次是普通記憶體域, 這種情況有所不同. 許多核心資料結構必須儲存在該記憶體域, 而不能放置到高階記憶體域.

因此如果普通記憶體完全用盡, 那麼核心會面臨緊急情況. 所以只要高階記憶體域的記憶體沒有用盡, 都不會從普通記憶體域分配記憶體.

最昂貴的是DMA記憶體域, 因為它用於外設和系統之間的資料傳輸. 因此從該記憶體域分配記憶體是最後一招.

6.2 zonelist結構

核心還針對當前記憶體結點的備選結點, 定義了一個等級次序. 這有助於在當前結點所有記憶體域的記憶體都用盡時, 確定一個備選結點

核心使用pg_data_t中的zonelist陣列, 來表示所描述的層次結構.

typedef struct pglist_data {
    struct zonelist node_zonelists[MAX_ZONELISTS];
    /*  ......  */
}pg_data_t;

關於該結構zonelist的所有相關資訊定義include/linux/mmzone.h?v=4.7, line 568, 我們下面慢慢來講.

node_zonelists陣列對每種可能的記憶體域型別, 都配置了一個獨立的陣列項.

該陣列項的大小MAX_ZONELISTS用一個匿名的列舉常量定義, 定義在include/linux/mmzone.h?v=4.7, line 571

enum
{
    ZONELIST_FALLBACK,      /* zonelist with fallback */
#ifdef CONFIG_NUMA
    /*
     * The NUMA zonelists are doubled because we need zonelists that
     * restrict the allocations to a single node for __GFP_THISNODE.
     */
    ZONELIST_NOFALLBACK,    /* zonelist without fallback (__GFP_THISNODE) */
#endif
    MAX_ZONELISTS
};

我們會發現在UMA結構下, 陣列大小MAX_ZONELISTS = 1, 因為只有一個記憶體結點, zonelist中只會儲存一個ZONELIST_FALLBACK型別的結構, 但是NUMA下需要多餘的ZONELIST_NOFALLBACK用以表示當前結點的資訊

pg_data_t->node_zonelists陣列項用struct zonelis結構體定義, 該結構包含了型別為struct zoneref的一個備用列表由於該備用列表必須包括所有結點的所有記憶體域,因此由MAX_NUMNODES * MAX_NZ_ZONES項組成,外加一個用於標記列表結束的空指標

struct zonelist結構的定義在include/linux/mmzone.h?v=4.7, line 606

/*
 * One allocation request operates on a zonelist. A zonelist
 * is a list of zones, the first one is the 'goal' of the
 * allocation, the other zones are fallback zones, in decreasing
 * priority.
 *
 * To speed the reading of the zonelist, the zonerefs contain the zone index
 * of the entry being read. Helper functions to access information given
 * a struct zoneref are
 *
 * zonelist_zone()      - Return the struct zone * for an entry in _zonerefs
 * zonelist_zone_idx()  - Return the index of the zone for an entry
 * zonelist_node_idx()  - Return the index of the node for an entry
 */
struct zonelist {
    struct zoneref _zo

而struct zoneref結構的定義如下include/linux/mmzone.h?v=4.7, line 583

/*
 * This struct contains information about a zone in a zonelist. It is stored
 * here to avoid dereferences into large structures and lookups of tables
 */
struct zoneref {
    struct zone *zone;      /* Pointer to actual zone */
    int zone_idx;       /* zone_idx(zoneref->zone) */
};

6.3 記憶體域的排列方式

那麼我們核心是如何組織在zonelist中組織記憶體域的呢?

NUMA系統中存在多個節點, 每個節點對應一個struct pglist_data結構, 每個結點中可以包含多個zone, 如: ZONE_DMA, ZONE_NORMAL, 這樣就產生幾種排列順序, 以2個節點2個zone為例(zone從高到低排列, ZONE_DMA0表示節點0的ZONE_DMA,其它類似).

  • Legacy方式, 每個節點只排列自己的zone;

  • Node方式, 按節點順序依次排列,先排列本地節點的所有zone,再排列其它節點的所有zone。

  • Zone方式, 按zone型別從高到低依次排列各節點的同相型別zone

可通過啟動引數”numa_zonelist_order”來配置zonelist order,核心定義了3種配置, 這些順序定義在mm/page_alloc.c?v=4.7, line 4551

// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4551
#define ZONELIST_ORDER_DEFAULT  0 /* 智慧選擇Node或Zone方式 */

#define ZONELIST_ORDER_NODE     1 /* 對應Node方式 */

#define ZONELIST_ORDER_ZONE     2 /* 對應Zone方式 */

注意

在非NUMA系統中(比如UMA), 由於只有一個記憶體結點, 因此ZONELIST_ORDER_ZONE和ZONELIST_ORDER_NODE選項會配置相同的記憶體域排列方式, 因此, 只有NUMA可以配置這幾個引數

全域性的current_zonelist_order變數標識了系統中的當前使用的記憶體域排列方式, 預設配置為ZONELIST_ORDER_DEFAULT, 參見mm/page_alloc.c?v=4.7, line 4564

巨集 zonelist_order_name巨集 排列方式 描述
ZONELIST_ORDER_DEFAULT Default 由系統智慧選擇Node或Zone方式
ZONELIST_ORDER_NODE Node Node方式 按節點順序依次排列,先排列本地節點的所有zone,再排列其它節點的所有zone
ZONELIST_ORDER_ZONE Zone Zone方式 按zone型別從高到低依次排列各節點的同相型別zone

6.4 build_all_zonelists初始化記憶體節點

核心通過build_all_zonelists初始化了記憶體結點的zonelists域

  • 首先核心通過set_zonelist_order函式設定了zonelist_order,如下所示, 參見mm/page_alloc.c?v=4.7, line 5031

  • 建立備用層次結構的任務委託給build_zonelists, 該函式為每個NUMA結點都建立了相應的資料結構. 它需要指向相關的pg_data_t例項的指標作為引數

7 總結

在linux中,核心也不是對所有實體記憶體都一視同仁,核心而是把頁分為不同的區, 使用區來對具有相似特性的頁進行分組.

Linux必須處理如下兩種硬體存在缺陷而引起的記憶體定址問題:

  1. 一些硬體只能用某些特定的記憶體地址來執行DMA
  2. 一些體系結構其記憶體的物理定址範圍比虛擬定址範圍大的多。這樣,就有一些記憶體不能永久地對映在核心空間上。

為了解決這些制約條件,Linux使用了三種區:

  1. ZONE_DMA : 這個區包含的頁用來執行DMA操作。
  2. ZONE_NOMAL : 這個區包含的都是能正常對映的頁。
  3. ZONE_HIGHEM : 這個區包”高階記憶體”,其中的頁能不永久地對映到核心地址空間

而為了相容一些裝置的熱插拔支援以及記憶體碎片化的處理, 核心也引入一些邏輯上的記憶體區.

  1. ZONE_MOVABLE : 核心定義了一個偽記憶體域ZONE_MOVABLE, 在防止實體記憶體碎片的機制memory migration中需要使用該記憶體域. 供防止實體記憶體碎片的極致使用
  2. ZONE_DEVICE : 為支援熱插拔裝置而分配的Non Volatile Memory非易失性記憶體區的實際使用與體系結構是相關的。linux把系統的記憶體結點劃分區, 一個區包含了若干個記憶體頁面, 形成不同的記憶體池,這樣就可以根據用途進行分配了

需要說明的是,區的劃分沒有任何物理意義, 只不過是核心為了管理頁而採取的一種邏輯上的分組. 儘管某些分配可能需要從特定的區中獲得頁, 但這並不是說, 某種用途的記憶體一定要從對應的區來獲取,如果這種可供分配的資源不夠用了,核心就會佔用其他可用去的記憶體.

下表給出每個區及其在X86上所佔的列表