Linux 内核 Intel Uncore 频率缩放指南:sysfs 接口、TPMI Fabric Cluster 与 ELC 延迟效率控制
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
本文围绕 Linux 内核文档 Documentation/admin-guide/pm/intel_uncore_frequency_scaling.rst 展开,系统讲解 Intel Xeon 服务器平台上 uncore(非核心域,含 RING/LLC 等)频率的调优方法:如何通过/sys/devices/system/cpu/intel_uncore_frequency/下的 sysfs 接口设置 uncore 最大/最小频率、如何利用 TPMI 接口在 fabric cluster 粒度上做精细控制,以及如何通过 Efficiency Latency Control(ELC)在功耗与延迟之间取得平衡。读完本文,你将掌握 uncore 频率控制属性表、实际配置命令、ELC 参数调优示例,并理解其背后的 MSR/MMIO 实现原理。
为什么需要手动控制 Uncore 频率
在基于 Intel Xeon 的服务器上,uncore(非核心硬件域,包括最后一级缓存 LLC、RING 总线、内存控制器等)的功耗占比取决于工作负载特性,可能相当可观。为了优化整机功耗并提升整体性能,SoC 内部内置了 uncore 频率缩放算法:这些算法持续监测工作负载对 uncore 的使用情况,并据此设定一个"期望频率"。
然而,不同用户对 uncore 性能的期望各不相同,有时需要直接介入控制:
- 延迟敏感型负载:某些应用不希望 uncore 频率发生任何变动,以免出现频率爬升延迟影响响应;
- 分阶段性能需求:某些负载在不同阶段对 core 与 uncore 性能的要求不同,用户希望同时使用 cpufreq 与 uncore 缩放接口来分配功耗、提升整体性能。
其目标与通过 cpufreq sysfs 设置 CPU scaling min/max 频率以提高 CPU 性能的思路一致——只不过这次控制的对象是 uncore。
从源码看,该功能由平台驱动 uncore-frequency.c 与 uncore-frequency-tpmi.c 提供,可在 Kconfig 中通过INTEL_UNCORE_FREQ_CONTROL配置项启用(依赖X86_64,编译为模块时名为intel-uncore-frequency)。
Sysfs 接口总览
控制 uncore 频率的 sysfs 接口位于:
/sys/devices/system/cpu/intel_uncore_frequency/该根目录下,每个 "package + die" 组合对应一个子目录。之所以如此划分,是因为 uncore 缩放控制的作用域在"多 die/包" SoC 上是per die,而在"单 die/包" SoC 上是per package。目录名直接体现控制作用域,例如:
package_00_die_00:package id 为 0、die 为 0 的作用域。
目录的创建逻辑可在 uncore-frequency-common.c 中看到:非 TPMI 模式下目录名按package_%02d_die_%02d生成。
package_*_die_*目录属性详解
每个package_*_die_*目录包含以下属性:
| 属性 | 读写 | 说明 |
|---|---|---|
initial_max_freq_khz | 只读 | 复位(out of reset)后的最大可能频率。若用户调整过max_freq_khz,可随时用此值恢复最大值 |
initial_min_freq_khz | 只读 | 复位后的最小可能频率。若用户调整过min_freq_khz,可随时用此值恢复最小值 |
max_freq_khz | 读写 | 设置 uncore 最大频率(单位 kHz) |
min_freq_khz | 读写 | 设置 uncore 最小频率(单位 kHz) |
current_freq_khz | 只读 | 读取 uncore 当前实际频率(单位 kHz) |
属性权限的源码实现
在 uncore-frequency-common.c 的create_attr_group()中可以看到属性权限的设定方式:
max_freq_khz、min_freq_khz使用init_attribute_rw,权限为0644(可读写);initial_min_freq_khz、initial_max_freq_khz使用init_attribute_ro,权限为0444(只读);current_freq_khz使用init_attribute_root_ro,权限为0400(仅 root 可读,保护频率状态信息)。
initial_min_freq_khz与initial_max_freq_khz的值在uncore_freq_add_entry()中通过uncore_read()采样得到(见 uncore-frequency-common.c),作为驱动的初始快照。
频率值的换算关系
所有频率单位均为 kHz,且换算因子固定为 100000(100 MHz)。在 uncore-frequency.c 中:
#define MSR_UNCORE_RATIO_LIMIT 0x620 #define MSR_UNCORE_PERF_STATUS 0x621 #define UNCORE_FREQ_KHZ_MULTIPLIER 100000写入时,用户提供的 kHz 值会被除以 100000 折算成 ratio 后写入寄存器位域:
UNCORE_MAX_RATIO_MASK为GENMASK_ULL(6, 0)(bit 0-6);UNCORE_MIN_RATIO_MASK为GENMASK_ULL(14, 8)(bit 8-14);UNCORE_CURRENT_RATIO_MASK为GENMASK_ULL(6, 0),用于从MSR_UNCORE_PERF_STATUS(0x621)读取当前频率 ratio。
非 TPMI 模式下,读写通过指定的control CPU执行 MSR 操作(rdmsrq_on_cpu/wrmsrq_on_cpu)。每个 die 在 CPU online 时指定一个 control CPU,若该 CPU 下线则切换给同 die 的其他在线 CPU;当 die 中最后一个 CPU 下线时,对应 die 的 sysfs 对象会被移除。同时,驱动注册了电源管理 notifier:在系统恢复(PM_POST_SUSPEND/PM_POST_HIBERNATION/PM_POST_RESTORE)时,会将用户最后写入的 MSR 0x620 值重新写回,保证休眠唤醒后设置不丢失(见 uncore-frequency.c)。
TPMI SoC 与 Fabric Cluster 粒度控制
什么是 Fabric Cluster
支持 TPMI(Topology Aware Register and PM Capsule Interface)的 SoC 可能包含多个功耗域(power domain),每个功耗域由一个或多个 mesh 分区构成,这种分区被称为fabric cluster。同一类 mesh 若需运行在同一频率上,会被放置到同一个 fabric cluster 中。fabric cluster 的收益在于:它提供了一种可扩展的机制来管理 SoC 中的分区 fabric。
传统package_*_die_*接口只能做到 package/die 粒度,无法满足 fabric cluster 级别的更细粒度控制,因此 sysfs 在此基础上做了增强:新增以uncore前缀命名的目录(如uncore00、uncore01等),每个目录对应一个 fabric cluster 级别的控制作用域。目录名中的序号来自ida_alloc()分配的递增编号(见 uncore-frequency-common.c)。
uncore*目录中的作用域标识属性
每个uncore*目录通过以下属性声明其控制作用域:
| 属性 | 说明 |
|---|---|
instance_id | 用户在用户态映射 MMIO 块时所需的 die 索引,索引是单个 TPMI 分区内的局部值,用于直接 TPMI 寄存器访问 |
domain_id | 该实例的功耗域 id,在给定 CPU package 的所有 TPMI 分区中唯一,可用于将计算 die 映射到对应 CPU |
die_id | 该实例的 Linux die id。仅当域中存在 core agent 且 CPUID leaf 0x1f 提供了 die ID 时存在 |
fabric_cluster_id | 该实例的 fabric cluster id |
package_id | 该实例的 package id |
agent_types | 该域内存在的全部硬件 agent。每个 agent 有能力控制一个或多个硬件子系统:core、cache、memory、I/O |
其中agent_types的实现位于 uncore-frequency-common.c:agent_name[]按序定义为{"core", "cache", "memory", "io"},输出时按agent_type_mask的置位顺序以空格分隔打印。在 TPMI 驱动中,agent 类型位来自 UFS_STATUS 寄存器的 bit 23-26(见 uncore-frequency-tpmi.c),与头文件 uncore-frequency-common.h 中的AGENT_TYPE_CORE/CACHE/MEMORY/IO位掩码对应。
除上述标识属性外,uncore*目录还包含与package_*_die_*级别相同的max_freq_khz、min_freq_khz、initial_*_freq_khz等属性。
两级控制模型的兼容性
在多数现有使用场景中,用户习惯在package_*_die_*级别更新max_freq_khz与min_freq_khz。这一模型在 TPMI 平台上依然被支持,其行为约定如下:
- 当用户在
package_*_die_*级别修改max_freq_khz时,同一 package id 下所有uncore*目录的max_freq_khz都会被同步更新; - 用户仍可单独在某个
uncore*级别更新max_freq_khz,这种设置更具限制性(更细粒度、作用范围更小); min_freq_khz同理,可在package_*_die_*级别统一应用,也可在uncore*级别单独调整;current_freq_khz仅在 fabric cluster 级别(即uncore*目录)提供,package_*_die_*目录不提供。
从 TPMI 源码可印证这一聚合语义:root_domain对应的uncore_data在读取时遍历所有功耗域、所有 cluster,取最低的 min与最高的 max作为聚合值;写入时则遍历所有 cluster 逐一更新(见 uncore-frequency-tpmi.c 与 uncore-frequency-tpmi.c)。
TPMI 模式下的额外校验
TPMI 驱动还引入了若干安全与一致性约束:
- 写入封锁(write_blocked):若固件通过 TPMI 特性状态标记禁止写入,
uncore_write_control_freq()与write_eff_lat_ctrl()会返回-EPERM;若固件禁止读取,驱动直接返回-ENODEV不注册(见 uncore-frequency-tpmi.c); - 越界保护:在 fabric cluster 级别单独写入时,若写入的
max_freq_khz高于 package 级聚合 max,或min_freq_khz低于 package 级聚合 min,返回-EINVAL,防止出现互相矛盾的配置(见 uncore-frequency-tpmi.c); - 版本检查:驱动校验 TPMI UFS 头版本,仅接受 major version 0,且 ELC 功能要求 minor version >= 2 且自主 UFS(autonomous UFS)未被禁用(见 uncore-frequency-tpmi.c 与 uncore-frequency-tpmi.c)。
Efficiency vs. Latency Tradeoff(ELC 延迟效率控制)
背景与动机
Efficiency Latency Control(ELC)特性用于提升"每瓦性能"(performance per watt)。启用后,硬件电源管理算法会在延迟与功耗之间自动优化取舍。
硬件会以固定间隔监测某个功耗域内所有 core 的平均 CPU 利用率,并据此决定 uncore 频率。这在多数场景下能带来最佳性能功耗比,但某些延迟敏感负载可能希望"以功耗换性能"。典型场景:一个应用间歇性唤醒并执行内存读操作,而系统其余时间处于空闲。此时若硬件降低了 uncore 频率,当应用真正需要访问内存时,频率爬升到目标性能点会出现延迟。
ELC 通过一组可由软件调整的参数解决该问题:
- 当平均 CPU 利用率低于用户定义的低阈值(
elc_low_threshold_percent)时,硬件使用用户定义的 uncore 地板频率(elc_floor_freq_khz)替代硬件计算的频率下限,避免频率降得过低; - 当 CPU 利用率高于高阈值(
elc_high_threshold_percent)时,频率不再直接跳升到最大值,而是按 100 MHz 步进逐步提升,避免因 CPU 利用率瞬时尖峰而立即消耗过高的功耗。
ELC 属性说明
| 属性 | 读写 | 说明 |
|---|---|---|
elc_floor_freq_khz | 读写 | 效率延迟地板频率。若该值低于min_freq_khz,固件会忽略它 |
elc_low_threshold_percent | 读写 | ELC 低阈值,单位为 CPU 利用率百分比 |
elc_high_threshold_percent | 读写 | ELC 高阈值,单位为 CPU 利用率百分比 |
elc_high_threshold_enable | 读写 | 启用/禁用 ELC 高阈值。写入1启用,写入0禁用 |
ELC 属性的动态挂载逻辑在 uncore-frequency-common.c:驱动先尝试读取elc_low_threshold_percent,只有读取成功(说明硬件支持 ELC)时,才注册这 4 个 ELC 属性,否则不出现。同时,ELC 属性仅存在于uncore*(fabric cluster)级别目录;root_domain(package 级聚合)与不支持 ELC 的 cluster 在读写时分别返回-ENODATA与-EOPNOTSUPP(见 uncore-frequency-tpmi.c)。
阈值与频率的位域编码细节
从 uncore-frequency-tpmi.c 可以看到 CONTROL 寄存器中的位域布局:
UNCORE_EFF_LAT_CTRL_RATIO_MASK:GENMASK_ULL(28, 22),地板频率 ratio;UNCORE_EFF_LAT_CTRL_LOW_THRESHOLD_MASK:GENMASK_ULL(38, 32),低阈值原始编码;UNCORE_EFF_LAT_CTRL_HIGH_THRESHOLD_ENABLE:BIT(39),高阈值使能位;UNCORE_EFF_LAT_CTRL_HIGH_THRESHOLD_MASK:GENMASK_ULL(46, 40),高阈值原始编码。
百分比阈值在读写时进行了编码换算:写入时val * FIELD_MAX(mask) / 100,读取时raw * 100 / FIELD_MAX(mask)并向上取整(DIV_ROUND_UP),保证百分比与硬件 7-bit 编码之间的双向无损换算。校验规则为:两个阈值百分比均不得超过 100,elc_high_threshold_enable仅接受 0/1,elc_floor_freq_khz折算为 ratio 后不得超过FIELD_MAX(UNCORE_EFF_LAT_CTRL_RATIO_MASK)(见 uncore-frequency-tpmi.c)。
完整配置示例:低利用率锁频 + 高利用率逐步升频
以下是文档给出的一个典型 ELC 配置,实现如下效果:
- 当 CPU 利用率低于 10%时:将 uncore 频率设定为800 MHz;
- 当 CPU 利用率高于 95%时:uncore 频率以100 MHz 步进逐步提升,直到功耗上限。
elc_floor_freq_khz:800000 elc_high_threshold_percent:95 elc_high_threshold_enable:1 elc_low_threshold_percent:10对应到 sysfs 的实际写操作(以uncore00目录为例,root 权限执行):
cd /sys/devices/system/cpu/intel_uncore_frequency/uncore00 # 设置地板频率:利用率低于低阈值时 uncore 不低于 800 MHz echo 800000 > elc_floor_freq_khz # 设置低/高阈值 echo 10 > elc_low_threshold_percent echo 95 > elc_high_threshold_percent # 启用高阈值步进升频 echo 1 > elc_high_threshold_enable # 验证当前配置 cat elc_floor_freq_khz cat elc_low_threshold_percent cat elc_high_threshold_percent cat elc_high_threshold_enable常规 min/max 频率设置示例
不带 ELC 的最基本用法同样简单:
cd /sys/devices/system/cpu/intel_uncore_frequency/package_00_die_00 # 查看复位后的初始范围 cat initial_min_freq_khz cat initial_max_freq_khz # 设置 uncore 频率范围(单位 kHz) echo 1200000 > min_freq_khz # 1.2 GHz echo 2400000 > max_freq_khz # 2.4 GHz # 读取当前实际 uncore 频率 cat current_freq_khz注意:频率单位一律为 kHz,数值需能被 100000(100 MHz)整除折算为 ratio;写入max_freq_khz/min_freq_khz时若换算后的 ratio 为 0 或超过位域上限(127),驱动会返回-EINVAL(见 uncore-frequency.c)。
源码视角:两类驱动的实现路径对比
整个 uncore 频率控制功能由三部分源码构成(均在drivers/platform/x86/intel/uncore-frequency/下):
| 文件 | 职责 |
|---|---|
| uncore-frequency.c | 非 TPMI 平台驱动:通过 MSR 0x620/0x621 在 per-die 粒度读写频率,处理 CPU 热插拔与休眠恢复 |
| uncore-frequency-tpmi.c | TPMI 平台驱动:通过 MMIO(UNCORE_STATUS_INDEX=0、UNCORE_CONTROL_INDEX=8)读写每个 fabric cluster 的 status/control 寄存器,替代 MSR 语义并新增 ELC 支持 |
| uncore-frequency-common.c | 公共框架:负责 sysfs 根目录(intel_uncore_frequency)的创建、属性组的注册/删除、互斥锁保护与 ELC 属性的按需挂载 |
两个驱动通过uncore_freq_common_init()注册各自的read/write回调(uncore-frequency-common.c),sysfs 层与硬件操作层被清晰解耦:公共层负责属性展示与输入解析(含kstrtouint十进制解析、kstrtobool布尔解析,见 uncore-frequency-common.c),底层驱动只负责 MSR/MMIO 的位域读写。
从驱动匹配表看,非 TPMI 驱动支持 Broadwell 至 Nova Lake 的广泛 Intel 平台(见 uncore-frequency.c),并在检测到运行于虚拟机(X86_FEATURE_HYPERVISOR)时拒绝加载;TPMI 驱动则作为 auxiliary 设备(intel_vsec.tpmi-uncore)由 VSEC/TPMI 框架实例化。
使用注意事项
- 作用域差异:无 TPMI 平台按 package/die 控制;TPMI 平台额外支持 fabric cluster 粒度(
uncore*目录)。current_freq_khz只在uncore*目录存在。 - 聚合与限制:在
package_*_die_*级别写入会广播到该 package 下所有 cluster;在uncore*级别单独写入更精细,但不能超出 package 级聚合范围(超出返回EINVAL)。 - ELC 前提:ELC 属性只在硬件支持且固件未屏蔽时出现;
elc_floor_freq_khz若低于min_freq_khz会被固件忽略,因此配置地板频率前应先确认min_freq_khz的取值。 - 恢复语义:休眠/挂起恢复后,驱动会自动写回最后保存的 MSR 0x620 值;如需恢复默认,读取
initial_min_freq_khz/initial_max_freq_khz并写回即可。 - 权限:可写属性权限为 0644,
current_freq_khz等敏感只读属性权限为 0400(仅 root),普通用户需要相应权限或 root 才能查看/修改。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考