LInux中的物理內存管理

2017-02-23html


 1、夥伴系統node

LInux下用夥伴系統管理物理內存頁,夥伴系統得益於其良好的算法,必定程度上能夠避免外部碎片爲什麼這麼說?先回顧下Linux下虛擬地址空間的分佈。linux

在X86架構下,系統有4GB的虛擬地址空間,其中0-3GB做爲用戶空間,而3-4GB是系統地址空間。linux系統系統地址空間理論上應該不可換出,即每一個虛擬頁面均會對應一個物理頁幀。若是這樣的話,系統地址空間就能使用1GB,若是系統有多餘的內存,這裏仍然使用不上,這就限制了其性能的發展。爲了解決這一問題,就有了高端內存的概念(本質上是因爲虛擬地址空間的不足,在64位模式下,因爲虛擬地址空間異常龐大,沒有高端內存的概念)。算法

因此這1GB的地址空間就劃分紅了三部分:數據庫

 這1GB地址空間的最低16M是做爲ZONE_DMA的空間,最爲昂貴,用戶外設和系統之間的數據傳輸;而ZONE_NORMAL區是一致映射區,這部分和前面DMA區的虛擬頁面都是和物理內存頁面一一對應,經過一個偏移量便可把這部分的虛擬地址轉化成具體的物理地址,LInux內核正是加載在這個區域,除此以外還有一些基本的數據結構如IDT、GDT等,所以此區域的物理內存也比較重要。而ZONE_HIGHMEM是爲了創建臨時映射用的,臨時映射就是普通的內存映射,內核中的vmalloc以及用戶空間進程的內存分配都是對應着部分的物理內存。這裏所說的分配是系統爲虛擬內存分配物理頁面。虛擬地址空間和物理地址空間的大體映射以下:windows

這裏解釋下內核空間中在一致映射區之上的三個空間:vmalloc、持久映射和固定映射。系統長時間運行後,物理內存中可能存在很多碎片。連續分配大塊物理內存就容易遭遇失敗,經過vmalloc,能夠把分散的物理內存聚合起來,至少表現爲虛擬空間上連續。當啓用了高端內存域時,持久映射用於將高端內存域中的非持久物理頁面映射到虛擬地址空間中,即這裏是給page一個虛擬地址,讓該物理頁面可用。而固定映射即是在內核的啓動的初始階段,內存管理子系統尚未ready,ioremap還不能調用的時候使用,具體參見wowo一篇文章:http://www.wowotech.net/memory_management/fixmap.html。數組

而夥伴系統又是如何工做的呢?LInux系統在初始化夥伴系統時,會根據空閒頁面(物理頁幀)的連續程度,造成不一樣的鏈表。在LINux下有三個內存域(不考慮NUMA),即上面提到的三個,每一個內存域由zone結構表示,在zone 結構表示以下:緩存

struct zone{
...
         struct free_area free_area[MAX_ORDER]  
...
}

MAX_ORDER 指定連續頁面的數量,其值通常從0-11表示頁面大小從2^0~2^11即從單頁面到2048個頁面。相同大小的連續內存區對應一個表項。當分配內存時,根據指定的值,首選在指定的匹配的內存鏈表中選擇,若是沒有對應的空閒內存塊,則從上面一級的空閒內存塊分割一塊可用的內存。其中一塊用於分配,另外一塊加入適當的鏈表。依次類推,這種就避免了我要申請一個page,結果從維護10個連續頁面的鏈表頁面中分配一個頁,形成的外部碎片問題。而free_area結構以下:服務器

struct free_area{
         struct list_head free_list[MIGRATE_TYPES];
          unsigned long nr_free;           

} 

該結構其實維護着一組鏈表,爲什麼呢?先看上面,夥伴系統必定程度上避免了外部碎片,可是隨着系統運行時間的增長,仍然會存在不少小碎片,雖然在用戶層,能夠實現交叉映射(物理地址不連續,邏輯上連續),可是因爲內核空間的一致映射,碎片問題仍是沒法避免。基於此,Linux開發者就對頁面根據可移動性質,分了幾種類型:數據結構

