Linux内核的物理内存管理

    在物理内容定义方面Linux引入了内存结节(node), 内存区(zone), 内存页page的概念。对物理内存的管理分两部分:最底层实现的页面级内存管理伙伴系统,基于伙伴系统实现的内核对象缓存和通用缓存Slab内存管理。

2,伙伴系统(Buddy System)

        节点:内核以struct pglist_data数据结构统一表示UMA系统和NUMA系统的内存结点,UMA只有一个内存节点,NUMA以链表的形式把内存节点串联起来。

    typedef struct pglist_data {
         struct zone node_zones[MAX_NR_ZONES];
         struct zonelist node_zonelists[GFP_ZONETYPES];
         int nr_zones;
         struct page *node_mem_map;
         struct bootmem_data *bdata;
         unsigned long node_start_pfn;
         unsigned long node_present_pages; /* total number of physical pages */
         unsigned long node_spanned_pages; /* total size of physical page range, including holes */
         int node_id;
         struct pglist_data *pgdat_next;
         wait_queue_head_t       kswapd_wait;
         struct task_struct *kswapd;
    } pg_data_t;

    内存区:Linux将节点的物理内存划分成不同的内存区,struct zone表示内存区,内存区的类型有:        

ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM 。

    struct zone {
        unsigned long  free_pages;
        unsigned long  pages_min, pages_low, pages_high;
        unsigned long  protection[MAX_NR_ZONES];

        struct per_cpu_pageset pageset[NR_CPUS];
        spinlock_t  lock;
        struct free_area free_area[MAX_ORDER];
        spinlock_t  lru_lock; 
        struct list_head active_list;
        struct list_head inactive_list;
        unsigned long  nr_scan_active;
        unsigned long  nr_scan_inactive;
        unsigned long  nr_active;
        unsigned long  nr_inactive;
        unsigned long  pages_scanned;    /* since last reclaim */
        int   all_unreclaimable; /* All pages pinned */
        wait_queue_head_t * wait_table;
        unsigned long  wait_table_size;
        unsigned long  wait_table_bits;
        struct pglist_data *zone_pgdat;
        struct page  *zone_mem_map;
        char   *name;
    }      

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

    内存页:Linux内核为每一个物理内存页框创建一个struct page对象,系统用一个全局变量struct page* mem_page数组存放page对象。

    struct page {
        page_flags_t flags;  

        atomic_t _count;  /* Usage count, see below. */
        atomic_t _mapcount; 
        unsigned long private;  

        struct address_space *mapping;

        pgoff_t index;  

        struct list_head lru; 

       #if defined(WANT_PAGE_VIRTUAL)
        void *virtual;  

       #endif /* WANT_PAGE_VIRTUAL */
    };

    伙伴系统分配单个的物理页面或2的整数次幂个连续的物理页面。struct zone的成员free_area数组保存页面。阶是伙伴系统的一个非常重要的术语,它描述了内存分配的数量单位,内存块的长度为2的order次幂,order的范围从0到11。free_area[]数组以阶为索引,每个元素保存连续内存页的链表。第0个链表管理单页大小的内存块,第1个链表管理两个连续页大小的内存块,第3个链表管理4个连续页大小的内存块,以此类推。Linux采用著名的buddy System算法解决外部碎片问题,把所有的空闲页框分组成11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512,1024个连续的页框。对1024个页框的最大请求对应着4MB大小的连续RAM块。

    工作原理:

    假设要请求一个256个页框的块(1MB),算法先在256个页框的链表中检查是否有一个空闲块,如果没有,算法查找下一个更大的页块的链表,也就是在块大小为512个页的链表中找一个空闲块,如果存在,内核把该块分成两块,一半用于满足请求,一半插入到256个页框大小的块链表中。如果512个页的链表中也没有空闲块,继续查找更大的块链表,找到后,256个页用于满足请求,剩余的768个页框分两部分,512个页框和256个页的块,分别插入到相应的链表。释放过程则相反,内核试图把大小为b的块与链表中与释放块连续的块合并成大小为2b的单独块,插入到更大的块链表中。满足以下条件的两个块称为伙伴:

  •  两个块具有相同的大小,记作b
  •  它们的物理地址是连续的
  • 第一块的第一个页框的物理地址是2*b*(2的12次方)的位数。

    算法是迭代的,如果成功合并释放的块,它会试图合并2b的块,以再欠试图形成更大的块。

