在Linux内核中,内存管理是核心系统的重要组成部分。为了实现高效且稳定的内存管理,内核采用了多种技术和数据结构。其中,内存节点(Node)是用于描述系统中内存分布和组织的重要概念。 本文将深入探讨Linux内核中内存节点的相关技术和数据结构。
在Linux内核中,pglist_data结构体用于描述内存节点。该结构体在include/linux/mmzone.h文件中定义。
struct pglist_data {
struct zone node_zones[MAX_NR_ZONES]; // 内存区域数组
struct zonelist node_zonelists[MAX_ZONELISTS]; // 备用区域列表
int nr_zones; // 该内存节点包含的内存区域数量
#ifdef CONFIG_FLAT_NODE_MEM_MAP // 除稀疏内存模型之外的情况
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; // 节点标识符
};
Linux内核中,CPU被划分为多个节点(node),而内存则被分簇(bank)。每个CPU对应一个本地物理内存,即一个CPU-node对应一个内存簇bank。每个内存簇被认为是一个节点。
每个内存节点都关联到系统中的一个处理器,在内核中表示为pg_data_t的实例。系统中每个节点被链接到一个以NULL结尾的pgdat_list链表中,而其中的每个节点利用pg_data_t的node_next字段链接到下一个节点。
对于PC这种UMA(统一内存访问)结构的机器来说,只使用了一个称为contig_page_data的静态pg_data_t结构。
内核提供了一系列函数和数据结构,用于对内存节点进行操作和管理。例如,通过get_node方法可以获取给定处理器对应的内存节点,而mm/page_alloc.c文件中的setup_node函数用于初始化一个新的内存节点。此外,内核还提供了诸如free_area_init、build_zonelists等函数用于构建和管理内存节点的相关数据结构。
在内核代码中,可以通过CPU的id号获取当前处理器对应的内存节点。例如,可以通过下列代码片段获取当前CPU的内存节点:
struct pglist_data *pd = NODE_DATA(raw_smp_processor_id());
要初始化一个新的内存节点,可以使用mm/page_alloc.c文件中的setup_node函数。该函数接受一个pglist_data结构体实例和一个 zonelist 结构体实例作为参数,并初始化该内存节点的相关数据结构。以下是一个示例代码片段:
struct pglist_data pd = { .node_id = 0, .node_start_pfn = 0 };
struct zonelist z = { .full_zones = 0, .zonelist = { } };
setup_node(NODE_DATA(0), &pd, &z);
include\linux\mmzone.h
由于不同计算机类型的硬件架构不同, 限制了页框的使用。Linux内核必须处理80×86体系结构的以下两种硬件约束:
为了解决以上两个限制,Linux将系统的页划分为不同的内存池,形成不同的内存区。这种划分是逻辑上的分组,对于内核来说,没有任何物理意义。下面解释如何使用struct zone结构来管理这些内存区。
在Linux内核中,内存被划分为三个不同的区域,分别是ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。
区 | 描述 | 物理内存 |
---|---|---|
ZONE_DMA | 这是用于直接内存存取(DMA)的区域,其物理内存位于系统的前16MB。这个区域的页框只能被DMA设备直接访问,内核不能直接访问这些页框。 | <16MB |
ZONE_NORMAL | 这是正常可寻址的区域,其物理内存位于16MB到896MB之间。内核可以直接访问这个区域的页框。 | 16~896MB |
ZONE_HIGHMEM | 这是动态映射的区域,其物理内存位于896MB以上。这个区域的页框不能被内核直接访问,但可以通过特定的API进行动态映射和访问。 | >896MB |
内核通过struct zone结构来管理每个内存区。这个结构包含了关于该内存区的各种信息,包括该区的名称、自旋锁、水位值等。下面是对struct zone结构的一些重要成员的解释:
name:一个以NULL结束的字符串,表示该内存区的名称。在内核启动期间进行初始化。
lock:一个自旋锁,用于保护该结构免于并发访问。注意,这个锁只保护结构本身,而不保护驻留在这个区中的所有页。
watermark:一个数组,包含该区的最小值、最低和最高水位值。内核使用这些水位值来设置合适的内存消耗基准。这些水位值会根据系统的空闲内存量进行动态调整。
如何确定某个页框属于哪个节点或管理区?
由每个页框描述符中的flag的高位索引的,比如page_zone()函数就是接收页描述符的地址作为参数,返回页描述符中flag的高位,并到zone_table[ ]数组中确定相应的管理区描述符的地址。具体还要看实际的操作系统,下面只是个简化的示例。
#include
/* 假设页框描述符的地址为pmd */
struct page_md *pmd;
/* 调用page_zone()函数获取flag的高位索引 */
int index = page_zone(pmd);
/* 查询zone_table[]数组获取相应管理区的描述符地址 */
struct zone *zone = &zone_table[index];
/* 输出结果 */
printk("The page frame belongs to zone %s\n", zone->name);
物理内存组织结构
深入浅出Linux内核内存管理基础