内存管理在内核中占据着举足轻重的地位,毕竟它是用来处理处理器和内存之间的协作的,而后两者都是计算机中最为重要的资源。内存管理的目标就是高效合理的使用物理内存,不造成浪费。
在计算机发展初期,如上图,CPU通过总线访问整个地址空间,这是一种简单经济的方式,可以尽可能使用内存。但是这个系统本身也存在伸缩性的问题,因为总线的宽度是有限的,这也限制了处理器的数量。如果添加CPU,会引起以下两个问题
于是有了下面的系统,如下图,其中北桥芯片离CPU最近,在CPU与显卡(PCIE/AGP)、内存(DRAM)等建立通信接口,处理高速信号。南桥则负责一些I/O设备,通常速度相对前者不高。比如PCI、磁盘(IDE/SATA)、USB等。
目前管理物理内存的方法主要有以下两种
也称为对称多处理器(SMP) 内存以连续的方式组织起来,每个CPU访问各个内存区都一样快。CPU通过系统总线(前端总线)连到北桥(其中包含内存控制器),与内存之间的通信必然经过北桥。IO控制器也连到北桥,IO必须通过北桥才能到达CPU。可以增加多个总线或者内存通道增加带宽。不过总的来将,UMA的这种伸缩性是有限的。
每个CPU是平等的(系统启动初始化除外),都有本地内存,离着最近访问速度最快。各个CPU之间通过总线连接,支持对其他CPU的访问。如下图,CPU不再共享北桥,而是独有一个内存控制器。
从处理器的角度看物理内存分布,内存管理子系统支持下面三种内存模型
在NUMA,CPU被划分为多个节点(node), 每个CPU对应一个本地物理内存, 即一个CPU-node对应一个内存node。 内存管理系统通过节点(node),内存域(zone)、页帧(page)三级结构描述物理内存。首先,内存划分为节点,每个节点关联一个CPU。各个节点又划分为内存域,各个内存域都关联了一个数组,用来组织属于该内存域的物理页。UMA被当作只有一个NUMA的系统。
各个内存节点保存在一个单链表中,供内核遍历。
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES]; //内存区域数组,MAX_NR_ZONES一般为3
struct zonelist node_zonelists[MAX_ZONELISTS]; //备用区域列表
int nr_zones; //内存区域数量
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
struct page *node_mem_map; // 页描述符,用于描述节点的所有物理内存页
#ifdef CONFIG_PAGE_EXTENSION
struct page_ext *node_page_ext; //页扩展属性
#endif
#endif
......
unsigned long node_start_pfn; //该节点的起始物理页号,系统中所有节点的额页帧是一次编号,全局唯一的。
unsigned long node_present_pages; //物理页总数
unsigned long node_spanned_pages; //物理页总数包含空洞
int node_id; //节点id
......
} pg_data_t;
enum zone_type {
#ifdef CONFIG_ZONE_DMA
ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
ZONE_DMA32,
#endif
ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
ZONE_HIGHMEM,
#endif
ZONE_MOVABLE,
#ifdef CONFIG_ZONE_DEVICE
ZONE_DEVICE,
#endif
__MAX_NR_ZONES
};
struct zone {
// 页分配器使用的字段
unsigned long watermark[NR_WMARK]; // 页分配器使用的水线
unsigned long nr_reserved_highatomic;
long lowmem_reserve[MAX_NR_ZONES];// 当前保留多少页(这些页不能借给高端地址的区域类型)
#ifdef CONFIG_NUMA
int node;
#endif
struct pglist_data *zone_pgdat; // 指向内存节点的pglist_data实例
struct per_cpu_pageset __percpu *pageset;
#ifndef CONFIG_SPARSEMEM
unsigned long *pageblock_flags;
#endif /* CONFIG_SPARSEMEM */
#ifdef CONFIG_NUMA
unsigned long min_unmapped_pages;
unsigned long min_slab_pages;
#endif /* CONFIG_NUMA */
/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
// 支持不连续内存模型的字段
unsigned long zone_start_pfn; // 当前区域的起始页号
unsigned long managed_pages; // 伙伴分配器管理的物理页的数量
unsigned long spanned_pages; // 当前区域总页数,包括空洞
unsigned long present_pages;
const char *name;
struct free_area free_area[MAX_ORDER];
......
} ____cacheline_internodealigned_in_smp;
页帧是系统内存中的最小单位,每个页都会对应有一个struct page的实例。一般位4KB。对应内核结构是struct page,里面有很多联合体,这样写的原因是为了节省内存。
参考:
[0] https://frankdenneman.nl/2016/07/07/numa-deep-dive-part-1-uma-numa/
[1] http://www.pcpop.com/article/628956_all.shtml
[2] 深入Linux内核架构
[3] https://ke.qq.com/webcourse/3294666/103425320
[4] https://cloud.tencent.com/developer/article/1366011