欢迎关注微信公众号:qiubinwei-1986
内存架构UMA和NUMA
内存数据结构
物理内存映射
页表初始化函数,在内核使用内存前,需要初始化内核的页表,paging_init函数主要做两次映射:
map_kernel(): 映射内核映像到内核空间的虚拟地址
map_mem(): 做物理内存的线性映射
两者都是建立物理内存到内核空间虚拟地址的线性映射,但是映射的地址不一样。
map_kernel()函数
对内核映像的各个段分别进行映射,映射到内核空间的虚拟地址为vmalloc区域。vmalloc区域的范围从0xFFFF 000 1000 0000 到 0xFFFF 7DFF BFFF 0000
映射如下内容:
代码段:从_text到_etext
只读数据段:从__start_rodata到__inittext_begin
初始化代码段:从__inittext_begin到__inittext_end
初始化数据段:从__initdata_begin到__inittext_end
数据段:从_data到_end
map_mem()函数
会映射三段物理内存到线性映射区。范围从0XFFFF 8000 0000 0000 到0xFFFF FFFF FFFF FFFF
映射第一段物理内存:从0x4000 0000 到0x4008 0000
映射第二段物理内存:从内核映像结束(_end)到物理内存结束(如256GB,最大不超过128TB)
映射第三段物理内存:从内核映像开始(_start)到内核映像结束(_end)
zone初始化
Linux的内核采用内存管理区(zone)的方式进行页面管理。
zone经常会被访问,因此这个数据结构要求以L1高速缓存对齐。
ZONE_PADDING()函数让zone->lock和zone->lru_lock两个热门锁可以分布在不同的高速缓存行中,一个node对应的zone有限,因此zone数据结构不需要关注数据结构的大小。
zone结构体源码解析及部分数据成员解析:
-
_watermark:每个zone启动时会计算3个水位,分别是最低警戒水位(WMARK_MIN),低水位(WMARK_LOW)和高水位(WMARK_HIGH),这个会在页面分配器和kswapd页面回收中用到。
-
lowmem_reserve:防止页面分配器过度使用低端zone的内存
-
zone_pgdat:指向内存节点
-
pageset:用于维护每个CPU上的一系列页面,减少自旋锁的争用
-
zone_start_pfn:zone的起始页帧号
-
managed_pages:zone中被伙伴系统管理的的页面数量
-
spanned_pages:zone包含的页面数量
-
present_pages:zone中实际管理的页面数量,在一些架构下,应该和spanned_pages一致
-
free_area:伙伴系统的核心数据结构,管理空闲页块(page block)链表的数组
-
lock:并行访问时用于保护zone的自旋锁
-
lru_lock:并行访问时用于保护zone中LRU链表的自旋锁。在Linux5.0已经转移到pglist_data中
-
lruvec:LRU链表集合
-
vm_stat:zone计数值。在Linux5.0内核中,该成员已经转移到pglist_data中
struct zone {
/* Read-mostly fields */
/* zone watermarks, access with *_wmark_pages(zone) macros */
unsigned long _watermark[NR_WMARK]; // 页分配器使用的水位线
unsigned long watermark_boost;
unsigned long nr_reserved_highatomic;
long lowmem_reserve[MAX_NR_ZONES]; // 页分配器使用,当前区域保留多少页不能借给高区域类型。
...
struct pglist_data *zone_pgdat; // 指向内存节点的pglist_data实例
struct per_cpu_pageset __percpu *pageset; // 用于维护每个CPU上的一系列页面,减少自旋锁的征用
...
unsigned long zone_start_pfn; // 当前区域的起始物理页号
atomic_long_t managed_pages; // 伙伴分配器管理的物理页的数量
unsigned long spanned_pages; // 当前区域跨越的总页数,包括空洞
unsigned long present_pages; // 当前区域存在的物理页的数量,不包括空洞
const char *name; //区域名称
struct free_area free_area[MAX_ORDER]; // 不同长度的空间区域
...
spinlock_t lock; //并行访问时用于保护zone的自旋锁
/* Write-intensive fields used by compaction and vmstats. */
struct lruvec __lruvec;
ZONE_PADDING(_pad2_)
...
ZONE_PADDING(_pad3_)
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS]; //zone计数值。在Linux5.0内核中,该成员已经转移到pglist_data中
atomic_long_t vm_numa_stat[NR_VM_NUMA_STAT_ITEMS];
} ____cacheline_internodealigned_in_smp;
zone类型
如前文提到,在64位操作系统下,zone分为ZONE_DMA,ZONE_DMA32, ZONE_NORMAL三个类型(ZONE_HIGHMEM只存在32位操作系统)
enum zone_type {
#ifdef CONFIG_ZONE_DMA // Direct Memory Access,内存直接访问。
// 有些设备不能直接访问所有内存,需要访问DMA区域
ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32 // 64位系统,如果既要支持只能直接访问16MB以下内存设备
// 又要支持只能直接访问4GB以下32位设备,必须使用该区域DMA32
ZONE_DMA32,
#endif
/*
* Normal addressable memory is in ZONE_NORMAL. DMA operations can be
* performed on pages in ZONE_NORMAL if the DMA devices support
* transfers to all addressable memory.
*/
ZONE_NORMAL,
// 直接映射到内核虚拟地址空间的内存区域,又称为普通区域、直接映射区、线性映射区
#ifdef CONFIG_HIGHMEM //此区域是32位的产物,内核和用户地址空间按1:3划分
#endif
ZONE_MOVABLE, //可移动区域,是一个伪内存区域,防止内存碎片
#ifdef CONFIG_ZONE_DEVICE
ZONE_DEVICE, //设备区,为支持持久内存热插拔增加的内存区
#endif
__MAX_NR_ZONES