enum {
    MIGRATE_UNMOVABLE,
    MIGRATE_RECLAIMABLE,
    MIGRATE_MOVABLE,
    MIGRATE_PCPTYPES,    /* the number of types on the pcp lists */
    MIGRATE_RESERVE = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
    /*
     * MIGRATE_CMA migration type is designed to mimic the way
     * ZONE_MOVABLE works.  Only movable pages can be allocated
     * from MIGRATE_CMA pageblocks and page allocator never
     * implicitly change migration type of MIGRATE_CMA pageblock.
     *
     * The way to use it is to change migratetype of a range of
     * pageblocks to MIGRATE_CMA which can be done by
     * __free_pageblock_cma() function.  What is important though
     * is that a range of pageblocks must be aligned to
     * MAX_ORDER_NR_PAGES should biggest page be bigger then
     * a single pageblock.
     */
    MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
    MIGRATE_ISOLATE,    /* can't allocate from here */
#endif
    MIGRATE_TYPES
};

這樣讓具備相同性質的內存集中分配,就能夠避免其餘內存被佔用而不能移除的狀況。在初始狀態,內存自己沒有這些性質,只是隨着系統的運行,系統固定在某個地方分配某種內存,造成的這種分佈。

具體架構以下圖所示:

在啓用NUMA的系統中(目前主流的系統都加入了對NUMA的支持),對於桌面類的系統,通常都是做爲一個NUMA節點,可是的確是NUMA的流程實現的內存分配。NUMA下的各個內存域的關係以下:

內存分配首先在當前CPU關聯的節點內分配,若是當前CPU內存不足,則能夠經過備用列表,分配其餘節點的內存,從這一點來看,貌似是把或夥伴系統擴大了,兩個相鄰節點也可做爲夥伴,可是速度會受到影響。

二、PAGE結構

Linux中每一個物理頁面對應一個page結構,保存在一個巨大的page數組中mem_map,這點就相似於windows下的pfn數據庫。page結構在數組中的順序正是物理頁面的佈局順序,因此,page結構在數組中的序號即是其對應的物理頁面的頁幀號。基於此看下pfn到page相互轉換的兩個宏操做

#define pfn_to_page(pfn)    (mem_map + ((pfn) - PHYS_PFN_OFFSET))
#define page_to_pfn(page)    ((unsigned long)((page) - mem_map) + PHYS_PFN_OFFSET)

這裏PHYS_PFN_OFFSET表示起始 的頁幀號,pfn_to_page即用mem_map加上pfn的值即讓mem_map指針後移pfn-PHYS_PFN_OFFSET個page,就獲得指定的page結構。而page_to_pfn逆向操做便可。

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

64位下物理地址空間劃分

64位下能夠有兩個DMA區,即在ZONE_DMA之上,能夠有ZONE_DMA32,該區間爲16M~4GB,且在64位Linux 上沒有高端內存的概念,DMA32之上統一做爲NORMAL區。所以,8GB物理內存狀況下,32位和64位狀況下物理內存劃分狀況以下:

而64位下內核虛擬地址空間的劃分以下:

ffff800000000000 - ffff80ffffffffff (=40 bits) guard hole
ffff880000000000 - ffffc7ffffffffff (=64 TB) direct mapping of all phys. memory
ffffc80000000000 - ffffc8ffffffffff (=40 bits) hole
ffffc90000000000 - ffffe8ffffffffff (=45 bits) vmalloc/ioremap space
ffffe90000000000 - ffffe9ffffffffff (=40 bits) hole
ffffea0000000000 - ffffeaffffffffff (=40 bits) virtual memory map (1TB)
... unused hole ...
ffffffff80000000 - ffffffffa0000000 (=512 MB) kernel text mapping, from phys 0
ffffffffa0000000 - ffffffffff5fffff (=1525 MB) module mapping space
ffffffffff600000 - ffffffffffdfffff (=8 MB) vsyscalls
ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole

所以64位下,起始虛擬地址空間足夠大,能夠一致映射64TB的物理內存,普通狀況下全部的物理內存都可以 已經映射在內核地址空間,且是一致映射。所以在內核中針對於任何一個物理地址都可以經過__va的方式,獲得虛擬地址,並對其進行訪問。而這樣並不影響用戶空間對物理頁面的使用。

(問題引入)

之因此對上述狀況作介紹主要是最近有一個問題,就是在師妹在KVM中獲得虛擬機的一個物理頁面,經過__va的方式和經過kmap的方式居然獲得一樣的虛擬地址,且均是內核地址空間的地址。這一時讓我很不解,虛擬機的內存均屬於qemu用戶進程地址空間,怎麼可能會經過__va獲得呢?經過__va獲得意味着該物理頁面一致映射到了內核地址空間,以前的確對64位下的內核映射不太清楚,目前算是搞清楚了!!

