物理内存相关的三个数据结构
基于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)