3,Buddy System API

    伙伴系统的API只分配2的整数幂个页

    alloc_pages(mask, order)分配2的order次幂个页,并返回一个struct page实例。

    get_zeroed_page(mask) 分配一个页,并返回 struct page实例,页对应的内容填充0.

    __get_free_pages和__get_free_page的工作方式与上述函数相同,但返回分配个内存块的虚拟地址。

    get_dma_pages(mask, order)用于获得适用于DMA的页

    内存分配标志mask:

    #define __GFP_WAIT ((__force gfp_t)___GFP_WAIT) /* Can wait and reschedule? */
    #define __GFP_HIGH ((__force gfp_t)___GFP_HIGH) /* Should access emergency pools? */
    #define __GFP_IO ((__force gfp_t)___GFP_IO) /* Can start physical IO? */
    #define __GFP_FS ((__force gfp_t)___GFP_FS) /* Can call down to low-level FS? */
    #define __GFP_COLD ((__force gfp_t)___GFP_COLD) /* Cache-cold page required */
    #define __GFP_NOWARN ((__force gfp_t)___GFP_NOWARN) 
    #define __GFP_REPEAT ((__force gfp_t)___GFP_REPEAT) /* See above */
    #define __GFP_NOFAIL ((__force gfp_t)___GFP_NOFAIL) /* See above */
    #define __GFP_NORETRY ((__force gfp_t)___GFP_NORETRY) /* See above */
    #define __GFP_MEMALLOC ((__force gfp_t)___GFP_MEMALLOC

    #define __GFP_COMP ((__force gfp_t)___GFP_COMP) /* Add compound page metadata */
    #define __GFP_ZERO ((__force gfp_t)___GFP_ZERO) /* Return zeroed page on success */
    #define __GFP_NOMEMALLOC ((__force gfp_t)___GFP_NOMEMALLOC)   

    #define __GFP_HARDWALL   ((__force gfp_t)___GFP_HARDWALL)

    #define __GFP_THISNODE ((__force gfp_t)___GFP_THISNODE)/* No fallback, no policies */
    #define __GFP_RECLAIMABLE ((__force gfp_t)___GFP_RECLAIMABLE) /* Page is reclaimable */
    #define __GFP_NOTRACK ((__force gfp_t)___GFP_NOTRACK)  /* Don't track with kmemcheck */

    #define __GFP_NO_KSWAPD ((__force gfp_t)___GFP_NO_KSWAPD)
    #define __GFP_OTHER_NODE ((__force gfp_t)___GFP_OTHER_NODE) /* On behalf of other node */
    #define __GFP_WRITE ((__force gfp_t)___GFP_WRITE) /* Allocator intends to dirty page */

    #define GFP_NOWAIT (GFP_ATOMIC & ~__GFP_HIGH)
   常用标志组合:

    #define GFP_ATOMIC (__GFP_HIGH)
    #define GFP_NOIO (__GFP_WAIT)
    #define GFP_NOFS (__GFP_WAIT | __GFP_IO)
    #define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS)
    #define GFP_TEMPORARY (__GFP_WAIT | __GFP_IO | __GFP_FS | \
    __GFP_RECLAIMABLE)
    #define GFP_USER (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
    #define GFP_HIGHUSER (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | \
    __GFP_HIGHMEM)
    #define GFP_HIGHUSER_MOVABLE (__GFP_WAIT | __GFP_IO | __GFP_FS | \
     __GFP_HARDWALL | __GFP_HIGHMEM | \
     __GFP_MOVABLE)
    #define GFP_IOFS (__GFP_IO | __GFP_FS)
    #define GFP_TRANSHUGE (GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
    __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | \
    __GFP_NO_KSWAPD)

