sysctl 处理函数和节点负载数组。
一、numa_zonelist_order[]与长度宏
static char numa_zonelist_order[] = "Node"; #define NUMA_ZONELIST_ORDER_LEN 16
1.numa_zonelist_order[]
当前 zonelist 排序策略的字符串表示,初始为
"Node"。这是 sysctl 暴露给用户读写的值:用户读它能看到当前策略,写它能修改策略。
static:文件内私有,通过 sysctl 接口对外暴露。
2.NUMA_ZONELIST_ORDER_LEN = 16
字符串缓冲区长度上限。
有效值只有
"Default"、"Node"、"Zone"(或它们的首字母形式),16 字节足够。用于 sysctl 表定义时的
maxlen和写入时的长度检查。
二、numa_zonelist_order_handler()—— sysctl 处理函数
static int numa_zonelist_order_handler(const struct ctl_table *table, int write, void *buffer, size_t *length, loff_t *ppos) { if (write) return __parse_numa_zonelist_order(buffer); return proc_dostring(table, write, buffer, length, ppos); }作用
这是/proc/sys/vm/numa_zonelist_order的读写处理函数。
逻辑
1. 写操作(write != 0)
return __parse_numa_zonelist_order(buffer);
直接把用户写入的缓冲区传给
__parse_numa_zonelist_order()(前面讲过的解析函数)。该函数解析字符串,设置
user_zonelist_order。若值有效且与当前不同,
__parse_numa_zonelist_order()的调用方(实际是更上层的逻辑)会触发build_all_zonelists()重建 zonelist。
注意:这里直接返回解析结果,没有调用proc_dostring()。这意味着它不走标准的“把用户输入写回table->data”流程——numa_zonelist_order[]数组的更新由__parse_numa_zonelist_order()或其调用链负责。
2. 读操作(write == 0)
return proc_dostring(table, write, buffer, length, ppos);
调用标准
proc_dostring(),把table->data(即numa_zonelist_order[])的内容复制到用户缓冲区。用户读到当前策略字符串。
与__parse_numa_zonelist_order()的配合
完整的写入流程(在更上层的 sysctl 框架中):
用户 write("/proc/sys/vm/numa_zonelist_order", "zone") └── numa_zonelist_order_handler(table, write=1, buffer="zone", ...) └── __parse_numa_zonelist_order("zone") ├── 解析 → user_zonelist_order = ZONELIST_ORDER_ZONE └── 返回 0 └── 上层框架:若值变化,调用 build_all_zonelists() └── 更新 numa_zonelist_order[] 字符串sysctl 表定义(补充)
这个 handler 通常注册在:
{ .procname = "numa_zonelist_order", .data = &numa_zonelist_order, .maxlen = NUMA_ZONELIST_ORDER_LEN, .mode = 0644, .proc_handler = numa_zonelist_order_handler, },.data指向numa_zonelist_order[];.maxlen用NUMA_ZONELIST_ORDER_LEN;.proc_handler是本函数。
三、node_load[]—— 节点负载数组
static int node_load[MAX_NUMNODES];
作用
记录每个 NUMA 节点的“负载”,用于 zonelist 排序决策。
MAX_NUMNODES是系统支持的最大节点数。static:文件内私有。
在 zonelist 排序中的用途
node_load[]主要服务于find_next_best_node()和build_zonelists()的“按距离排序”逻辑:
初始化:
build_zonelists()开始时,把每个节点的node_load设为其 zone 数量或内存大小,作为初始“负载”。选择下一个节点:
find_next_best_node()遍历所有节点,根据距离和负载选出一个“最优”节点加入当前节点的 zonelist。更新负载:选中一个节点后,递增其
node_load,这样下次选择时会倾向于负载低的节点,避免某个节点被过度使用。
典型逻辑
static int find_next_best_node(int node, nodemask_t *used_node_mask) { int n, val; int min_val = INT_MAX; int best_node = NUMA_NO_NODE; const struct cpumask *tmp = cpumask_of_node(node); for_each_node_state(n, N_ONLINE) { if (node_isset(n, *used_node_mask)) continue; /* 优先选本节点(距离为 0) */ if (n == node) return n; /* 计算“距离 + 负载”的代价 */ val = node_distance(node, n); val += node_load[n]; if (val < min_val) { min_val = val; best_node = n; } } if (best_node >= 0) node_load[best_node]++; return best_node; }node_load[n]作为惩罚项加入距离计算。选中后
node_load[best_node]++,下次该节点更难被选中(负载均衡)。这实现了“近的优先,但不过度集中”的策略。
与numa_zonelist_order的关系
Node order:更依赖节点距离和
node_load做节点间排序;Zone order:先按 zone 类型分组,
node_load用于同类型 zone 内的节点排序。
四、三者关系与整体流程
用户配置(启动参数 / sysctl) └── numa_zonelist_order_handler() └── __parse_numa_zonelist_order() └── user_zonelist_order = ZONELIST_ORDER_{DEFAULT,NODE,ZONE} └── build_all_zonelists() ├── set_zonelist_order() // 决定策略 └── build_zonelists(pgdat) ├── find_next_best_node() // 用 node_load[] 选节点 │ └── node_load[n]++ └── build_zonerefs_node() // 填充 zone五、小结表
| 项 | 说明 |
|---|---|
numa_zonelist_order[] | 当前策略的字符串表示,初始"Node",sysctl 读写 |
NUMA_ZONELIST_ORDER_LEN | 字符串缓冲区长度上限(16) |
numa_zonelist_order_handler() | sysctl 处理函数:写时调用__parse_numa_zonelist_order(),读时用proc_dostring() |
node_load[] | 每个 NUMA 节点的负载计数,用于 zonelist 排序时做负载均衡 |
| 关键配套 | __parse_numa_zonelist_order()、build_all_zonelists()、find_next_best_node()、build_zonerefs_node() |
| 可见性 | 都是 |