2、SLAB機制

 夥伴系統做爲底層的內存管理機制,雖然已經作到足夠優秀,可是其內存的分配老是以頁爲單位,面對小內存塊的需求,經過夥伴系統分配就有點殺雞用牛刀的感受了。何況比較頻繁的對夥伴系統進行調用對性能也有很多影響。基於此,SLAB分配器便被引入進來。這裏SLAB分配器的思想就比如是一個代售點,而夥伴系統就比如是廠家。廠家不容許商品單獨銷售,只會按照必定的規格發售。而通常來說,普通用戶達不到廠家發售的量,可是普通用戶卻構成了比較大的消費羣體。這時候,SLAB發現了商機(哈哈),他一次性的從夥伴系統批發足夠多的內存,而後對普通用戶發售。普通用戶使用完畢,由SLAB回收,可是SLAB不用交還給夥伴系統,這樣在下次又有請求,直接從SLAB這裏分配便可,不須要走夥伴系統的流程,從這一點就大大提升了分配的效率。說到這裏,你們可能就明白了,說到底SLAB不就是一個緩存麼,內核中緩存的思想太多了。windows中的非換頁內存的管理,其實就有點這種意思。

 SLAB分配的功能

SLAB主要由兩個功能:

一、對對象的管理

二、對小內存塊的管理

1.1 對對象的管理

對於對象的管理,系統中存在某些對象須要頻繁的申請和銷燬。好比進程對象task_struct,針對這種需求,內核首先分配好必定數量的對象經過某種數據結構保存起來,在有分配需求的時候,直接從對應數據結構獲取便可。使用完畢再交換給相應數據結構。這裏實現這種功能的就是SLAB。

 針對一個對象的緩存,有一個專門的結構kmem_cache表示,一個cache可能有多個slab組成,系統中的cache造成一條鏈表,而一個cache中的slab也會造成鏈表,只不過按照slab中的對象使用狀況,分紅三條鏈表:所有使用、部分使用、所有空閒。

系統中緩存組織結構以下:

 

 緩存結構kmem_cache以下:

struct kmem_cache {
/* 1) Cache tunables. Protected by cache_chain_mutex */
    unsigned int batchcount;
    unsigned int limit; 
    unsigned int shared;

    unsigned int size;
    u32 reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */

    unsigned int flags;        /* constant flags */
    unsigned int num;        /* # of objs per slab */

/* 3) cache_grow/shrink */
    /* order of pgs per slab (2^n) */
    unsigned int gfporder;

    /* force GFP flags, e.g. GFP_DMA */
    gfp_t allocflags;

    size_t colour;            /* cache colouring range */
    unsigned int colour_off;    /* colour offset */
    struct kmem_cache *slabp_cache;
    unsigned int slab_size;

    /* constructor func */
    void (*ctor)(void *obj);

/* 4) cache creation/removal */
    const char *name;
    struct list_head list;
    int refcount;
    int object_size;
    int align;

/* 5) statistics */
#ifdef CONFIG_DEBUG_SLAB
    unsigned long num_active;
    unsigned long num_allocations;
    unsigned long high_mark;
    unsigned long grown;
    unsigned long reaped;
    unsigned long errors;
    unsigned long max_freeable;
    unsigned long node_allocs;
    unsigned long node_frees;
    unsigned long node_overflow;
    atomic_t allochit;
    atomic_t allocmiss;
    atomic_t freehit;
    atomic_t freemiss;

    /*
     * If debugging is enabled, then the allocator can add additional
     * fields and/or padding to every object. size contains the total
     * object size including these internal fields, the following two
     * variables contain the offset to the user object and its size.
     */
    int obj_offset;
#endif /* CONFIG_DEBUG_SLAB */
#ifdef CONFIG_MEMCG_KMEM
    struct memcg_cache_params *memcg_params;
#endif

/* 6) per-cpu/per-node data, touched during every alloc/free */
    /*
     * We put array[] at the end of kmem_cache, because we want to size
     * this array to nr_cpu_ids slots instead of NR_CPUS
     * (see kmem_cache_init())
     * We still use [NR_CPUS] and not [1] or [0] because cache_cache
     * is statically defined, so we reserve the max number of cpus.
     *
     * We also need to guarantee that the list is able to accomodate a
     * pointer for each node since "nodelists" uses the remainder of
     * available pointers.
     */
    struct kmem_cache_node **node;
    struct array_cache *array[NR_CPUS + MAX_NUMNODES];
    /*
     * Do not add fields after array[]
     */
};

