注:本文分析基于linux-4.18.0-193.14.2.el8_2内核版本,即CentOS 8.2
1、Node
Node是内存管理最顶层的结构,在NUMA架构下,CPU平均划分为多个Node,每个Node有自己的内存控制器及内存插槽。CPU访问自己Node上的内存时速度快,而访问其他CPU所关联Node的内存的速度比较慢。而UMA则被当做只有一个Node的NUMA系统。
内核中使用struct pglist_data来描述一个Node节点,我们看下主要的一些变量,
/*
* On NUMA machines, each NUMA node would have a pg_data_t to describe
* it's memory layout. On UMA machines there is a single pglist_data which
* describes the whole memory.
*/
typedef struct pglist_data {
//当前节点中包含的zone数组,如ZONE_DMA,ZONE_DMA32,ZONE_NORMAL
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[MAX_ZONELISTS];
//当前节点中不同内存域zone的数量
int nr_zones;
...
unsigned long node_start_pfn; //当前节点的页帧的起始值
unsigned long node_present_pages; //当前节点可使用的page数量
unsigned long node_spanned_pages; //包含空洞内存的内存总长度
int node_id; //节点编号
wait_queue_head_t kswapd_wait; //kswapd进程的等待队列
wait_queue_head_t pfmemalloc_wait; //直接内存回收过程中的进程等待队列
struct task_struct *kswapd; //指向该结点的kswapd进程的task_struct
int kswapd_order; //kswap回收页面大小
enum zone_type kswapd_classzone_idx; //kswap扫描的内存域范围
...
//每个节点的保留内存
unsigned long totalreserve_pages;
//zone reclaim becomes active if more unmapped pages exist.
unsigned long min_unmapped_pages;
//当前node中可回收slab页面阈值,超过该值才会回收该node内存
unsigned long min_slab_pages;
...
/* Fields commonly accessed by the page reclaim scanner */
//LRU链表管理结构
struct lruvec lruvec;
...
} pg_data_t;
- 第一部分主要是内存管理域,比如包括的zone类型,以及该Node管理的内存范围等。
- 第二部分主要是和kswap进程相关,我们后续会专门对kswap进行梳理,看看它是如何工作的。从这里我们也能看出,kswap是和Node挂钩,也就是每个Node对应一个kswap进程,更多的我们后续再说。
- 第三部分是LRU链表相关,这个成员原本是在zone结构体中,后来移到了Node上,我们后面也会对页面的LRU管理进行梳理。
2、Zone
在x86_64的机器上,主要有以下几种Zone类型,
[root@my-test proc]# cat /proc/zoneinfo | grep -w zone
Node 0, zone DMA
Node 0, zone DMA32
Node 0, zone Normal
Node 0, zone Movable
Node 0, zone Device
ZONE_DMA:内存首部16MB,即低端范围的物理内存,某些工业标准体系结构(ISA)设备需要用到ZONE_DMA;
ZONE_DMA32:标记了使用32位地址字可寻址, 适合DMA的内存域。
ZONE_NORMAL:16MB~896MB,该部分的内存由内核直接映射到线性地址空间的较高部分;
ZONE_HIGHMEM:896MB~末尾,将保留给系统使用,是系统中预留的可用内存空间,不能被内核直接映射。
ZONE_MOVABLE:内核定义了一个伪内存域ZONE_MOVABLE, 在防止物理内存碎片的机制memory migration中需要使用该 内存域. 供防止物理内存碎片的极致使用。
ZONE_DEVICE:为支持热插拔设备而分配的Non Volatile Memory非易失性内存。
内存域Zone用struct zone描述,
struct zone {
/* Read-mostly fields */
//每个zone的min、low、high水位值
unsigned long watermark[NR_WMARK];
unsigned long nr_reserved_highatomic;
//每个zone的预留内存,有些进程只能使用某些低地址的zone空间,但是在极端情况下
//有些进程本来是可以使用高地址zone,但是可能此时系统内存刚好被占用,就会去
//使用这些低地址zone的内存,导致后续一些进程即使高地址zone还有空闲内存,但还是
//无法获得内存。因此需要为每个zone预留一部分内存,保证跨zone内存分配有上限
//预留的值可通过/proc/sys/vm/lowmem_reserve_ratio设置
long lowmem_reserve[MAX_NR_ZONES];
int node;
struct pglist_data *zone_pgdat; //指向所属node节点
struct per_cpu_pageset __percpu *pageset; //每CPU保留一些页面,避免自旋锁冲突
unsigned long zone_start_pfn; //该内存域起始页帧地址
unsigned long managed_pages; //该zone中被伙伴系统管理的页面数量
unsigned long spanned_pages; //该zone包含的总页数,包含空洞
unsigned long present_pages; //该zone包含的总页数,不包含空洞
//内存域名字,如"DMA", "NROMAL"
const char *name;
...
//页面使用状态的信息,用于伙伴系统的,每个元素对应不同阶page大小
//目前系统上有11种不同大小的页面,从2^0 - 2^10,即4k-4M大小的页面
struct free_area free_area[MAX_ORDER];
/* zone flags, see below */
unsigned long flags;
...
} ____cacheline_internodealigned_in_smp;
- 第一部分为zone的水位值watermark,包括MIN、LOW、HIGH三个水位,这三个值根据/proc/sys/vm/min_free_kbytes来设置;以及zone的保留内存lowmem_reserve,则是由/proc/sys/vm/lowmem_reserve_ratio控制,关于这两部分可以参考之前的文章《vm内核参数之内存水位min_free_kbytes和保留内存lowmem_reserve_ratio》。
- 第二部分就是内存域的描述,比如管理的内存范围以及page数量
- 第三部分就是free_area,用于管理伙伴系统上11个不同大小的page页面
3、Page
页是内存管理的最小单位,页面中的内存其物理地址是连续的,每个物理页面由struct page描述。为了节约内存,struct page是个联合体,我们在之前梳理slab的时候知道,slab管理结构也是用struct page描述。
struct page {
/* 在lru算法中主要用到的标志
* PG_active: 表示此页当前是否活跃,当放到或者准备放到活动lru链表时,被置位
* PG_referenced: 表示此页最近是否被访问,每次页面访问都会被置位
* PG_lru: 表示此页处于lru链表
* PG_mlocked: 表示此页被mlock锁在内存中,禁止换出和释放
* PG_swapbacked: 表示此页依靠swap,可能是进程的匿名页(堆、栈、数据段),匿名mmap共享内存映射,shmem共享内存映射
*/
unsigned long flags; /* Atomic flags, some possibly
* updated asynchronously */
struct { /* Page cache and anonymous pages */
//通过该结构挂到node的对应LRU链表
struct list_head lru;
//以下三个变量都是pagecache相关
struct address_space *mapping;
pgoff_t index; /* Our offset within mapping. */
/**
* @private: Mapping-private opaque data.
* Usually used for buffer_heads if PagePrivate.
* Used for swp_entry_t if PageSwapCache.
* Indicates order in the buddy system if PageBuddy.
*/
unsigned long private;
};
...
struct mem_cgroup *mem_cgroup; //cgroup相关
void *virtual; //页面内核虚拟地址
...
} _struct_page_alignment;
- 第一部分是LRU链表相关,包括用于LRU的标志位,和LRU表头
- 第二部分是pagecache相关,我们之后也会梳理