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