4,Slab分配器

    slab分配器用于在物理页框分配的基础,实现对更小内存空间或特定对象的管理。slab的基本思想是先利用页面分配器分配出单个或连续的物理页面,然后在将整块页面划分成多个相等的小内存单元,以满足小内存空间的需要。

    slab分配器把对象分组放进高速缓存,每个高速缓存都同种类型对象。高速缓存对kmem_cache_s表示,

高速缓存的内存区被划分为多个slab,每个slab由一个或多个连续的页框组成,这些页框包含已分配的对象和空闲对象。

    struct kmem_cache_s {
        struct kmem_list3 lists;
        unsigned int  objsize;
        unsigned int   flags; /* constant flags */
        unsigned int  num; /* # of objs per slab */
        unsigned int  free_limit; /* upper limit of objects in the lists */
        spinlock_t  spinlock;

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

        const char  *name;
        struct list_head next;
    };

    struct kmem_list3 {
        struct list_head slabs_partial; /* partial list first, better asm code */
         struct list_head slabs_full;
         struct list_head slabs_free;
         unsigned long free_objects;
         int  free_touched;
         unsigned long next_reap;
         struct array_cache *shared;
       };

       struct slab {
          struct list_head list;
          unsigned long  colouroff;
          void   *s_mem;  /* including colour offset */
          unsigned int  inuse;  /* num of objs active in slab */
          kmem_bufctl_t  free;
       };

       


         

    size_cache:

    Linux内核称为general cache或default cache。是kmalloc函数实现的基础。
     struct cache_sizes {
        size_t   cs_size;
        kmem_cache_t *cs_cachep;
        kmem_cache_t *cs_dmacachep;
     };

    struct cache_sizes malloc_sizes[] = {
        #define CACHE(x) { .cs_size = (x) },
        #if (PAGE_SIZE == 4096)
        CACHE(32)
        #endif
        CACHE(64)
        #if L1_CACHE_BYTES < 64
        CACHE(96)
        #endif
        CACHE(128)
        #if L1_CACHE_BYTES < 128
        CACHE(192)
        #endif
        CACHE(256)
        CACHE(512)
        CACHE(1024)
        CACHE(2048)
        CACHE(4096)
        CACHE(8192)
        CACHE(16384)
        CACHE(32768)
        CACHE(65536)
        CACHE(131072)
       #ifndef CONFIG_MMU
        CACHE(262144)
        CACHE(524288)
        CACHE(1048576)
       #ifdef CONFIG_LARGE_ALLOCS
        CACHE(2097152)
        CACHE(4194304)
        CACHE(8388608)
        CACHE(16777216)
        CACHE(33554432)
       #endif /* CONFIG_LARGE_ALLOCS */
       #endif         { 0, }
        #undef CACHE
    };

       

    
5,分配API

    kmalloc函数是驱动程序中使用最多的内存分配函数,它分配出来的内存空间物理上是连续的,函数不负责内存空间的清空,它建立在slab分配器基础上,它实现主要是围绕size_cache展开。

    void * kmalloc(size_t size, int flags){

        struct cache_sizes *csizep = malloc_sizes;

        struct kmem_cache *cachep;

        while(size > csizep->size)

            csizep++

       cachep = csizep->cs_cachep;

        return kmem_cache_alloc(cachep, flags);

    }

    内核对象的分配:

    Linux内核源码中,大量利用kmem_cache_create来创建内核对象的缓存,相对于size_cache,内核模块开发者可以定满足自己特定要求的kmem_cache。

    struct kmem_cache* kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags, void (*ctor) (void *))

    name是字符型指针,表示内核对象缓存的名称,size指定缓存的内核对象的大小。


    /**
     * kmem_cache_alloc - Allocate an object
     */
    void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)

    /**
     * kmem_cache_destroy - delete a cache
     * @cachep: the cache to destroy
      */
    int kmem_cache_destroy (kmem_cache_t * cachep)

猜你喜欢

转载自leilianjie.iteye.com/blog/2322448