一文玩转Linux内核架构的内存管理(NUMA模型中的内存组织)
一,概述
内存通过节点(pg_data_t)来管理,每个节点关联到系统中的一个处理器节点又进一步划分为内存域,是对内存的进一步细分,一个节点最多有3个内存域。各个内存域关联了一个数组,用来组织属于该内存域的物理内存页(页帧)。对各个页帧,都分配了一个struct page实例以及所需的管理数据。除了节点自己的内存域,每个节点还提供了一个备用列表(struct zonelist),该列表包含了其他节点,可用于代替当前节点分配内存。

二,数据结构
1. 内存域类型
【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)


node_zones:包含了节点内各内存域的数据结构.
node_zonelists:指定了备用节点及其内存域的列表,如果当前节点没有可用空间时,可在备用节点内分配内存
nr_zones:节点内内存域的个数
node_mem_map:包含节点内的所有物理内存页
bdata:指向自举内存分配器数据结构的实例
node_start_pfn:该节点内第一个页帧的逻辑编号,系统内所有页帧是依次编号的,每个页帧的号码是全局唯一的
node_present_pages:节点中页帧的个数
node_spanned_pages:节点中包含空洞的页帧个数
node_id:全局节点ID
pgdat_next:连接到下一个内存节点,系统中的所有内存节点都通过单链表连接起来.
kswapd_wait:交换守护进程的等待队列.
【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)
节点状态管理数据结构
static inline void node_set_state(int node, enum node_states state)
static inline void node_clear_state(int node, enum node_states state)
3.内存域
ZONE_PADDING:生成填充字段,使常用数据结构处于自身的缓冲行中
pages_min, pages_high,pages_low:页换出时使用的内存水印,如果内存不足,内核可以将页写到硬盘
空闲页多于pages_high:内存域是理想状态
空闲页低于pages_low:内核开始将页换出到硬盘
空闲页低于page_min:页回收工作压力大
lowmem_reserve:用于一些无论如何都不能失败的关键性内存分配,分别为各个内存域指定了若干页
pageset:用于实现每个CPU的热/冷页帧列表,用于保存可用于满足分配的新鲜页,仍然在高速缓存中的称为热页
free_area:用于实现伙伴系统,每个数组元素表示固定长度的一些连续内存区
active_list:活动页的集合
inactive_list:不活动页的集合
nr_scan_active和nr_scan_inactive:内存回收时需要扫描的活动和不活动页的数据
pages_scanned:从上次换出一页以来,有多少页未能成功扫描
flags:内存域的当前状态
ZONE_ALL_UNRECLAIMABLE:页无法回收
ZONE_RECLAIM_LOCKED:CPU回收内存域时设置,阻止其他CPU进行同样操作
ZONE_OOM_LOCKED:内核试图杀死消耗进程最多的进程时设置该标志位
vm_stat:维护该内存域的统计信息
prev_priority:上一次扫描该内存域的优先级
wait_table, wait_table_bits和wait_table_hash_nr_entries实现了一个等待队列,可用于等待某一页表变为可用的进程
zone_pgdat:指向父节点
4.内存域水印
在计算内存水印前,内核首先要确定为关键性分配保留的内存空间的最小值,该值随可用内存大小而非线性增长,并保存在全局变量min_free_kbytes中,一个不变的约束是不能少于128k也不能多于64MB。
数据结构中水印值的填充由init_per_zone_pages_min处理,该函数在内核启动期间调用,主要调用如下两个函数:
setup_per_zone_pages_min负责设置struct zone中的pages_min,pages_low和pages_high成员,在计算出高端内存区域之外的页面总数后(保存在lowmem_pages),内核迭代系统中的所有内存域并执行相关计算
lowmem_reserve的计算由setup_per_zone_lowmem_reserve完成,内核迭代系统中所有节点,对每个节点的各个内存域分别计算预留内存最小值
5. 冷热页
struct zone的pageset成员用于实现冷热分配器,热页是指那些加载到CPU高速缓存的页,其数据访问速度比不在高速缓存中的冷页快
cout:该列表相关的页的数目
high:如果count > high则表明列表中的页太多了
list:双链表,保存了当前CPU的冷页或者热页
6. 页帧
页帧是系统内存的最小单位,对内存中的每一个页都会创建struct page的一个实例
页的广泛使用,增加了struct page的复杂性(内核不同部分对相同的数据有不同的解释)和保持结构长度的难度。
slab,freelist和inuse用于slub分配器
flags存储了和体系结构无关的标志,用于描述页的属性
_count是一个使用计数,表示内核中引用该页的次数
_mapcount表示在页表中有多少项指向该页
lru是一个表头,用于在各种链表上维护该页,以便将页按照不同类别分组,最重要的类别是活动和不活动页
内核可以将多个毗连的页合并为较大的复合页,第一个页称作首页,而所有其余各页叫做尾页,所有尾页对应的page实例,都将first_page设置为指向首页
mapping指定了页帧所在的地址空间,index是页帧在映射内部的偏移量,地址空间是一个非常一般的概念,例如,可以用在向内存读取文件时,用于将文件的内容与装载数据的内存区关联起来。通过一个小技巧,mapping不仅能保存一个指针,还能包含一些额外的信息,用于判断页是否属于某个未关联到地址空间某个匿名内存区:如果将mapping最低位置1,该指针指向另外一个数据结构(anon_vma),这个结构对实现匿名映射的逆向映射很重要
private指向私有数据的指针,根据页的用途,可以用不同方式使用该指针
virtual用于高端内存区域中的页
体系结构无关的标志
PG_locked:锁定页面不允许内核的其他部分访问.
PG_error:涉及该页的I/O操作期间发生错误.
PG_referenced, PG_active:控制系统中使用该页的活跃程度.
PG_uptodate:页的数据已经从块设备读取,其间没有出错
PG_dirty:与硬盘上的数据相比,页的内容发生改变.
PG_lru:有助于实现页面回收和切换,内核使用两个最近最少使用链表来区分活动页和不活动页,如果页在其中一个链表中,该位置位,如果在活动链表中,PG_active会置位
PG_highmem:页在高端内存中
PG_private:表明page的private成员非空
PG_writeback:页的内容处于向块设备回写过程中
PG_slab:页是slab分配器的一部分
PG_swapcache:页处于交换缓存中
PG_reclaim:内核决定回收特定页时设置
PG_compound:该页属于一个更大复合页
PG_buddy:表明页空闲且包含在伙伴系统的列表中
