Linux PowerPC pseries 共享处理器 LPAR 的 VCPU 调度统计:vcpudispatch_stats 深度指南
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
导读
在 POWER 虚拟化环境中,共享处理器 LPAR(Shared Processor Logical Partition)由 Power Hypervisor(PHYP)将分区内的虚拟处理器(vCPU)动态调度到宿主机的物理处理器核心上执行。Hypervisor 会尽量把 vCPU 调度到其"归属(home)"物理芯片上,但在负载压力、分区迁移等场景下,vCPU 可能被调度到远离归属节点的芯片甚至跨 socket/drawer 执行,导致 NUMA 访存延迟上升。本文基于 Linux 内核 pseries 平台实现,完整讲解/proc/powerpc/vcpudispatch_stats与/proc/powerpc/vcpudispatch_stats_freq两个 procfs 接口的启用方法、8 个统计字段的精确含义、底层 DTL(Dispatch Trace Log)处理机制与 NUMA 关联性(associativity)计算原理,帮助你量化分区内 vCPU 调度的局部性,为性能调优提供数据依据。
一、背景:为什么需要跟踪 vCPU 调度分布
对于共享处理器 LPAR,Power Hypervisor 维护着一张 vCPU 到物理处理器芯片的相对静态映射关系。每个 vCPU 都有一个"home node"(归属物理芯片),Hypervisor 会优先将 vCPU 调度到其归属芯片上,以充分利用芯片级缓存与本地内存访问路径,获得良好的 NUMA 局部性。
然而在以下场景中,vCPU 可能会被调度到远离 home node 的物理芯片上:
- 归属芯片上的物理处理器全部繁忙,Hypervisor 需要将 vCPU 临时"溢出"调度到其他芯片;
- 分区迁移、资源收缩或处理器热插拔导致映射关系变化;
- 多分区竞争导致全局调度压力增大。
当 vCPU 频繁被调度到远端芯片时,其访存路径跨越芯片互连甚至 socket/drawer 边界,延迟显著增加。因此,量化"vCPU 实际被调度在哪里"对于诊断共享处理器分区的性能抖动、验证 Hypervisor 调度局部性至关重要。这正是 vcpudispatch_stats.rst 文档所描述功能的价值所在。
二、procfs 接口总览:启用、关闭与采样频率
该功能由 pseries 平台代码在启动时通过machine_device_initcall(pseries, vcpudispatch_stats_procfs_init)注册(见 arch/powerpc/platforms/pseries/lpar.c),并在共享处理器环境下创建两个 procfs 文件:
| procfs 文件 | 权限 | 作用 |
|---|---|---|
/proc/powerpc/vcpudispatch_stats | 0600(仅 root) | 写入1启用统计采集,写入0关闭统计;读取时输出每个 vCPU 的调度统计 |
/proc/powerpc/vcpudispatch_stats_freq | 0600(仅 root) | 读取/设置每个 vCPU 的 DTL 日志处理频率(次/秒),默认 50 |
从源码看,这两个文件都通过proc_create("powerpc/vcpudispatch_stats", 0600, NULL, ...)创建在 procfs 根命名空间下,0600权限意味着只有 root 用户可以读写,这符合统计信息涉及系统调度细节的特性。
2.1 启用统计
$ sudo sh -c 'echo 1 > /proc/powerpc/vcpudispatch_stats'写入1后内核会依次完成四件事(对应 vcpudispatch_stats_write 的cmd分支):
- 调用
init_cpu_associativity()为每个可能的 vCPU 与物理 CPU 分配 associativity 缓冲(lpar.c); - 清空所有 CPU 的统计结构
vcpu_disp_data,并将last_disp_cpu初始化为-1(表示尚无上一次调度记录); - 通过
set_global_dtl_mask(DTL_LOG_ALL)将每个 CPU 的dtl_enable_mask置为全部事件类型(CEDE 等待、时间片抢占、缺页),见 lpar.c; - 注册 DTL worker(
dtl_worker_enable),启动逐 CPU 的延迟工作队列开始消费 DTL 日志。
写入时输入必须是 0 或 1,其他值会被拒绝并输出提示please use 0 to disable or 1 to enable dispatch statistics。重复写入相同值会被幂等地忽略。
2.2 关闭统计
$ sudo sh -c 'echo 0 > /proc/powerpc/vcpudispatch_stats'关闭流程执行dtl_worker_disable():撤销 cpuhp 状态、释放 DTL 缓冲区、将全局dtl_mask复位(非原生虚拟 CPU 计账时复位为 0)、释放 associativity 缓冲,并释放写锁。
2.3 调整采样频率
默认情况下,每个 vCPU 的 DTL 日志每秒被处理 50 次,以避免因处理不及时而丢失日志条目。该频率可通过第二个 procfs 文件调整:
# 查看当前频率 $ cat /proc/powerpc/vcpudispatch_stats_freq 50 # 调整为每秒 100 次 $ sudo sh -c 'echo 100 > /proc/powerpc/vcpudispatch_stats_freq'频率的合法取值范围为1到HZ(内核时钟节拍数,通常为 100 或 250),超出范围的写入会被拒绝(vcpudispatch_stats_freq_write)。采样频率越高,DTL 日志被消费得越及时、越不容易丢失条目,但对应的每 CPU 延迟工作队列唤醒开销也越大,需要根据分区繁忙程度权衡。
三、统计输出格式:8 个数字的精确语义
统计启用后,读取 procfs 文件即可得到结果:
$ sudo cat /proc/powerpc/vcpudispatch_stats输出中每一行对应一个 vCPU:第一个字段是 vCPU 名称(cpuN),其后跟随 8 个数字。下面是文档中的完整示例输出:
cpu0 6839 4126 2683 30 0 6821 18 0 cpu1 2515 1274 1229 12 0 2509 6 0 cpu2 2317 1198 1109 10 0 2312 5 0 cpu3 2259 1165 1088 6 0 2256 3 0 cpu4 2205 1143 1056 6 0 2202 3 0 cpu5 2165 1121 1038 6 0 2162 3 0 cpu6 2183 1127 1050 6 0 2180 3 0 cpu7 2193 1133 1052 8 0 2187 6 0 cpu8 2165 1115 1032 18 0 2156 9 0 cpu9 2301 1252 1033 16 0 2293 8 0 cpu10 2197 1138 1041 18 0 2187 10 0 cpu11 2273 1185 1062 26 0 2260 13 0 cpu12 2186 1125 1043 18 0 2177 9 0 cpu13 2161 1115 1030 16 0 2153 8 0 cpu14 2206 1153 1033 20 0 2196 10 0 cpu15 2163 1115 1032 16 0 2155 8 03.1 第 1 个数字:累计调度次数
第 1 个数字(如 cpu0 的6839)是自统计启用以来该 vCPU 被调度的总次数(total_disp)。注意它不含第一次调度——内核在收到第一条 DTL 记录时只记录last_disp_cpu而不递增计数(见 update_vcpu_disp_stat 中last_disp_cpu == -1的 early return 分支)。
3.2 中间 4 个数字:相对上次调度的离散程度
接下来的 4 个数字描述该 vCPU 每次调度相对于"上一次调度位置"的离散情况:
| 序号 | 字段(源码字段名) | 含义 |
|---|---|---|
| 2 | same_cpu_disp | 被调度到与上次相同的物理 CPU(或同核 sibling 线程)的次数 |
| 3 | same_chip_disp | 被调度到与上次不同的物理核心、但在同一物理芯片(chip)内的次数 |
| 4 | diff_chip_disp | 被调度到与上次不同的物理芯片的次数 |
| 5 | far_chip_disp | 被调度到与上次不同的 socket/drawer(下一个 NUMA 边界)的次数 |
以内核源码为证,update_vcpu_disp_stat()首先判断两次调度的物理 CPU 是否相同或互为同一核心的兄弟线程(通过cpu_first_thread_sibling()比较);否则调用cpu_relative_dispatch_distance()计算两个物理 CPU 的 associativity 相对距离,距离 0/1/2 分别落入same_chip_disp/diff_chip_disp/far_chip_disp。
3.3 最后 3 个数字:相对 home node 的分布
最后 3 个数字描述该 vCPU 的调度位置相对于其归属节点(home node)的关系:
| 序号 | 字段(源码字段名) | 含义 |
|---|---|---|
| 6 | numa_home_disp | 被调度在 home node(归属芯片)内的次数 |
| 7 | numa_remote_disp | 被调度在归属节点之外、相邻节点(neighbouring chip)的次数 |
| 8 | numa_far_disp | 被调度在更远节点(NUMA distance 更大)的次数 |
这一组数据由cpu_home_node_dispatch_distance()计算:取当前 vCPU 的 associativity(通过hcall_vphn(cpu, VPHN_FLAG_VCPU, ...)获取)与本次被调度物理 CPU 的 associativity(VPHN_FLAG_PCPU),调用cpu_relative_distance()得到距离值后分类计数。
3.4 示例逐行解读
以文档中的 cpu0 为例:
- 自启用统计以来共发生
6839次调度; - 其中
4126次与上次调度在同一物理 CPU; 2683次换到了同芯片内的不同核心;30次跨芯片调度(相对上次);0次跨 socket/drawer 调度(相对上次);- 就 home node 而言,
6821次调度发生在归属芯片内,18次发生在归属节点之外(相邻芯片),0次落在更远的节点。
可以看出,该 vCPU 的调度局部性相当好:99.7%(6821/6839)的调度都留在归属芯片内。而 cpu11 的numa_remote_disp = 13、cpu10 的= 10等则提示这些 vCPU 存在更多的跨节点溢出调度,值得结合工作负载进一步分析。
四、底层实现:DTL 日志的采集流水线
vCPU 调度统计并非内核主动轮询 Hypervisor,而是基于 PowerPC 的Dispatch Trace Log(DTL)机制——Hypervisor 将每个物理处理器上的 vCPU 调度/抢占事件写入一段由内核注册的共享内存日志,内核侧再周期性地消费这段日志。完整流水线如下:
Hypervisor 写入 DTL 缓冲区 │ ▼ 每个 CPU 的 delayed_work(process_dtl_buffer) 每秒触发 vcpudispatch_stats_freq 次 │ ▼ 逐条解析 dtl_entry,取 processor_id │ ▼ update_vcpu_disp_stat():与上次调度位置、home node 比较 │ ▼ 累加到 per-CPU 的 struct vcpu_dispatch_data │ ▼ 读取 /proc/powerpc/vcpudispatch_stats 时经 seq_file 输出4.1 DTL 缓冲区与事件掩码
每个 CPU 的 DTL 缓冲区为 4096 字节(DISPATCH_LOG_BYTES,见 arch/powerpc/include/asm/dtl.h),由struct dtl_entry组成,其中processor_id(__be16,大端)字段即 Hypervisor 记录的实际调度物理 CPU 编号,正是统计的核心输入。缓冲区在启用统计时通过alloc_dtl_buffers()从dtl_cache分配,并调用register_dtl_buffer()通过register_dtl(hwcpu, __pa(dtl))超调用注册给 Hypervisor。
事件掩码定义在 dtl.h:
#define DTL_LOG_CEDE 0x1 /* 自愿的虚拟处理器等待 */ #define DTL_LOG_PREEMPT 0x2 /* 时间片抢占 */ #define DTL_LOG_FAULT 0x4 /* 虚拟分区内存缺页 */ #define DTL_LOG_ALL (DTL_LOG_CEDE | DTL_LOG_PREEMPT | DTL_LOG_FAULT)启用统计时内核将全局掩码置为DTL_LOG_ALL,即记录全部三类事件,确保调度(重新调度)事件不因掩码而被 Hypervisor 过滤。
4.2 每 CPU 的延迟工作队列
统计的消费端是每个 CPU 上的一个struct dtl_worker(内含delayed_work),通过 cpuhp 回调dtl_worker_online()在 CPU 上线时注册并调度首次运行,间隔为HZ / vcpudispatch_stats_freq(lpar.c)。process_dtl_buffer()每次执行时:
- 读取 VPA(Virtual Processor Area)中的
dtl_idx,与本地已消费索引dtl_entry_ridx比较; - 若
dtl_idx领先本地索引超过整个缓冲区长度(N_DISPATCH_LOG),说明缓冲已溢出,内核记录丢失样本数并跳到最新位置(lpar.c); - 逐条取出
dtl_entry,用be16_to_cpu(dtle.processor_id)取出实际调度 CPU,交给update_vcpu_disp_stat()累加统计; - 处理完毕后在同一个 CPU 上重新调度下一次执行。
worker 还具备自我保护:如果它被迁移到了其他 CPU(d->cpu != smp_processor_id()),会直接取消自己,避免在错误的 CPU 上消费日志。
4.3 per-CPU 统计数据结构
统计的载体是 per-CPU 变量vcpu_disp_data(DEFINE_PER_CPU(struct vcpu_dispatch_data, vcpu_disp_data)),其结构与输出字段一一对应(lpar.c):
struct vcpu_dispatch_data { int last_disp_cpu; /* 上次调度的物理 CPU,-1 表示无记录 */ int total_disp; /* 累计调度次数 */ int same_cpu_disp; /* 与上次同一物理 CPU */ int same_chip_disp; /* 同芯片不同核心 */ int diff_chip_disp; /* 不同芯片 */ int far_chip_disp; /* 不同 socket/drawer */ int numa_home_disp; /* 在 home node 内 */ int numa_remote_disp; /* 在相邻节点 */ int numa_far_disp; /* 在更远节点 */ };输出侧vcpudispatch_stats_display()遍历for_each_online_cpu,用seq_put_decimal_ull依次打印 8 个计数字段(lpar.c)。注意:如果统计尚未启用,读取文件只会输出一行off。
五、NUMA 距离判定:associativity 与 VPHN 超调用
"同一芯片""不同芯片""不同节点"这些分类,最终都由物理 CPU 与 vCPU 的associativity(关联性)数组决定。POWER 的 associativity 是一组从系统顶层到底层逐级缩放的域标识,NUMA 距离通过比较两个数组的前缀得出。
5.1 获取 associativity
- vCPU 侧:
hcall_vphn(cpu, VPHN_FLAG_VCPU, &assoc[0]),即虚拟处理器归属节点号码(Virtual Processor Home Node)超调用,返回 vCPU 的关联性数组(vphn.h); - 物理 CPU 侧:
hcall_vphn(cpu, VPHN_FLAG_PCPU, &assoc[0])。
这两类信息在启用统计时分别缓存于vcpu_associativity与pcpu_associativity两个全局数组(lpar.c),按cpu / threads_per_core索引,避免每条日志都重复发起超调用。
5.2 相对距离计算
cpu_relative_distance()(arch/powerpc/mm/numa.c)根据系统当前的 associativity 形式分发:
- FORM1 关联性:逐级比较两个数组的
distance_ref_points指定索引处的域 ID,第一个不相等的层级即距离(__cpu_form1_relative_distance)——距离 0 表示同级(同芯片)、1 表示跨一级(跨芯片/节点)、2 表示跨两级(跨 socket/drawer); - FORM2 关联性:先各自转换为 node ID,再查
numa_distance_table距离矩阵,<= LOCAL_DISTANCE返回 0、<= REMOTE_DISTANCE返回 1、否则返回 2(__cpu_form2_relative_distance)。
这解释了为什么"第 5 个数"与"第 8 个数"的语义存在细微差别:前者衡量两次调度位置之间的相对距离(与上次比),后者衡量调度位置与 vCPU 归属节点之间的距离(与 home 比),但两者都复用同一套 0/1/2 三级距离模型。
六、适用前提与使用限制
- 仅限共享处理器 LPAR:procfs 文件的创建受
lppaca_shared_proc()保护(lpar.c),即只有分区运行在共享处理器模式下(CONFIG_PPC_SPLPAR启用、VPA 声明 shared processor)时接口才存在;专用(dedicated)处理器分区无法使用。 - 需要 root 权限:两个文件权限均为 0600,普通用户只能看到"无权限"。
- 统计从启用时刻开始累计:所有计数都是启用后累计的单调计数,不是速率或百分比;需要观察"速率"时,应记录两个时间点的差值自行计算。
- DTL 缓冲溢出会丢样本:若 Hypervisor 写入过快而 worker 消费不及,缓冲溢出时内核会打印
lost N DTL samples并跳过丢失部分(lpar.c)。遇到丢失时应调高vcpudispatch_stats_freq。 - 首次调度不计数:每个 vCPU 的第一条 DTL 记录仅用于建立
last_disp_cpu基线,不进入任何计数器,因此total_disp会略小于真实的调度次数。 - 距离计算失败会跳过:若 associativity 获取失败(如超调用返回错误),对应样本不会计入任何分类字段,仅打印限速的 debug 日志。
七、典型使用场景
7.1 验证 Hypervisor 调度局部性
启用统计并让分区运行一段时间后读取输出,计算 home node 命中率:
home_node_hit_ratio = numa_home_disp / total_disp若命中率长期偏低、numa_remote_disp持续增长,说明 vCPU 经常被溢出调度到相邻芯片,可能与相邻分区争抢资源或归属芯片容量不足有关,可作为向虚拟化平台团队反馈调度问题的量化依据。
7.2 诊断性能抖动
当共享处理器分区出现间歇性性能抖动且怀疑与调度迁移有关时,对比抖动发生前后两个时间点导出的统计差值,观察diff_chip_disp/far_chip_disp/numa_remote_disp增量是否显著上升,可将"NUMA 局部性劣化"从"CPU 争抢"等候选原因中区分出来。
7.3 评估采样开销
vcpudispatch_stats_freq默认 50 次/秒。若在大型分区(数百 vCPU)上启用统计后发现额外的唤醒开销,可适当调低频率(下限 1);若观察到 DTL 溢出丢样本,则应调高频率。频率参数在运行时即可调整,无需重启分区。
结语
/proc/powerpc/vcpudispatch_stats是 pseries 共享处理器分区上唯一的内核级 vCPU 调度位置统计接口。通过本文梳理的 8 字段语义、DTL 采集流水线与 associativity 距离模型,你可以在真实环境中准确解读每一行输出,将"vCPU 被调度到了哪里"这一虚拟化黑盒行为转化为可量化、可对比的性能数据。相关文档原文位于 Documentation/arch/powerpc/vcpudispatch_stats.rst,核心实现位于 arch/powerpc/platforms/pseries/lpar.c,NUMA 距离判定见 arch/powerpc/mm/numa.c,DTL 数据结构定义见 arch/powerpc/include/asm/dtl.h,感兴趣的读者可沿此路径深入阅读源码。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考