上一篇《cfs调度类深入解刨------最新内核细节分析4》中讲述了kernel 7.1版本中的下一个任务选取与核心调度的作用。
本篇文章讲述numa的相关细节知识,包括numa节点性能,故障扫描等。
numa
NUMA(Non-Uniform Memory Access,非一致性内存访问) 是一种专为多处理器(SMP)系统设计的内存架构。在 NUMA 架构下,物理内存被划分为多个本地节点(Nodes),每个 CPU 访问与其直接相连的本地节点内存速度极快,而访问通过总线相连的远端节点内存速度较慢(较本地节点内存的访问时间1.2倍以上,cpu内存带宽较高时甚至达到2倍以上)。
举个例子,一款384核心(逻辑cpu)的intel cpu,按照6个numa区域划分(node0---node5),一个区域包含(不连续,如分为两段32个连续逻辑cpu,为了优化缓存局部性,通常不会把超线程(smt)的两个线程(逻辑cpu)连续编号,而是交错排列)64个逻辑cpu。
在内核中,评估将一个任务(进程/线程)迁移到另一个cpu是否会因为缓存局部性的损失而降低性能,需要计算源cpu的numa节点与目标cpu的numa节点的距离,两个numa节点相等返回本地节点到自身的距离(LOCAL_DISTANCE),否则返回远程节点间的典型距离(REMOTE_DISTANCE)。并且将共享内存地址空间(mm)或紧密关联的进程 / 线程归为一个 "NUMA 组",统一管理它们的内存访问模式和 NUMA 调度策略,避免分散调度导致的跨节点内存访问性能损耗。
cpu在numa节点上有着故障次数占总故障次数的比例这么一说,任务所在的numa组存在并且组的总故障数大于0,遍历在线的numa节点,根据numa拓扑类型等方式统计故障总值,将故障总值累加到numa组的(numa节点)故障值中,将统计出的故障值扩大1000倍,除numa组的总故障,用于计算某个NUMA节点上的故障次数占总故障次数的比例,并以整数形式返回一个"千分比"值。
numa拓扑结构分为 :
NUMA_DIRECT 所有节点直接相连,没有中间层次的互连。通常每个节点到其他节点的距离可能相同或呈简单的矩阵,但互连是点对点的,没有层次化
NUMA_GLUELESS_MESH 节点以网状(mesh)方式连接,但"glueless"可能指没有全局的背板(backplane),而是通过某种互连(如 HyperTransport 或 QPI)构成网格。这种拓扑中,节点间的距离可能随跳数增加而增加,但并非所有节点都直接相连,需要经过中间节点转发
NUMA_BACKPLANE 存在一个背板(backplane)将所有节点连接在一起
NUMA调度均衡(sched_numa_balancing)启用的情况下支持:
- numa_next_scan(下次扫描PTE以触发NUMA提示故障的时间(jiffies)),PTE(页表项)将被重新映射为"PROT_NONE"(表示无任何读、写、执行权限)的下一次时间点,以此来触发 NUMA(非对称多处理)提示性故障;此类故障会收集相关数据,并在必要时将页面迁移到新的节点。
- numa_scan_offset,当前扫描的偏移量,用于循环扫描整个地址空间。
- numa_scan_seq,序列号,防止多个线程同时修改PTE。
NUMA调度均衡扫描属于任务自主驱动形式的扫描,这里有一个关键的内核细节:多线程共享同一个内存空间,但每个线程拥有独立的扫描周期。在 Linux 中,一个多线程程序(如 MySQL、Java 进程)拥有多个线程(多个 task_struct)。它们都指向同一个 p->mm(内存描述符)。当线程 A 的 numa_scan_period 到期时,它会查看整个进程共享的虚拟地址空间,并顺着上一次扫描留下的游标(p->mm->numa_scan_offset) 往后扫描一段物理页(比如 256MB),将其刷成 PROT_NONE。当线程 B 的周期到期时,它会接着线程 A 留下的游标继续往下扫描等,多个单个任务(线程)像接力赛一样,共同轮流扫描它们所属的那个完整的内存空间(基于RSS(资源共享区)的计算(即那些不存在且为空的页面)将被PTE扫描器跳过,而NUMA提示故障则应根据驻留页面进行捕获)。
任务最小扫描周期间隔1000毫秒(1秒),最大扫描周期间隔60000毫秒(1分钟),每次扫描的内存区域最大256MB。虽然扫描是在任务内部发生的,但它最终的反馈结果(计算出的下一次周期值)是高度依赖硬件 NUMA 层次的(如果一个任务长期处于休眠状态(比如挂起的后台进程),它就不会触发时钟中断,也不会推进自己的 p->numa_scan_period。只有活跃的的任务才会密集触发扫描)。在每轮扫描结束、计算新周期的阶段,内核会去读取一个矩阵:p->numa_faultstask_idxnode_idx(NR_NUMA_HINT_FAULT_TYPES*(s*nr_node_ids+nid)+priv,任务在各个 NUMA 节点上的陷阱命中次数,被划分为四个区域:依次为"NUMA_MEM"、"NUMA_CPU"、"NUMA_MEMBUF"和"NUMA_CPUBUF")。通常本地NUMA fault数量极少,正常场景下几乎为0,远程NUMA fault数量越大说明远程访问越多,跨节点迁移中触发的 fault 数量属于临时状态,用于监控迁移开销。因此扫描周期可以看作两部分:1. 任务被局限在单个 NUMA 节点内所有线程和它们的内存都完美留在线路图中的 Node 0(本地)。此时每个线程的 p->numa_scan_period 都会各自开始独立变长(拉大到60秒),整个进程的扫描动作全面熄火。2. 任务跨越了 NUMA 节点,内核通过任务的 Fault 发现数据散落在 Node 0 和 Node 1。此时,所有受影响线程的 p->numa_scan_period 会同步剧烈缩短(如跌至1秒)。在宏观上看,就像是针对某一个特定 NUMA 区域的内存发起了一场"暴风雨般的密集扫描和迁移"。