当前位置: 首页 > news >正文

Linux中zonelist分配策略初始化

前言

在NUMA系统中,为了平衡访问延迟与内存利用率,进行分层设计智能回退机制

  • 空间分层:通过node_zones将物理内存按功能和特性划分为DMA、NORMAL、HIGHMEM三个层次,每个区域服务于特定的使用场景

  • 拓扑感知:在NUMA系统中,find_next_best_node算法综合考虑节点距离、CPU分布和当前负载,构建出最优的访问序列

  • 策略分离:不同的GFP分配标志对应不同的zonelist,确保每种类型的内存请求都能遵循最合适的分配路径

  • 动态适应:负载均衡机制使得系统能够根据运行时状态自动调整内存分配策略,避免热点和竞争

构建所有内存区域列表build_all_zonelists

void __init build_all_zonelists(void)
{int i;for(i = 0 ; i < numnodes ; i++)build_zonelists(NODE_DATA(i));printk("Built %i zonelists\n", numnodes);
}

1. 函数功能

为每个内存节点构建区域列表(zonelists)

2. 代码详细解释

2.1. build_all_zonelists 函数

void __init build_all_zonelists(void)
{int i;for(i = 0 ; i < numnodes ; i++)build_zonelists(NODE_DATA(i));printk("Built %i zonelists\n", numnodes);
}
  • __init:表示该函数只在初始化阶段使用
  • numnodes:系统中的内存节点数量(NUMA系统可能有多个节点)
  • for(i = 0 ; i < numnodes ; i++):遍历所有内存节点
  • NODE_DATA(i):获取第i个内存节点的pg_data_t结构指针
  • build_zonelists(NODE_DATA(i)):为每个节点构建区域列表
  • printk("Built %i zonelists\n", numnodes):打印构建的zonelist数量

构建内存节点区域列表build_zonelists

static void __init build_zonelists(pg_data_t *pgdat)
{int i, j, k, node, local_node;int prev_node, load;struct zonelist *zonelist;DECLARE_BITMAP(used_mask, MAX_NUMNODES);/* initialize zonelists */for (i = 0; i < GFP_ZONETYPES; i++) {zonelist = pgdat->node_zonelists + i;memset(zonelist, 0, sizeof(*zonelist));zonelist->zones[0] = NULL;}/* NUMA-aware ordering of nodes */local_node = pgdat->node_id;load = numnodes;prev_node = local_node;bitmap_zero(used_mask, MAX_NUMNODES);while ((node = find_next_best_node(local_node, used_mask)) >= 0) {/** We don't want to pressure a particular node.* So adding penalty to the first node in same* distance group to make it round-robin.*/if (node_distance(local_node, node) !=node_distance(local_node, prev_node))node_load[node] += load;prev_node = node;load--;for (i = 0; i < GFP_ZONETYPES; i++) {zonelist = pgdat->node_zonelists + i;for (j = 0; zonelist->zones[j] != NULL; j++);k = ZONE_NORMAL;if (i & __GFP_HIGHMEM)k = ZONE_HIGHMEM;if (i & __GFP_DMA)k = ZONE_DMA;j = build_zonelists_node(NODE_DATA(node), zonelist, j, k);zonelist->zones[j] = NULL;}}
}

1. 函数功能

为指定的内存节点构建完整的zonelist结构,包含本节点和其他节点的内存区域,按照NUMA距离和内存类型排序

2. 代码详细解释

2.1. 变量声明和初始化

int i, j, k, node, local_node;
int prev_node, load;
struct zonelist *zonelist;
DECLARE_BITMAP(used_mask, MAX_NUMNODES);
  • i, j, k:循环计数器
  • node:当前处理的节点ID
  • local_node:本地节点ID
  • prev_node:前一个处理的节点ID
  • load:负载权重
  • zonelist:指向当前处理的zonelist
  • DECLARE_BITMAP(used_mask, MAX_NUMNODES):声明位图,用于标记已处理的节点

2.2. 初始化zonelists数组

/* initialize zonelists */
for (i = 0; i < GFP_ZONETYPES; i++) {zonelist = pgdat->node_zonelists + i;memset(zonelist, 0, sizeof(*zonelist));zonelist->zones[0] = NULL;
}
  • GFP_ZONETYPES:不同GFP标志对应的zonelist类型数量
  • 遍历所有zonelist类型:
    • zonelist = pgdat->node_zonelists + i:获取第i个zonelist
    • memset(zonelist, 0, sizeof(*zonelist)):清零整个zonelist结构
    • zonelist->zones[0] = NULL:设置第一个zone为NULL,表示空列表

