3 numa-node-selection: NUMA 目标节点选择与 CPU chunk 分配

📅 2026/8/9 16:22:09
3 numa-node-selection: NUMA 目标节点选择与 CPU chunk 分配
本篇聚焦 managed / HMM 路径回迁到 CPU 时具体如何选节点、如何分配物理页、失败如何回退、以及如何按 NUMA 节点跟踪驻留。这是「回迁是否考虑 NUMA」最核心的答案所在。1. per-NUMA-node 的 CPU 驻留跟踪UVM 的 VA block 不是笼统地记「这页在 CPU 上」而是按 NUMA 节点分别记录。结构uvm_va_block_cpu_node_state_tuvm_va_block.htypedefstruct{uvm_page_mask_tresident;// 该 NUMA 节点上「有一致副本」的页uvm_page_mask_tallocated;// 该 NUMA 节点上「已分配 CPU chunk」的页unsignedlongchunks;// 该节点的 CPU chunk 存储opaque}uvm_va_block_cpu_node_state_t;block 里为每个有内存的 NUMA 节点维护一份这样的状态block-cpu.node_state[]uvm_va_block.h并有一个聚合的block-cpu.resident/block-cpu.allocated。访问器block_node_state_get(block, nid)uvm_va_block.cuvm_va_block_cpu_is_page_resident_on(block, nid, page_index)uvm_va_block.cuvm_va_block_resident_mask_get(block, UVM_ID_CPU, nid)nid NUMA_NO_NODE时返回聚合掩码。意义这让「同一页可以在不同 NUMA 节点间迁移」成为可跟踪的一等操作——回迁到 CPU 不只是「回到 CPU」而是「回到某个具体 node」。2. 目标节点归一化uvm_va_block_context_get_node()uvm_va_block.cstaticintuvm_va_block_context_get_node(uvm_va_block_t*block,uvm_va_block_region_tregion,uvm_va_block_context_t*va_block_context){constuvm_va_policy_t*policyuvm_va_policy_get_region(block,region);if(va_block_context-make_resident.dest_nid!NUMA_NO_NODE)returnva_block_context-make_resident.dest_nid;// ① 迁移请求显式节点if(policy-preferred_nid!NUMA_NO_NODE)returnpolicy-preferred_nid;// ② preferred location 的节点returnnuma_mem_id();// ③ 当前就近节点兜底}优先级链显式请求 preferred_nid 就近。3. 核心分配block_populate_pages_cpu()uvm_va_block.c。这是把 CPU 页在指定 NUMA 节点上分配出来的地方。3.1 确定preferred_nid与是否 STRICTintpreferred_nidblock_context-make_resident.dest_nid;// 测试注入调试用if(block_testblock_test-cpu_chunk_allocation_target_id!NUMA_NO_NODE)preferred_nidblock_test-cpu_chunk_allocation_target_id;// 请求未指定则用 range 的首选节点if(preferred_nidNUMA_NO_NODE)preferred_nidpolicy-preferred_nid;if(preferred_nid!NUMA_NO_NODE){uvm_va_block_cpu_node_state_t*node_stateblock_node_state_get(block,preferred_nid);allocated_masknode_state-allocated;// 只看该节点的已分配位图alloc_flags|UVM_CPU_CHUNK_ALLOC_FLAGS_STRICT;// ★ 严格落在该节点}else{allocated_maskblock-cpu.allocated;// 聚合位图}指定了节点 → 加STRICT标志底层用__GFP_THISNODE且只认该节点allocated位图已在别的节点上的页不算数会重新在目标节点分配 → 实现「换 node」。未指定节点 → 用聚合位图任意 CPU 节点都算已分配。3.2 逐页/逐 chunk 分配对区域内每个尚未在目标节点分配/驻留的页if(uvm_page_mask_test(allocated_mask,page_index)||uvm_va_block_cpu_is_page_resident_on(block,preferred_nid,page_index)){// 已在目标节点跳过continue;}allocation_sizesblock_calculate_largest_alloc_size(...);// 尽量用大 chunk含 2M...statusblock_alloc_cpu_chunk(block,allocation_sizes,chunk_alloc_flags,preferred_nid,chunk);3.3 STRICT 失败 → 回退到NUMA_NO_NODE关键容错if(statusNV_WARN_MORE_PROCESSING_REQUIRED){alloc_flags~UVM_CPU_CHUNK_ALLOC_FLAGS_STRICT;// 放弃严格preferred_nidNUMA_NO_NODE;// 不再限定节点block_context-make_resident.dest_nidNUMA_NO_NODE;}elseif(status!NV_OK){returnstatus;}语义尽力best-effort落在目标节点落不下就退回「任意节点」而不是让整个迁移失败。这保证了功能正确性优先NUMA 优化其次。4. 物理页分配uvm_cpu_chunk_alloc*()文件uvm_pmm_sysmem.c。uvm_cpu_chunk_alloc()L493→uvm_cpu_chunk_alloc_page()L427if((alloc_flagsUVM_CPU_CHUNK_ALLOC_FLAGS_STRICT)nid!NUMA_NO_NODE)kernel_alloc_flags|__GFP_THISNODE;// ★ 强制本节点不跨节点回退if(alloc_flagsUVM_CPU_CHUNK_ALLOC_FLAGS_ALLOW_MOVABLE)kernel_alloc_flags|GFP_HIGHUSER_MOVABLE;elsekernel_alloc_flags|GFP_HIGHUSER;if(alloc_sizePAGE_SIZE)// 大 chunk避免高延迟内存整理kernel_alloc_flags|__GFP_COMP|__GFP_NORETRY|__GFP_NOWARN;if(alloc_flagsUVM_CPU_CHUNK_ALLOC_FLAGS_ZERO)kernel_alloc_flags|__GFP_ZERO;if(nidNUMA_NO_NODE)pagealloc_pages(kernel_alloc_flags,get_order(alloc_size));else{UVM_ASSERT(node_isset(nid,node_online_map));pagealloc_pages_node(nid,kernel_alloc_flags,get_order(alloc_size));// ★ 指定节点}if(page(alloc_flagsUVM_CPU_CHUNK_ALLOC_FLAGS_STRICT))UVM_ASSERT(page_to_nid(page)nid);// STRICT 下断言确实落在目标节点分配标志汇总uvm_pmm_sysmem.hflag作用STRICT加__GFP_THISNODE强制目标节点不跨节点回退失败向上抛触发 §3.3 回退ALLOW_MOVABLE允许从 movable zone 分配默认不允许保证可被 GPU 直接映射ZERO分配即清零当高阶 chunk 内不是所有页都已驻留时使用ACCOUNT计入 cgroup 内存核算有 mm、非 HMM 时5.make_resident之后更新 per-node 状态拷贝完成后node_pages_mask记录「本次落到该节点的页」随后block_add_cpu_chunk()/ block 状态更新会把这些页记入对应节点的allocated/resident位图uvm_va_block.c。这样后续查询uvm_va_block_cpu_is_page_resident_on(block, nid, ...)能反映真实 NUMA 落点。6. 待优化点源码 TODO代码显式标注了两个与 NUMA 相关的已知次优点Bug 4158598staging中转分配使用NUMA_NO_NODE是次优的uvm_va_block.c。当 GPU→GPU 迁移需要 CPU 中转中转页没有 NUMA 倾向。Bug 4148100preferred_location与preferred_nid应合并为一个「处理器节点」类型uvm_va_policy.h。7. 一句话结论回迁到 CPU 的 managed/HMM 路径是 NUMA 感知的按「显式请求 → preferred_nid → 就近」选节点用__GFP_THISNODE严格分配并按节点跟踪驻留严格分配失败时优雅回退到任意节点功能正确性优先。特殊硬件EGM / 集成 GPU / C2C与 ATS 缺页的 NUMA 行为见 04-special-hardware-ats。