關於此結構不在詳細描述,只是最後一個字段array數組記錄CPU緩存的使用狀況。每次申請和釋放完對象都要訪問該字段。array_cache結構以下:

struct array_cache {
    unsigned int avail;//可用對象的數目
    unsigned int limit;//可擁有的最大對象的數目
    unsigned int batchcount;//
    unsigned int touched;
    spinlock_t lock;
    void *entry[];    /*主要是爲了訪問後面的對象
             * Must have this definition in here for the proper
             * alignment of array_cache. Also simplifies accessing
             * the entries.
             *
             * Entries should not be directly dereferenced as
             * entries belonging to slabs marked pfmemalloc will
             * have the lower bits set SLAB_OBJ_PFMEMALLOC
             */
};

具體到slab自己,一個slab包含兩部分:管理數據和被管理的對象。對象的存儲通常並非連續的,而是按照必定的方式進行對齊。目前有兩種方式:一、按照硬件緩存行進行對對齊;二、按照處理器位數對齊。好比32位處理器就是4字節對齊,對於6個字節的對象,後面就填充兩個字節,對其到8字節。填充字節能夠加速對slab中對象的訪問,至關於拿空間換時間吧,畢竟如今硬件的性能逐步提高,內存容量也是指數級增長。

管理數據能夠位於slab的起始位置,也能夠位於堆空間。首先是一個slab結構,結構後面是一個管理數組,每一個數組項對應一個slab對象,slab結構以下所示:

struct slab {
    union {
        struct {
            struct list_head list;
            unsigned long colouroff; //slab第一個對象的偏移
            void *s_mem;        /* including colour offset 第一個對象的地址*/
            unsigned int inuse;    /* num of objs active in slab 被使用對象的數目*/
            kmem_bufctl_t free;//下一個空閒對象的下標
            unsigned short nodeid;///用於尋址具體CPU高速緩存
        };
        struct slab_rcu __slab_cover_slab_rcu;
    };
};

slab結構位於slab起始處,在slab結構以後,是一個kmem_bufctl_t數組,用以跟蹤slab對象的使用狀況。大體結構以下:

在上述結構中能夠看到,slab結構中的free表示下一個可用的對象索引,按照圖中所示,下一個可用的索引是3,那麼當這個對象分配以後,須要充值free,這裏就是取kmem_buctl_t[3]的值做爲下一個可用的索引。上圖描述的是管理數據部分和對象部分在一塊內存上的情形。而當管理數據部分和對象部分再也不一塊內存的情形,原理根上面是一致的,由slab結構中的s_mem指針指向對象存儲區。當slab對象的大小超過八分之一個頁,就採用後者的方式創建緩存。不然,使用同一塊內存區。

 1.2 slab存在的問題

你們可能可以注意到slab結構中有個color字段,字面意思是着色,實際上就是一個偏移。此字段的意義是啥呢?前面已經提到,一個cache可能由多個slab組成,因爲slab的分配都是頁對齊的,而CPU的緩存行通常都是根據低地址尋址,即不一樣slab上的相同索引的對象會被映射到同一個緩存行。這樣就容易形成某個固定位置的緩存行被過渡使用,而某些位置的確很新。於硬件保養很不利。固然,最大的緣由仍是發生衝突就須要更新緩存行,對於緩存行的利用率比較低下,從而形成效率的低下。爲了解決這一問題,就有了上面着色的概念,即在每一個slab的最開始隨機放一個偏移量,這樣就可讓容易發生衝突的緩存行映射到不一樣的地方。提到緩存行的利用率,以下圖所示。然而在服務器系統中,即便加上偏移,通過一個循環,就再次發生衝突,因此着色並不能解決根本問題,只能相對減小這種影響,這也是slab的本質問題

 

對小內存塊的管理請參考下篇博文,Linux 下物理內存管理2  下篇文章將重點介紹小內存塊的分配並結合代碼描述SLAB的具體實現

參考資料:

一、http://www.secretmango.com/jimb/Whitepapers/slabs/slab.html

二、LInux 3.10.1源代碼

相關文章
相關標籤/搜索