物理内存相关的三个数据结构

物理内存相关的三个数据结构

基于linux-4.9介绍linux内存管理中跟物理内存相关的三个数据结构pglist_data、zone、page。

pg_data_t

typedef struct pglist_data {
    /* 跟zone相关的成员 */
    struct zone node_zones[MAX_NR_ZONES];
    struct zonelist node_zonelists[MAX_ZONELISTS];
    int nr_zones;

    /* 内存节点的信息 */
    unsigned long node_start_pfn;     //内存节点起始的页帧号
    unsigned long node_present_pages; //总共的物理页面数
    unsigned long node_spanned_pages; //总共的物理地址所占的页面数,包括内存空洞

    /* 内存回收相关的数据结构 */
    wait_queue_head_t kswapd_wait;  
    wait_queue_head_t pfmemalloc_wait;
    struct task_struct *kswapd;       //内存回收专门的线程kswapd
    int kswapd_order; 
    enum zone_type kswapd_classzone_idx; 

}pg_data_t;

zone

[include/linux/mmzone.h]

struct zone {

    /* zone的水位,用来内存回收时使用 */
    unsigned long watermark[NR_WMARK];

    /* 记录紧急内存的大小 */
    unsigned long nr_reserved_highatomic;

    /* 每一个zone的最少保留内存,比如来自high zone
     * 的内存分配时,如果high zone空闲内存不够用,
     * 这个时候就会到normal zone取内存,
     * 而这个lowmem_reserve的意义就是为了,
     * 让high zone在normal zone取内存时不能无限制的取,
     * 最少要保留一些给normal zone。
     */
    long lowmem_reserve[MAX_NR_ZONES]; 

    /* 其所在的pglist_data */
    struct pglist_data      *zone_pgdat;

    /* 每一个cpu都维护一个page列表用来提高速度 */
    struct per_cpu_pageset __percpu *pageset;

    /* 第一个页的索引 */
    unsigned long           zone_start_pfn;

    /* spanned_pages 是这个zone跨越的所有物理内存的page数目
     * 包括内存空洞,它的计算方式:
     *  spanned_pages = zone_end_pfn - zone_start_pfn
     */
    unsigned long           spanned_pages;

    /* present_pages 是这个zone在物理内存真实存在的所有page数目,
     * 它的计算方式:
     *  present_pages = spanned_pages - absent_pages
     *  其中absent_pages指的是内存空洞的page数目
     */
    unsigned long           present_pages;

    /* managed_pages 是这个zone被Buddy管理的所有的page数目,
     * 计算的方式:
     *  managed_pages = present_pages - reserved_pages
     * 其中reserved_pages包括被Bootmem分配走的内存
     */
    unsigned long           managed_pages;

    /* 这个zone的名字 */
    const char              *name;

    /* ZONE_PADDING是为了cache line对齐,用空间换时间。
     * 防止cache line 中有多个不同的数据结构而同时影响多个
     * cpu
     */
    ZONE_PADDING(_pad1_)

    /* 包含所有的空闲页面的列表 */
    struct free_area        free_area[MAX_ORDER];

    /* zone flags, see below */
    unsigned long           flags;

    /* 用来保护free_area的锁 */
    spinlock_t              lock;

    ZONE_PADDING(_pad2_)

    /* 内存规整和CMA相关的成员 */
    #if defined CONFIG_COMPACTION || defined CONFIG_CMA
            unsigned long           compact_cached_free_pfn;
            unsigned long           compact_cached_migrate_pfn[2];
    #endif
    #ifdef CONFIG_COMPACTION
            unsigned int            compact_considered;
            unsigned int            compact_defer_shift;
            int                     compact_order_failed;
    #endif
    #if defined CONFIG_COMPACTION || defined CONFIG_CMA
            bool                    compact_blockskip_flush;
    #endif

    ZONE_PADDING(_pad3_)

    /* zone统计信息 */
    atomic_long_t           vm_stat[NR_VM_ZONE_STAT_ITEMS];

}____cacheline_internodealigned_in_smp;

zone中的一些成员的作用

包含所有空闲页面的free_area数组

[include/linux/mmzone.h]

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

MIGRATE_TYPES的定义如下:

enum migratetype {
        MIGRATE_UNMOVABLE,   //不可迁移
        MIGRATE_MOVABLE,     //可迁移
        MIGRATE_RECLAIMABLE, //可被回收
        MIGRATE_PCPTYPES,    //跟per_cpu_pageset有关的迁移类型的个数
        MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
        MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
        MIGRATE_ISOLATE,
#endif
        MIGRATE_TYPES
};

migratetype定义了一个page的迁移类型。free_area为每一个迁移类型都单独设一个链表,以方便维护。

所以free_area这个数组包含了这个zone的所有空闲页面,并按照不通的迁移类型把这些页面放在不通的链表中方便管理。

每一个core维护的本地页链表per_cpu_pageset

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

struct per_cpu_pages {
        int count;
        int high;
        int batch;

        /* 3个不同的迁移类型的链表 */
        struct list_head lists[MIGRATE_PCPTYPES];
};

每个cpu core都维护一个per_cpu_pageset,用来加速只分配一个page的情况。当只申请一个页的时候,可以直接从per_cpu_pageset中获取,per_cpu_pages->high记录了这个cpu core维护的per_cpu_pageset的最度的个数,如果超过了这个个数,就把其中的per_cpu_pages->batch个还给buddy系统。如果发现per_cpu_pageset中没有可分配的页,就从buddy中获取batch个。

MMU管理内存的最小单位–页page

struct page {

    /* 描述这个页面的状态:脏页、被上锁、正在写回?等等 */
    unsigned long flags;

    union {
        /* 如果是有文件背景的page,指向这个文件相关的信息,低bit为0(为0时也可能是NULL);
         * 如果是匿名页,指向一个anon_vma结构体,低Bit为1。*/
        struct address_space *mapping;
        void *s_mem;
        atomic_t compound_mapcount;
    };

    union {
        struct {
            union {
                atomic_t _mapcount; //这个页面被映射的次数 - 1
            };
            atomic_t _refcount;        //这个页面被引用的次数
        };
    };

    union {
        struct list_head lru;        //lru算法页面回收时使用
    };

    struct rcu_head rcu_head;

    union {
        unsigned long private;
    };

#ifdef CONFIG_MEMCG
        struct mem_cgroup *mem_cgroup;
#endif
}

存储所有page的数组mem_map

mem_map的定义在:

[mm/memory.c]

struct page *mem_map;

有两个宏就是利用mem_map来将page和pfn进行转换的:

[include/asm-generic/memory_model.h]

#define __pfn_to_page(pfn)  (mem_map + ((pfn) - ARCH_PFN_OFFSET))
#define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + ARCH_PFN_OFFSET)
原文地址:https://www.cnblogs.com/muahao/p/10299725.html