资讯动态

Linux 内核 NUMA 内存策略(NUMA Memory Policy)完整指南:作用域、模式标志与系统调用 API

发布时间:2026/9/11 6:30:42 来源:尧图企业网站定制
Linux 内核 NUMA 内存策略NUMA Memory Policy完整指南作用域、模式标志与系统调用 API【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linuxNUMANon-Uniform Memory Access架构下内存访问延迟因节点而异Linux 内核通过内存策略memory policy机制决定进程的内存究竟从哪个 NUMA 节点分配。本文以 Documentation/admin-guide/mm/numa_memory_policy.rst 为骨架结合当前内核源码mm/mempolicy.c、include/uapi/linux/mempolicy.h深入剖析内存策略的作用域、六种模式、模式标志、引用计数模型、四个系统调用 API 以及与 cpuset 的交互帮助读者理解并编写 NUMA-aware 应用程序。什么是 NUMA 内存策略在 Linux 内核中内存策略决定了在 NUMA 系统或模拟 NUMA 系统上内核将从哪个节点分配内存。Linux 自 2.4 时代就开始支持非均匀内存访问架构的平台而当前的内存策略支持于 2004 年 5 月左右加入 Linux 2.6。本文描述的就是这套 2.6 以来内存策略的概念与 API。需要注意内存策略不应与 cpuset 混淆。cpuset见 Documentation/admin-guide/cgroup-v1/cpusets.rst是一种管理机制用于限制一组进程可以从哪些节点分配内存而内存策略是 NUMA-aware 应用程序可以利用的编程接口。当 cpuset 和策略同时作用于一个任务时cpuset 的限制优先。内存策略的作用域ScopeLinux 内核支持四种作用域的内存策略从最通用到最具体排列如下。系统默认策略System Default Policy该策略被硬编码进内核管辖所有未被更具体策略作用域控制的内存分配。系统正常运行期间系统默认策略使用本地分配local allocation但在引导期间系统默认策略会设置为跨所有内存充足的节点进行交错分配interleave以免引导节点被引导期的内存分配过载。任务/进程策略Task/Process Policy这是可选的、按任务设置的策略。任务定义了该策略后它管辖该任务发起或代表该任务发起的所有、未被更具体作用域控制的内存分配若任务未定义任务策略则回退到系统默认策略。任务策略适用于任务的整个地址空间因此它可继承父任务设置的任务策略会通过fork()即不带CLONE_VM的clone()和exec*()继承给子任务这让父任务可以为对内存策略毫无感知的可执行镜像预先建立策略。几个关键细节多线程任务策略只作用于安装该策略的线程内核任务及其后续创建的线程策略安装时已存在的兄弟线程保持原有策略。仅限新页面任务策略只作用于安装之后分配的页面之前已缺页faulted in的页面留在原处不受策略变更影响。VMA 策略VMAVirtual Memory Area指任务虚拟地址空间中的一个区间。任务可以为其地址空间的某段区间定义专属策略通过mbind()系统调用设置。VMA 策略管辖支撑该地址区间的页面分配未显式设置 VMA 策略的区间回退到任务策略任务策略再回退到系统默认策略。VMA 策略有几个容易踩坑的细节仅适用于匿名页包括栈、堆等匿名段以及用MAP_ANONYMOUS标志mmap()的区间。若对文件映射应用 VMA 策略MAP_SHARED文件映射会忽略该策略MAP_PRIVATE文件映射仅在写时复制COW分配匿名页时应用。共享与继承VMA 策略被共享同一地址空间的所有任务即线程共享与安装时机无关并在fork()时继承但由于exec*()会丢弃并重建地址空间VMA 策略不能跨 exec 继承——因此只有 NUMA-aware 的应用才能使用 VMA 策略。区间拆分任务可以在已mmap()的区间上再安装新的 VMA 策略此时 Linux 会把现有虚拟内存区拆成 2 或 3 个 VMA各自携带独立策略。页面迁移默认 VMA 策略只影响安装后分配的页面但自 2.6.16 起Linux 通过mbind()支持页面迁移可将已存在的页面内容搬到与新政一致的节点。共享策略Shared Policy共享策略作用于被映射到一个或多个任务各自地址空间的内存对象。应用使用mbind()指定映射该共享对象的虚拟地址范围安装共享策略——与 VMA 策略方式相同。区别在于VMA 策略是任务地址空间某段区间的属性而共享策略直接作用于共享对象本身所有附着到该对象的任务共享该策略任何任务为该对象分配的页面都遵循它。限制与历史自 2.6.22 起只有shmget()创建的共享内存段或mmap(MAP_ANONYMOUS|MAP_SHARED)创建的共享内存支持共享策略。共享策略支持加入 Linux 时数据结构被挂到 hugetlbfs shmem 段上但当时 hugetlbfs 不支持缺页时分配lazy allocation故从未真正接入共享策略尽管 hugetlbfs 现在已支持懒分配其共享策略支持仍未完成。对MAP_SHARED映射的普通文件页缓存页的分配会忽略 VMA 策略共享页缓存页包括尚未被任务写过的私有映射后备页遵循任务策略否则回退系统默认策略。共享策略基础设施支持在共享对象的不同子区间上设置不同策略但 Linux 仍会为安装策略的任务按不同策略区间拆分其 VMA。因此附着到同一共享内存段的不同任务可能有不同的 VMA 布局——可以通过查看共享区域各任务的/proc/pid/numa_maps观察到此现象。内存策略的组成一个 NUMA 内存策略由三部分组成模式mode、可选的模式标志mode flags、可选的节点集set of nodes。模式决定策略行为标志决定模式行为节点集可视为策略行为的参数。内核内部策略由带引用计数的struct mempolicy实现。行为模式NUMA 内存策略支持以下行为模式定义于 include/uapi/linux/mempolicy.h 的enum mempolicy_mode模式行为MPOL_DEFAULT仅用于 API 层内部在所有作用域都被转换为 NULL 策略。指定它即移除现有非默认策略含义是回退到下一个更具体的策略作用域。例如NULL/默认任务策略回退到系统默认策略NULL/默认 VMA 策略回退到任务策略。该模式不使用节点集指定非空节点集属于错误。MPOL_BIND内存必须来自策略指定的节点集从集合中拥有足够空闲内存且离分配发生处最近的节点分配。MPOL_PREFERRED优先从策略指定的单一节点分配若失败则按平台固件提供的距离信息从该节点由近及远搜索其他节点。内部使用struct mempolicy的preferred_node成员当内部标志MPOL_F_LOCAL置位时忽略preferred_node解释为本地分配可视为从分配所在 CPU 的节点开始的 Preferred 策略。传入空 nodemask即可指定始终优先本地分配但此时不能与MPOL_F_STATIC_NODES或MPOL_F_RELATIVE_NODES标志组合。MPOL_INTERLEAVE以页面粒度在策略指定的节点间交错分配。行为随上下文略有不同匿名页与共享内存页按缺页地址在段VMA内的页偏移对节点数取模来索引节点集然后从选中节点开始分配如同 Preferred/本地分配沿该节点的 per-node zonelist 走页缓存页则使用每任务维护的节点计数器索引节点集计数器在达到最高指定节点后回绕到最低节点——这会使页面按分配顺序、而非地址偏移摊开到各节点。系统引导期的临时交错默认策略即工作在此模式。MPOL_PREFERRED_MANY优先从策略指定的 nodemask 中分配若 mask 内所有节点都有内存压力则允许回退到所有现有 NUMA 节点。相当于允许 mask 的 MPOL_PREFERRED。MPOL_WEIGHTED_INTERLEAVE与MPOL_INTERLEAVE工作方式相同但交错行为依据 [sysfs 接口/sys/kernel/mm/mempolicy/weighted_interleave/] 中设置的权重执行。例如节点 [0,1] 权重为 [5,2]则每在 node1 分配 2 页就在 node0 分配 5 页。模式标志Mode Flags标志行为MPOL_F_STATIC_NODES指定任务或 VMA 的允许节点集在策略定义后发生变化时不重映射用户传入的 nodemask。不带该标志时策略因允许节点集变化而 rebind 时preferred nodemaskPreferred Many/preferred nodePreferred/nodemaskBind、Interleave会被重映射到新的允许节点集可能用上此前不受欢迎的节点。带该标志时用户指定节点与任务 cpuset 允许节点有交集则策略作用于交集无交集则使用默认策略。示例cpuset mems 为 1-3 的任务对同集设置 Interleave当 cpuset mems 变为 3-5 时不带标志则交错于节点 3、4、5带标志则仅交错于节点 3用户 nodemask 中唯一仍被允许的节点若用户 nodemask 中无任何节点仍被允许则退化为默认行为。不可与MPOL_F_RELATIVE_NODES组合也不能用于空 nodemask 的 Preferred本地分配。MPOL_F_RELATIVE_NODES用户传入的 nodemask 将相对于任务或 VMA 的允许节点集映射。内核保存用户 nodemask允许节点集变化后按新的允许节点集相对重映射。不带该标志也未带 STATIC_NODES时rebind 的普通重映射可能不保持相对性nodemask 1,3,5 可能被重映射到 7-9若允许节点集恢复原状又会变成 1-3。带该标志时用户 nodemask 中的节点编号是相对于允许节点集的偏移例如用户 nodemask 设置了节点 0、2、4策略将作用于允许节点集的第 1、3、5 个节点Bind/Interleave 情形若用户 nodemask 含超出新允许节点集范围的节点如允许集仅 0-3 而用户置了节点 5重映射会回绕到 nodemask 开头补齐。官方建议使用该标志的应用在准备 nodemask 时应忽略当前 cpuset 施加的内存布局仿佛自己始终位于内存节点 0 到 N-1N 为策略要管理的节点数把重映射交给内核。不可与MPOL_F_STATIC_NODES组合也不能用于空 nodemask 的 Preferred本地分配。源码层面include/uapi/linux/mempolicy.h 还定义了MPOL_F_NUMA_BALANCING允许 NUMA balancing 优化、MPOL_MODE_FLAGS上述三个标志的并集与MPOL_USER_NODEMASK_FLAGS。另外get_mempolicy()还有三个专用标志MPOL_F_NODE返回下一次交错分配的节点而非节点掩码、MPOL_F_ADDR按地址查找 VMA、MPOL_F_MEMS_ALLOWED返回允许的内存集mbind()则支持MPOL_MF_STRICT校验映射中已存在的页面、MPOL_MF_MOVE迁移本进程拥有的页面以符合策略、MPOL_MF_MOVE_ALL迁移所有页面、MPOL_MF_LAZYUNSUPPORTED懒迁移标志当前不支持。模式与标志的内核实现对应从 mm/mempolicy.c 的mpol_modes[]表可以看出每种模式对应一对create/rebind回调MPOL_BIND、MPOL_INTERLEAVE、MPOL_PREFERRED_MANY、MPOL_WEIGHTED_INTERLEAVE使用mpol_new_nodemask/mpol_rebind_nodemaskMPOL_PREFERRED使用mpol_new_preferred/mpol_rebind_preferredMPOL_LOCAL与MPOL_DEFAULT使用mpol_rebind_default。这从实现上印证了文档所述的标志决定节点重映射方式。内存策略的引用计数为化解使用/释放竞态use/free racestruct mempolicy含原子引用计数字段。内部接口mpol_get()/mpol_put()分别增减引用计数mpol_put()仅在计数归零时才把结构体归还给 mempolicy kmem cache。新策略分配时引用计数初始化为 1代表安装新策略的任务所持有的引用当策略指针存入另一结构体时再加一个引用因为安装完成时任务会释放自己的引用。运行时使用策略查询策略、为页面分配检查模式与节点集期间内核尽量减少原子操作因为原子操作会导致缓存行在 CPU 与 NUMA 节点间抖动。避免额外引用的手段系统默认策略永不 get/free系统运行后不变不释放。查询任务策略/VMA 策略时始终持有该任务 mm 的mmap_lock读锁而set_mempolicy()/mbind()安装或替换策略时必须持mmap_lock写锁因此查询期间策略不可能被并发释放。页面分配路径中的任务/VMA 策略使用发生在缺页路径持mmap_lock读锁替换策略需要写锁故分配期间策略同样不会被释放。共享策略需特殊处理一个任务可能替换共享策略而另一任务持有不同mmap_lock正在查询或据此分配页面。共享策略基础设施在查找时、于共享策略管理结构的自旋锁保护下为共享策略增加一个额外引用用完后必须条件性丢弃仅共享策略如此。代价是共享策略在页面分配路径上开销更高——尤其当共享内存区域被运行在不同 NUMA 节点的任务共享时。可避免此开销的方法共享内存区域始终回退到任务/系统默认策略或把整个共享内存区域预取并锁入内存但这未必适合所有应用。内存策略 APILinux 提供 4 个控制内存策略的系统调用它们只影响调用任务自身、调用任务自身的地址空间或映射进其地址空间的共享对象。注意定义这些 API 及用户态参数数据类型的头文件不属于内核发布包内核侧的系统调用接口带sys_前缀定义在linux/syscalls.h模式与标志定义在linux/mempolicy.h。set_mempolicy —— 设置任务内存策略long set_mempolicy(int mode, const unsigned long *nmask, unsigned long maxnode);将调用任务的任务/进程内存策略设置为mode指定的模式、nmask定义的节点集。nmask指向一个包含至少maxnode个 id 的节点位掩码。可通过将模式与标志按位或传入模式标志例如MPOL_INTERLEAVE | MPOL_F_STATIC_NODES。更详细说明参见set_mempolicy(2)手册页。内核实现上SYSCALL_DEFINE3(set_mempolicy, ...)mm/mempolicy.c依次调用sanitize_mpol_flags()校验模式/标志组合、get_nodes()从用户空间拷贝 nodemask最终进入do_set_mempolicy()安装策略。get_mempolicy —— 获取任务内存策略或相关信息long get_mempolicy(int *mode, const unsigned long *nmask, unsigned long maxnode, void *addr, int flags);根据flags参数查询调用任务的任务/进程内存策略或指定虚拟地址处的策略/位置。典型标志如MPOL_F_ADDR按地址查 VMA 策略、MPOL_F_NODE返回下一次交错分配的节点、MPOL_F_MEMS_ALLOWED返回允许的内存集。实现见 mm/mempolicy.c 的SYSCALL_DEFINE5(get_mempolicy, ...)其中do_get_mempolicy()负责具体查询copy_nodes_to_user()负责把节点掩码拷回用户空间。mbind —— 为地址空间区间安装 VMA/共享策略long mbind(void *start, unsigned long len, int mode, const unsigned long *nmask, unsigned long maxnode, unsigned flags);将(mode, nmask, maxnode)指定的策略安装为调用任务地址空间中start/len区间上的 VMA 策略flags可请求附加动作如MPOL_MF_MOVE、MPOL_MF_MOVE_ALL、MPOL_MF_STRICT。自 2.6.16 起配合MPOL_MF_MOVE等标志可实现页面迁移。SYSCALL_DEFINE6(mbind, ...)见 mm/mempolicy.c内部经kernel_mbind()进入do_mbind()strictly_unmovable()mm/mempolicy.c表明STRICT不带MOVE标志时若发现错位页面do_mbind()会立即以-EIO失败。详见mbind(2)手册页。set_mempolicy_home_node —— 为地址空间区间设置 home nodelong sys_set_mempolicy_home_node(unsigned long start, unsigned long len, unsigned long home_node, unsigned long flags);为任务地址区间中已存在的 mempolicy 设置 home node仅更新现有策略区间其他地址区间被忽略。home node 是页面分配最倾向于靠近的 NUMA 节点显式指定它可覆盖为执行 CPU 就近分配内存的默认分配策略。SYSCALL_DEFINE4(set_mempolicy_home_node, ...)mm/mempolicy.c要求start页对齐、flags必须为 0、home_node必须在线否则返回-EINVAL且区间内 VMA 策略的模式必须为MPOL_BIND或MPOL_PREFERRED_MANY否则返回-EOPNOTSUPP——这从源码印证了 home node 机制与 Bind/Preferred Many 策略的绑定关系。内存策略命令行接口numactl虽非 Linux 内存策略实现本身的一部分但存在命令行工具numactl(8)可用于通过set_mempolicy(2)、fork(2)和exec(2)为指定程序设置任务策略通过mbind(2)为共享内存段设置共享策略。numactl随包含内存策略系统调用包装器的运行时库一起打包部分发行版将头文件与编译期库放在单独的开发包中。内存策略与 cpuset 的交互内存策略在 cpuset 约束内工作需要节点或节点集的策略其节点被限制在 cpuset 允许的内存节点集内。若策略 nodemask 含 cpuset 不允许的节点且未使用MPOL_F_RELATIVE_NODES则取策略节点集与内存节点集的交集交集为空则策略视为无效、无法安装。若使用MPOL_F_RELATIVE_NODES策略节点按前述方式映射并折叠进任务的允许节点集。当两个 cpuset 中的任务共享某内存区域如shmget()或mmap(MAP_ANONYMOUS|MAP_SHARED)创建的共享内存段且任一任务对该区域安装共享策略时只有两个 cpuset 都允许内存的节点可用于该策略。获取此信息需要跳出内存策略 API、借助 cpuset 信息并需知道其他任务可能附着到共享区域所在哪些 cpuset。若 cpuset 允许内存集不相交则本地分配是唯一有效策略。深入源码Weighted Interleave 的 sysfs 实现MPOL_WEIGHTED_INTERLEAVE是相对较新的模式其权重配置暴露在/sys/kernel/mm/mempolicy/weighted_interleave/内核实现细节位于 mm/mempolicy.c权重状态由 RCU 保护的struct weighted_interleave_state含mode_auto布尔与iw_table[]权重表承载NULL 状态被解释为modeauto且权重全 1。每个节点对应一个 sysfs 属性node_show/node_storemm/mempolicy.c写入会先校验权重非 0kstrtou8解析失败或写 0 均返回-EINVAL随后基于旧表复制并更新该节点权重、将mode_auto置为 false再以rcu_assign_pointer()原子发布旧状态经synchronize_rcu()后释放。weighted_interleave_auto_show/_storemm/mempolicy.c控制自动模式自动模式下内核根据内存带宽信息自动生成权重reduce_interleave_weights()mm/mempolicy.c以weightiness 32为权衡基准将各节点带宽换算为 [1, 32] 范围内的整数权重并按其最大公约数约简1:2 严格优于 16:32。分配路径上weighted_interleave_nodes()mm/mempolicy.c借助current-il_prev与current-il_weight记录当前节点及其剩余配额配额耗尽即推进到下一节点并刷新权重——这就是文档所述[0,1] 权重 [5,2] 时 node0 每分 5 页、node1 分 2 页的逐页配额实现。小结NUMA 内存策略是 Linux 为 NUMA/模拟 NUMA 平台提供的一套从哪个节点取内存的编程接口由作用域系统默认 → 任务 → VMA → 共享对象、模式Default/Bind/Preferred/Interleave/Preferred-Many/Weighted-Interleave、模式标志STATIC_NODES/RELATIVE_NODES与节点集共同描述并通过set_mempolicy、get_mempolicy、mbind、set_mempolicy_home_node四个系统调用操作。理解其作用域回退规则、标志重映射语义与 cpuset 交集限制是编写高性能、可移植 NUMA-aware 应用的前提配合numactl命令行工具与/proc/pid/numa_maps接口可以方便地在实际系统中观察和验证策略效果。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价