2.3. NUMA节点排序准备

/* NUMA-aware ordering of nodes */
local_node = pgdat->node_id;
load = numnodes;
prev_node = local_node;
bitmap_zero(used_mask, MAX_NUMNODES);
  • local_node = pgdat->node_id:获取当前节点的ID
  • load = numnodes:初始化负载值为节点总数
  • prev_node = local_node:前一个节点初始化为本地节点
  • bitmap_zero(used_mask, MAX_NUMNODES):清空已使用节点位图

2.4. 节点遍历循环

while ((node = find_next_best_node(local_node, used_mask)) >= 0) {
  • find_next_best_node(local_node, used_mask):找到距离本地节点最近且未处理的节点
  • 循环直到所有节点都被处理(返回负值)

2.5. 负载平衡机制

/** We don't want to pressure a particular node.* So adding penalty to the first node in same* distance group to make it round-robin.*/
if (node_distance(local_node, node) !=node_distance(local_node, prev_node))node_load[node] += load;
prev_node = node;
load--;
  • node_distance(local_node, node):计算本地节点到当前节点的距离
  • 如果距离发生变化:
    • node_load[node] += load:给该距离组的第一个节点增加负载惩罚
  • prev_node = node:更新前一个节点
  • load--:减少负载值,后续节点惩罚递减

2.6. 构建每个zonelist

for (i = 0; i < GFP_ZONETYPES; i++) {zonelist = pgdat->node_zonelists + i;for (j = 0; zonelist->zones[j] != NULL; j++);k = ZONE_NORMAL;if (i & __GFP_HIGHMEM)k = ZONE_HIGHMEM;if (i & __GFP_DMA)k = ZONE_DMA;j = build_zonelists_node(NODE_DATA(node), zonelist, j, k);zonelist->zones[j] = NULL;
}

获取当前zonelist和位置

zonelist = pgdat->node_zonelists + i;
for (j = 0; zonelist->zones[j] != NULL; j++);
  • 获取第i个zonelist
  • 找到当前zonelist的末尾位置(第一个NULL位置)

确定起始区域类型

k = ZONE_NORMAL;
if (i & __GFP_HIGHMEM)k = ZONE_HIGHMEM;
if (i & __GFP_DMA)k = ZONE_DMA;
  • 默认从ZONE_NORMAL开始
  • 如果GFP标志包含__GFP_HIGHMEM,从ZONE_HIGHMEM开始
  • 如果GFP标志包含__GFP_DMA,从ZONE_DMA开始

构建节点区域列表

j = build_zonelists_node(NODE_DATA(node), zonelist, j, k);
zonelist->zones[j] = NULL;
  • 调用build_zonelists_node将节点的区域添加到zonelist
  • 在末尾设置NULL终止符

3. 内存分配时的完整流程

当调用 kmalloc(GFP_KERNEL) 时:

  1. 确定zonelist:根据GFP_KERNEL选择 node_zonelists[1]
  2. 遍历zonelist:按顺序尝试每个Zone
  3. 分配尝试
    • 先尝试本节点的ZONE_NORMAL
    • 如果失败,尝试本节点的ZONE_DMA
    • 如果还失败,尝试其他节点的ZONE_NORMAL
    • 继续直到成功或全部失败
  • node_zones = “我有什么内存” - 描述节点物理内存的静态划分
  • node_zonelists = “我怎么分配内存” - 定义内存分配的动态策略

添加区域到指定分配列表build_zonelists_node

static int __init build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist, int j, int k)
{switch (k) {struct zone *zone;default:BUG();case ZONE_HIGHMEM:zone = pgdat->node_zones + ZONE_HIGHMEM;if (zone->present_pages) {
#ifndef CONFIG_HIGHMEMBUG();
#endifzonelist->zones[j++] = zone;}case ZONE_NORMAL:zone = pgdat->node_zones + ZONE_NORMAL;if (zone->present_pages)zonelist->zones[j++] = zone;case ZONE_DMA:zone = pgdat->node_zones + ZONE_DMA;if (zone->present_pages)zonelist->zones[j++] = zone;}return j;
}
  • pgdat:内存节点数据指针
  • zonelist:要构建的区域列表
  • j:当前在zonelist中的位置索引
  • k:起始区域类型

1. 内存区域类型详解

Linux内存区域类型

#define ZONE_DMA		0
#define ZONE_NORMAL		1
#define ZONE_HIGHMEM		2
#define MAX_NR_ZONES		3

1.1. 函数开始和变量声明

switch (k) {struct zone *zone;
  • 声明zone指针,在switch语句的所有case中共享
  • 这是C语言的特性,在switch开头声明,所有case可见

1.2. 默认情况处理

default:BUG();
  • 如果k不是预期的区域类型,触发内核BUG(致命错误)

1.3. HIGHMEM区域处理

case ZONE_HIGHMEM:zone = pgdat->node_zones + ZONE_HIGHMEM;if (zone->present_pages) {
#ifndef CONFIG_HIGHMEMBUG();
#endifzonelist->zones[j++] = zone;}
  • zone = pgdat->node_zones + ZONE_HIGHMEM:获取HIGHMEM区域指针
  • if (zone->present_pages):检查该区域是否有实际存在的页面
  • #ifndef CONFIG_HIGHMEM:如果没有配置HIGHMEM支持但存在HIGHMEM页面,触发BUG
  • zonelist->zones[j++] = zone:将区域添加到zonelist,并递增索引

1.4. NORMAL区域处理

case ZONE_NORMAL:zone = pgdat->node_zones + ZONE_NORMAL;if (zone->present_pages)zonelist->zones[j++] = zone;
  • 检查NORMAL区域是否有页面,有则添加到zonelist

1.5. DMA区域处理

case ZONE_DMA:zone = pgdat->node_zones + ZONE_DMA;if (zone->present_pages)zonelist->zones[j++] = zone;
  • 检查DMA区域是否有页面,有则添加到zonelist

1.6. 函数返回

return j;
  • 返回更新后的zonelist索引位置

NUMA节点排序find_next_best_node

static int __init find_next_best_node(int node, void *used_node_mask)
{int i, n, val;int min_val = INT_MAX;int best_node = -1;for (i = 0; i < numnodes; i++) {cpumask_t tmp;/* Start from local node */n = (node+i)%numnodes;/* Don't want a node to appear more than once */if (test_bit(n, used_node_mask))continue;/* Use the local node if we haven't already */if (!test_bit(node, used_node_mask)) {best_node = node;break;}/* Use the distance array to find the distance */val = node_distance(node, n);/* Give preference to headless and unused nodes */tmp = node_to_cpumask(n);if (!cpus_empty(tmp))val += PENALTY_FOR_NODE_WITH_CPUS;/* Slight preference for less loaded node */val *= (MAX_NODE_LOAD*MAX_NUMNODES);val += node_load[n];if (val < min_val) {min_val = val;best_node = n;}}if (best_node >= 0)set_bit(best_node, used_node_mask);return best_node;
}

1. 函数功能

在NUMA系统中找到距离指定节点"最近"且未使用的最佳节点,用于构建内存分配的fallback顺序

2. 代码详细解释

2.1. 变量声明

int i, n, val;
int min_val = INT_MAX;
int best_node = -1;
  • i:循环计数器
  • n:当前检查的节点ID
  • val:当前节点的综合距离值
  • min_val:最小距离值,初始化为最大整数
  • best_node:最佳节点ID,初始化为-1表示未找到

2.2. 主循环开始

for (i = 0; i < numnodes; i++) {cpumask_t tmp;/* Start from local node */n = (node+i)%numnodes;
  • 遍历所有节点:i = 0numnodes-1
  • cpumask_t tmp:临时变量,用于检查节点是否有CPU
  • n = (node+i)%numnodes关键技巧 - 从本地节点开始环形搜索
    • i=0 时:n = node (本地节点)
    • i=1 时:n = node+1 (下一个节点)
    • 使用模运算确保不越界

2.3. 跳过已使用的节点

    /* Don't want a node to appear more than once */if (test_bit(n, used_node_mask))continue;
  • test_bit(n, used_node_mask):检查节点n是否已在used_node_mask位图中被标记
  • 如果已使用,continue跳过该节点
  • 确保每个节点只出现一次在最终的zonelist

2.4. 优先使用本地节点

    /* Use the local node if we haven't already */if (!test_bit(node, used_node_mask)) {best_node = node;break;}
  • !test_bit(node, used_node_mask):如果本地节点还未被使用
  • best_node = node:直接选择本地节点作为最佳节点
  • break:立即跳出循环,因为找到了最佳选择
  • 这确保了本地节点总是第一个被考虑

2.5. 计算节点距离

    /* Use the distance array to find the distance */val = node_distance(node, n);
  • node_distance(node, n):获取从源节点到目标节点的NUMA距离
  • 距离值越小表示节点越"近",访问延迟越低

2.6. 对有CPU的节点施加惩罚

    /* Give preference to headless and unused nodes */tmp = node_to_cpumask(n);if (!cpus_empty(tmp))val += PENALTY_FOR_NODE_WITH_CPUS;
  • node_to_cpumask(n):获取节点n的CPU掩码
  • !cpus_empty(tmp):检查该节点是否有CPU
  • val += PENALTY_FOR_NODE_WITH_CPUS:对有CPU的节点增加距离惩罚

2.7. 考虑节点负载

    /* Slight preference for less loaded node */val *= (MAX_NODE_LOAD*MAX_NUMNODES);val += node_load[n];
  • val *= (MAX_NODE_LOAD*MAX_NUMNODES):将距离值放大,确保距离优先级
  • val += node_load[n]:加上节点的当前负载
  • 设计原理:距离是主要因素,负载是次要的

2.8. 更新最佳节点

    if (val < min_val) {min_val = val;best_node = n;}
}
  • val < min_val:如果当前节点的综合值更小(更好)
  • min_val = val:更新最小距离值
  • best_node = n:更新最佳节点ID
  • 循环继续,寻找综合距离最小的节点

2.9. 标记已使用的节点并返回

if (best_node >= 0)set_bit(best_node, used_node_mask);return best_node;
  • best_node >= 0:如果找到了有效的节点(不是-1)
  • set_bit(best_node, used_node_mask):在used_node_mask中标记该节点已使用
  • return best_node:返回找到的最佳节点ID

3. 算法策略详解

3.1. 环形搜索策略

// 搜索顺序示例: node=2, numnodes=4
i=0: n = (2+0)%4 = 2  // 本地节点
i=1: n = (2+1)%4 = 3  // 下一个节点
i=2: n = (2+2)%4 = 0  // 环绕
i=3: n = (2+3)%4 = 1  // 继续环绕

3.2. 评分公式

综合评分 = (基础距离 + CPU惩罚) × 放大系数 + 节点负载

其中:

  • 基础距离:NUMA架构决定的访问延迟
  • CPU惩罚:鼓励使用无CPU的内存节点,避免带宽竞争
  • 放大系数:确保距离优先级高于负载
  • 节点负载:负载均衡的tie-breaker

什么是"tie-breaker"?

当两个节点在其他方面评分相同时,用负载来打破平局

http://www.dtcms.com/a/490505.html

相关文章:

  • hadoop的三副本数据冗余策略
  • 岳阳网站建设企业足球比赛直播app下载
  • React 三元运算符页面切换:完整进出流程
  • NumPy zeros_like() 函数详解
  • 网站建设要后台吗公司网页制作哪家好
  • 天津网站建设优化网页设计图片代码
  • CXR SDK实战指南:跨设备AR应用开发
  • 已知明文攻击(Known plaintext):原理、方法与防御体系深度剖析
  • ​SPI四种工作模式
  • 深度学习------YOLOV1和YOLOV2
  • 最小二乘问题详解5:非线性最小二乘求解实例
  • 算法入门数学基础
  • 错误边界:用componentDidCatch筑起React崩溃防火墙
  • 网站备案提交管局原创软文
  • 成都比较好的网站建设公司视频制作和剪辑软件
  • 如何从电脑上卸载安卓应用程序
  • 每日手撕算法--哈希映射/链表存储数求和
  • k8s的pvc和pv
  • RK3562核心板/开发板RT-Linux系统实时性及硬件中断延迟测试
  • node.js把webp,gif格式图片转换成jpg格式图片
  • 不能识别adb/usb口记录
  • SpringBoot-常用注解
  • 支付商城网站制作软件开发报价表
  • wordpress类似的平台快速优化排名公司推荐
  • Git 基础操作指南
  • 网站给部分文字做遮挡代码wordpress主题仿逛丢
  • 【bug】大模型微调bug:OSError: Failed to load tokenizer.| Lora
  • 视频生成的背后机理:Wan2技术报告分析
  • 有什么做衣服的网站吗天津市建筑信息平台
  • HTB BoardLight writeup(enlightenment 0.23.1 exploit)