perf后端:硬件PMU(下)

Linux阅码场 2023-03-28 09:40

扩展阅读:perf 后端:硬件 PMU(上)

0. 目录

1. 综述
2. 事件设计
3. 编码设计
3.1 PMC ID 编码空间划分
3.2 PMU 抽象
3.3 事件抽象
3.3.1 业务层抽象
3.3.2 硬件层抽象
3.4 设计大图
4. 工程实践
4.1 模型
4.2 PMU 初始化
4.3 事件初始化
4.3.1 my_perf_event_init
4.3.2 my_sys_perf_event_open
4.3.3 intel_pmu_hw_config
4.4 事件使能
4.4.1 my_perf_event_enable
4.4.2 x86_assign_hw_event
4.4.3 intel_pmu_enable_event
4.5 事件读取
4.6 事件禁能
5. 总结
6. 伏笔
7. 附录

1. 综述

本文乃内核 perf 框架解构系列文章第三篇。
《[perf 2] perf 后端:硬件 PMU(上)》一文,我们讨论了 PMU 硬件的基本使用范式,架构相关的概念,以及寄存器层面的基本操作及编程。
本文在上文基础上进行编码实践,目的是展示 Intel x86 架构下硬件 PMU 的编程操作,并从实践中提炼 perf 框架所面临和要解决的问题。
本文在 4.9 内核、skylake 超线程平台上编写一个内核模块,模块的功能是使用硬件 PMU 采集相应事件。本文代码与 OS、处理器平台关系不是很大,可以移植到其他平台。
阅读本文之前,强烈建议先阅读《[perf 2] perf 后端:硬件 PMU(上)》。
本文所有代码在第 7 章附录中,读者自行编译成内核模块运行验证(请不要直接在生产环境中干这事)。

2. 事件设计

本文通过硬件 PMU,采集 CPU 3 上的 'Instruction Retired' 及 'UnHalted Core Cycles' 事件。
之所以选择此二者事件,是因为这二者不仅可以通过 generic PMC(指定 umask + eventsel 的方式)采集,亦可通过 fixed PMC 采集,如此我们可以同时利用两种 PMC 来对此二者事件进行监控,通过相互对比采集的事件数值确认程序的正确性(符合预期的情况是,两种 PMC 采集出来的事件数值几乎相等)。
根据 Intel SDM Chapter 18 "Performance Monitoring",此二者事件是 architecture 的。所谓 architecture,指的是在不同架构之间皆相同,其反义词是 model specific。
'Instruction Retired' 事件的 umask 和 eventsel 分别是 00H 和 C0H;'UnHalted Core Cycles' 事件的 umask 和 eventsel 分别是 00H 和 3CH:
图 1
同时,此二者还可以通过第 0 和 第 1 个 fixed PMC 采集:
图 2
若要查询非 architecture 事件的 umask、eventsel,自行参阅 Intel SDM 19 "Performance Monitoring Events",不在本文话题范围内。

3. 编码设计

按说事件设计好,知道怎么用内核接口做 MSR 读写、发起 smp_call 以及起 hrtimer,基本上就可以开干了。
但为了让代码看起来不是这么的 naive,我们稍微做一点点设计。

3.1 PMC ID 编码空间划分

如你所知,generic PMC 和 fixed PMC,在实际编程中都是通过 ID 来索引的,也就是第几个 generic/fixed PMC。而此二者 ID 编码都是从 0 开始。
为了不让二者的 ID 编码混淆,我们对这二者 PMC 的 ID 做编码空间划分。根据《[perf 2] perf 后端:硬件 PMU(上)》“4.3 PMU 的配置及使能”一节,“至少从目前的 Intel CPU 设计来说,其为 generic PMC 预留的数量为 32”,一个合理的编码空间划分方案如下:
  • 0 - 31:用于 generic PMC 的 ID 编码。
  • 32 - :用于 fixed PMC 的 ID 编码。
我们参考内核的实现,这里将 32 这个魔术字定义为宏:
#define INTEL_PMC_IDX_FIXED              32
所以,编码为 32 的 fixed PMC,对应的是第 0 个 fixed PMC,以此类推。

3.2 PMU 抽象

数据结构如下(之所以加个 my_ 前缀,是因为内核中也有 x86_pmu 的数据结构,虽然本人很反感 my_ 前缀的 naming,但先将就着。下文的数据结构同理。):
struct my_x86_pmu {  int version;  unsigned eventsel;  unsigned perfctr;  int num_counters;  int num_counters_fixed;  int cntval_bits;  int (*rdpmc_index)(int index);    void (*enable)(struct my_perf_event *);  void (*disable)(struct my_perf_event *);  void (*read)(struct my_perf_event *event);  void (*hw_config)(struct my_perf_event *event);};
  • version:PMU 的版本。
  • eventsel:PMU IA32_PERFEVTSELx 寄存器起始地址。实际上,eventsel 是寄存器组,起始地址为 186H,一个 IA32_PERFEVTSELx 对应一个 generic PMC,用于对此 generic PMC 进行配置。
  • perfctl:PMU 的 IA32_PMCx 寄存器起始地址。实际上,perfctl 是一个寄存器组,起始地址为 0C1H,可以通过 IA32_PMCx 读出一个 generic PMC 的数值(本文采用与内核相同的方式,使用 RDPMC 指令,而不是从 IA32_PMCx 中读取)。
  • num_counters:generic PMC 数量。
  • num_counters_fixed:fixed PMC 数量。
  • cntval_bits:PMC 数据读出宽度。不同 PMU 实现,其 PMC 数据宽度可能是 48 bit 或其他,但内核的 perf 框架侧,会统一将其转成 64 bit 输出,方法是符号扩展,具体符号扩展算法需要依赖  PMC 的实际数据宽度,也就是 cntval_bits。
  • rdpmc_index:如前文所言,内核 perf 框架中读取 PMC 数据时,使用的是 RDPMC 指令,此指令需要传入一个 index,rdpmc_index 函数将 PMC 的 ID 转成 RDPMC 指令所需的 index。实际上,此函数并未实现,Intel 平台下,读取 generic PMC 数值的 RDPMC index 参数,就是此 PMC 的 ID。fixed PMC index 的计算略有不同,参考下文“4.4.2 x86_assign_hw_event”一节。
  • enable/disable/read:使能、禁能、读取一个 event。
  • hw_config:此函数获取一个事件在寄存器层面的配置,参考下文“4.3.3 intel_pmu_hw_config”一节。

3.3 事件抽象

3.3.1 业务层抽象

所谓的业务层,指的是用户通过 perf 前端接口传进来的事件属性。
struct my_perf_event_attr {  u64 config;};
struct my_perf_event { local64_t count; struct my_perf_event_attr attr; struct my_hw_perf_event hw;};
  • my_perf_event_attr.config:这是业务层传入的事件属性,是业务层语义,注意与下一小节中硬件层抽象 config 的区别。
  • count:该事件的具体数值,用户读取一个事件的具体数值时,返回的就是这个值,参考下文“4.5 事件读取”一节。
  • attr:事件中保存的业务层所传入的属性。
  • hw:一个业务层的事件,其具体采集任务需要一个具体的硬件 PMC,struct my_hw_perf_event 即是对硬件 PMC 的抽象。参考下一小节。

3.3.2 硬件层抽象

struct my_hw_perf_event {  struct { /* hardware */    u64 config;    unsigned long config_base;    unsigned long event_base;    int event_base_rdpmc;    int idx;  };
local64_t prev_count;};
  • config:事件的硬件层配置,对应一个 PMC 配置寄存器的配置,比如是否使能用户态(USER)下的事件采集。
  • config_base:此 PMC 的配置寄存器,对于 generic PMC,其是 IA32_PERFEVTSELx,对于 fixed PMC,其是 IA32_FIXED_CTR_CTRL 。
  • event_base:此 PMC 的读数寄存器,对于 generic PMC,其是 IA32_PMCx,对于 fixed PMC,其是 IA32_FIXED_CTRx。
  • event_base_rdpmc:使用 RDPMC 指令读取此 PMC 数值时,应该传入的 index 参数。
  • idx:此 PMC 的 ID 编码,注意,这是经过 ID 编码空间划分后的 ID。
  • prev_count:上一次从该 PMC 读出的事件计数值。

3.4 设计大图

图 3

4. 工程实践

4.1 模型

代码的模型,是在一个 kthread 中,监控 3 号 CPU 的 instruction 和 cycles 事件,并同时采用 generic 和 fixed PMC 两种方案:
static int test_loop(void *__){  unsigned long long prev_count[4] = { 0 }, now_count[4];  struct my_perf_event events[4];  int target_cpu = 3, i;
// suppose we are using the #2 generic pmc for 'Instruction Retired', umask 00h, event C0h // suppose we are using the #3 generic pmc for 'UnHalted Core Cycles', umask 00h, event 3Ch my_perf_event_init(&events[0], 0x00, 0xc0, 2, false); my_perf_event_init(&events[1], 0x00, 0x3c, 3, false);
// suppose we are using the #0 fixed pmc for 'Instruction Retired', event C0h // suppose we are using the #1 fixed pmc for 'UnHalted Core Cycles', event 3Ch my_perf_event_init(&events[2], 0x00, 0xc0, 0, true); my_perf_event_init(&events[3], 0x00, 0x3c, 1, true);
for (i = 0; i < ARRAY_SIZE(events); ++i) my_perf_event_enable(target_cpu, &events[i]);
for (i = 0; i < ARRAY_SIZE(events); ++i) prev_count[i] = my_perf_event_read(target_cpu, &events[i]);
while (!kthread_should_stop()) { for (i = 0; i < ARRAY_SIZE(events); ++i) { now_count[i] = my_perf_event_read(target_cpu, &events[i]); printk("event %d: %llu\n", i, now_count[i] - prev_count[i]); prev_count[i] = now_count[i]; }
msleep_interruptible(5000); }
for (i = 0; i < ARRAY_SIZE(events); ++i) my_perf_event_disable(target_cpu, &events[i]); return 0;}
static int __init test_module_init(void){ intel_pmu_init(); loop_task = kthread_run(test_loop, NULL, "test_loop");
return 0;}
static void __exit test_module_exit(void){ kthread_stop(loop_task);}
模型总体逻辑如下:
  • 事件初始化(7 - 15 行):调用 my_perf_event_init 接口初始化四个 PMC 监控事件,前两个采用 generic PMC 来监控,后两个采用 fixed PMC 来监控。
  • 事件使能(17 - 18 行):调用 my_perf_event_enable 接口使能上一步初始化的四个事件。
  • 事件初始值读取(20 - 21 行):调用 my_perf_event_read 接口读出四个事件的初始值。注意,PMC 第一次使用时,其中可能有残留的值(上一次被使用后残留的值),为了获取准确的读数,我们先读出其初始值,后续取每两次读数的差值,也即净增长值。
  • 事件净增长值读取(23 - 31 行):调用 my_perf_event_read 接口读出四个事件的当前值,并与上一次的读数做差获取净增长值。本文模型下,每 5 秒采集一次。
  • 事件禁能(33 - 34 行):事件禁能。
  • PMU 初始化(41 行):module 的 init 函数中,调用 intel_pmu_init 初始化 PMU。
下面,逐个拆解函数的具体实现。

4.2 PMU 初始化

my_x86_pmu 是对 x86 体系结构 PMU 的抽象,其各项回调函数,由具体处理器平台实现。
本文实验是在 Intel x86 平台上,底层函数是 intel_pmu_ 前缀的 Intel PMU 实现。
static struct my_x86_pmu x86_pmu = {  .eventsel = MSR_ARCH_PERFMON_EVENTSEL0,  .perfctr = MSR_ARCH_PERFMON_PERFCTR0,
.enable = intel_pmu_enable_event, .disable = intel_pmu_disable_event, .read = intel_pmu_read_event, .hw_config = intel_pmu_hw_config,};
intel_pmu_init 中做 PMU 的基本信息获取:
void intel_pmu_init(void){  union cpuid10_edx edx;  union cpuid10_eax eax;  union cpuid10_ebx ebx;  unsigned int unused;  int version;
cpuid(10, &eax.full, &ebx.full, &unused, &edx.full);
version = eax.split.version_id;
x86_pmu.version = version; x86_pmu.num_counters = eax.split.num_counters; x86_pmu.cntval_bits = eax.split.bit_width;
if (version > 1) { int assume = 3 * !boot_cpu_has(X86_FEATURE_HYPERVISOR);
x86_pmu.num_counters_fixed = max((int)edx.split.num_counters_fixed, assume); }
printk("... version: %d\n", x86_pmu.version); printk("... bit width: %d\n", x86_pmu.cntval_bits); printk("... generic registers: %d\n", x86_pmu.num_counters); printk("... fixed-purpose events: %d\n", x86_pmu.num_counters_fixed); printk("... x86_model: %d\n", boot_cpu_data.x86_model);}
简单讲一下:
  • 9 行:通过 cpuid 获取 PMU 基本信息。
  • 17 - 22 行:根据 PMU 版本信息,计算 num_counters_fixed,也就是 fixed PMC 的数量。具体算法有点 specific,不必深究。
  • 24 - 28 行:打印 PMU 信息概览。
在我的机器上:
... version:                4... bit width:              48... generic registers:      4... fixed-purpose events:   3... x86_model:              85

4.3 事件初始化

总体逻辑:
static void intel_pmu_hw_config(struct my_perf_event *event){  event->hw.config = ARCH_PERFMON_EVENTSEL_INT;
event->hw.config |= ARCH_PERFMON_EVENTSEL_USR; event->hw.config |= ARCH_PERFMON_EVENTSEL_OS; event->hw.config |= event->attr.config & X86_RAW_EVENT_MASK;}
static void x86_pmu_event_init(struct my_perf_event *event){ x86_pmu.hw_config(event);}
static void perf_init_event(struct my_perf_event *event){ x86_pmu_event_init(event);}
static void my_sys_perf_event_open(struct my_perf_event *event, struct my_perf_event_attr *attr, int idx, bool fixed){ memset(event, 0, sizeof(*event));
event->attr = *attr;
if (fixed) event->hw.idx = idx + INTEL_PMC_IDX_FIXED; else event->hw.idx = idx;
perf_init_event(event);}
static void my_perf_event_init(struct my_perf_event *event, u64 umask, u64 eventsel, int idx, bool fixed){ struct my_perf_event_attr attr;
memset(&attr, 0, sizeof(attr)); attr.config = (umask << 4) | eventsel;
my_sys_perf_event_open(event, &attr, idx, fixed);}
下面逐个讲解重要的函数。

4.3.1 my_perf_event_init

此函数接受 4 个参数,事件的抽象数据结构、事件的 umask/eventsel、事件底层 PMC ID 编号,此事件底层 PMC 是否是 fixed。
框架侧调用点的注释写的很清楚了,使用第 2、3 个 generic PMC,分别对 instruction、cycles 两个事件做监控;同时使用第 0、1 个 fixed PMC,分别对 instruction、cycles 两个事件做监控:
// suppose we are using the #2 generic pmc for 'Instruction Retired', umask 00h, event C0h// suppose we are using the #3 generic pmc for 'UnHalted Core Cycles', umask 00h, event 3Chmy_perf_event_init(&events[0], 0x00, 0xc0, 2, false);my_perf_event_init(&events[1], 0x00, 0x3c, 3, false);
// suppose we are using the #0 fixed pmc for 'Instruction Retired', event C0h// suppose we are using the #1 fixed pmc for 'UnHalted Core Cycles', event 3Chmy_perf_event_init(&events[2], 0x00, 0xc0, 0, true);my_perf_event_init(&events[3], 0x00, 0x3c, 1, true);
这里要注意,两个 generic PMC 的 ID 是我随便选的(我的机器上不大于 generic PMC 的最大编号,也就是 3 即可),两个 fixed PMC 的 ID 需要严格与 SDM 上的一致,也就是 0、1。参考本文“2. 事件设计”一节。
my_perf_event_init 函数中将 umask、eventsel 按照 architecture specific 的约束,构造成事件属性的 config 域,最后调用 my_sys_perf_event_open 接口打开此事件。

4.3.2 my_sys_perf_event_open

此接口:
  • 将事件属性保存到事件数据结构中。
  • 根据是否是 fixed PMC,对 PMC 的 ID 做编码空间上的隔离,注意这里的编码信息记录到事件的硬件抽象层,也就是 my_hw_perf_event 中。
  • 最后调用 perf_init_event,此函数最终调用到 intel_pmu_hw_config,对事件的硬件层抽象做初始化。

4.3.3 intel_pmu_hw_config

为什么要配置成这样,参考下面两个寄存器:
图 4
图 5
在我们的 intel_pmu_hw_config 实现中,使能了如下 bit 位:
  • INT:如果 PMC overflow,则处理器通过其 LAPIC 触发一个 exception。
  • USR:使能 CPU 在 ring 1、2、3 特权级下的事件计数(简单理解为,使能 CPU 运行在用户态时的事件计数)。
  • OS:使能 CPU 在 ring 0 特权级下的事件计数(简单理解为,使能 CPU 运行在内核态时的事件计数)。
  • UMASK + EventSelect:将业务层传入的事件 umask、eventsel,转成硬件层 PMC 的配置。
此寄存器 bit 位详解,参考 SDM "18.2.1.1 Architectural Performance Monitoring Version 1 Facilities"。
注意:我们的代码模型下,hardcode 了寄存器的这几个 bit,实际的内核 perf 框架中,当然不是 hardcode 的,而是根据 perf 前端接口所传入的事件属性,决定对应 bit 位是否置上。
比如 USER、OS bit 位是否置上,取决于 perf_event_attr 中的 exclude_user、exclude_kernel 配置:
图 6

4.4 事件使能

总体逻辑:
static void __x86_pmu_enable_event(struct my_hw_perf_event *hwc, u64 enable_mask){  wrmsrl(hwc->config_base, hwc->config | enable_mask);}
static void intel_pmu_enable_fixed(struct my_perf_event *event){ u64 ctrl_val, mask, bits = 0; struct my_hw_perf_event *hwc = &event->hw; int idx = hwc->idx;
bits |= 0x8; if (hwc->config & ARCH_PERFMON_EVENTSEL_USR) bits |= 0x2; if (hwc->config & ARCH_PERFMON_EVENTSEL_OS) bits |= 0x1;
idx -= INTEL_PMC_IDX_FIXED; bits <<= (idx * 4); mask = 0xfULL << (idx * 4);
rdmsrl(hwc->config_base, ctrl_val); ctrl_val &= ~mask; ctrl_val |= bits; wrmsrl(hwc->config_base, ctrl_val);}
static void intel_pmu_enable_event(struct my_perf_event *event){ struct my_hw_perf_event *hwc = &event->hw;
switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: __x86_pmu_enable_event(hwc, ARCH_PERFMON_EVENTSEL_ENABLE); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: intel_pmu_enable_fixed(event); break; };}
static void x86_pmu_enable(struct my_perf_event *event){ x86_assign_hw_event(event); x86_pmu.enable(event);}
static void __my_perf_event_enable(void *info){ struct my_perf_event *event = info;
x86_pmu_enable(event);}
static void my_perf_event_enable(int target_cpu, struct my_perf_event *event){ (void)smp_call_function_single(target_cpu, __my_perf_event_enable, event, 1);}
下面逐个讲解重要的函数。

4.4.1 my_perf_event_enable

因为 PMU 是 per-cpu 的,要使能目标 CPU 上的 event(本质就是使能目标 CPU 上的某个 PMC),需要通过 smp_call 跨核调过去,回调是 __my_perf_event_enable,此回调中干了两件事:
  • x86_assign_hw_event:为 event 分配一个 PMC,并初始化此 PMC 的寄存器信息。在咱们的模型下,event 的 PMC 是代码中手动指定的(my_perf_event_init 中指定了 PMC 的 ID)
  • intel_pmu_enable_event:写配置寄存器的 enable bit,从硬件上使能 PMC。

4.4.2 x86_assign_hw_event

根据事件硬件层抽象中的 idx,区分其是 generic 还是 fixed PMC,并分别调用相应逻辑:
  • 如果是 generic PMC(0 ... INTEL_PMC_IDX_FIXED - 1):config_base 是 PMC 对应的 IA32_PERFEVTSELx 寄存器,event_base 是 PMC 对应的 IA32_PMCx 寄存器(实际上此寄存器压根没用到),event_base_rdpmc 就是 PMC 的编号(generic PMC ID 编址空间下)。
  • 如果是 fixed PMC(INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15):config_base 是 IA32_FIXED_CTR_CTRL 寄存器(所有 fixed PMC 皆是通过这一个寄存器来配置),event_base 是 PMC 对应的 IA32_FIXED_CTRx 寄存器(实际上此寄存器压根没用到),event_base_rdpmc 的计算算法见代码。
如果你对这些寄存器感到困惑,请参阅上一篇文章 “4.2 事件(PMC)的配置及读取”一节。
此函数说白了,就是将一个事件与一个硬件 PMC 建立关系(assign)。
static inline unsigned int x86_pmu_config_addr(int index){  return x86_pmu.eventsel + index;}
static inline unsigned int x86_pmu_event_addr(int index){ return x86_pmu.perfctr + index;}
static inline int x86_pmu_rdpmc_index(int index){ return x86_pmu.rdpmc_index ? x86_pmu.rdpmc_index(index) : index;}
static void x86_assign_hw_event(struct my_perf_event *event){ struct my_hw_perf_event *hwc = &event->hw;
switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: hwc->config_base = x86_pmu_config_addr(hwc->idx); hwc->event_base = x86_pmu_event_addr(hwc->idx); hwc->event_base_rdpmc = x86_pmu_rdpmc_index(hwc->idx); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: hwc->config_base = MSR_ARCH_PERFMON_FIXED_CTR_CTRL; hwc->event_base = MSR_ARCH_PERFMON_FIXED_CTR0; hwc->event_base_rdpmc = (hwc->idx - INTEL_PMC_IDX_FIXED) | INTEL_PMC_FIXED_RDPMC_BASE; break; default: return; };}

4.4.3 intel_pmu_enable_event

x86_assign_hw_event 函数中,为事件分配了一个硬件 PMC,并获取了 PMC 的配置、读数寄存器。
intel_pmu_enable_event 函数,基于上一步成果,真正地写入寄存器,让 PMC 硬件实际生效。
  • 对于 generic PMC,使能 PMC 的代码(__x86_pmu_enable_event)非常简单,将 config 带上 EN bit,然后写入 IA32_PERFEVTSELx 寄存器,参考图 4。
  • 对于 fixed PMC,略显复杂,但实际上也很简单,其本质就是写入 IA32_FIXED_CTR_CTRL 寄存器,参考图 5。

4.5 事件读取

my_perf_event_read 接口做了两件事:
  • perf_event_read:此函数通过 smp_call,一路调用到 PMC 底层寄存器读操作(x86_perf_event_update),通过 rdpmcl(底层是 RDPMC 指令),根据事件的 event_base_rdpmc,读出 PMC 硬件中的值,并将读出的硬件数据,做符号扩展至 64 bit,并将数值存到事件的 count 域中。
  • perf_event_count:基于上一步成果,将 event 的 count 域返回。
u64 x86_perf_event_update(struct my_perf_event *event){  struct my_hw_perf_event *hwc = &event->hw;  int shift = 64 - x86_pmu.cntval_bits;  u64 prev_raw_count, new_raw_count;  u64 delta;
again: prev_raw_count = local64_read(&hwc->prev_count); rdpmcl(hwc->event_base_rdpmc, new_raw_count);
if (local64_cmpxchg(&hwc->prev_count, prev_raw_count, new_raw_count) != prev_raw_count) goto again;
/* * Now we have the new raw value and have updated the prev * timestamp already. We can now calculate the elapsed delta * (event-)time and add that to the generic event. * * Careful, not all hw sign-extends above the physical width * of the count. */ delta = (new_raw_count << shift) - (prev_raw_count << shift); delta >>= shift;
local64_add(delta, &event->count);
return new_raw_count;}
static void intel_pmu_read_event(struct my_perf_event *event){ x86_perf_event_update(event);}
static void x86_pmu_read(struct my_perf_event *event){ x86_pmu.read(event);}
static void __perf_event_read(void *info){ struct my_perf_event *event = info;
x86_pmu_read(event);}
static void perf_event_read(int target_cpu, struct my_perf_event *event){ (void)smp_call_function_single(target_cpu, __perf_event_read, event, 1);}
static u64 perf_event_count(struct my_perf_event *event){ return local64_read(&event->count);}
u64 my_perf_event_read(int target_cpu, struct my_perf_event *event){ perf_event_read(target_cpu, event); return perf_event_count(event);}

4.6 事件禁能

重点看 x86_pmu_disable_event(generic PMC)、intel_pmu_disable_fixed(fixed PMC),主体逻辑与事件使能类似,只不过是反着来的,操作的是同一个寄存器。读者自行理解,不啰嗦了。
static inline void x86_pmu_disable_event(struct my_perf_event *event){  struct my_hw_perf_event *hwc = &event->hw;
wrmsrl(hwc->config_base, hwc->config);}
static void intel_pmu_disable_fixed(struct my_perf_event *event){ struct my_hw_perf_event *hwc = &event->hw; u64 ctrl_val, mask; int idx = hwc->idx;
mask = 0xfULL << ((idx - INTEL_PMC_IDX_FIXED) * 4); rdmsrl(hwc->config_base, ctrl_val); ctrl_val &= ~mask; wrmsrl(hwc->config_base, ctrl_val);}
static void intel_pmu_disable_event(struct my_perf_event *event){ struct my_hw_perf_event *hwc = &event->hw;
switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: x86_pmu_disable_event(event); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: intel_pmu_disable_fixed(event); break; };}
static void x86_pmu_disable(struct my_perf_event *event){ x86_pmu.disable(event);}
static void __perf_event_disable(void *info){ struct my_perf_event *event = info;
x86_pmu_disable(event);}
static void my_perf_event_disable(int target_cpu, struct my_perf_event *event){ smp_call_function_single(target_cpu, __perf_event_disable, event, 1);}

5. 总结

  • 我们的模型下,通过查阅 SDM,获取所要监控事件的 umask、eventsel,并手动指定通过哪个 generic/fixed PMC 来监控对应事件。
  • 模块初始化函数中,获取 PMU 信息,比较重要的是 PMU 的数据宽度(cntval_bits)。
  • 事件初始化逻辑,将业务层传入的事件属性做记录,并生成事件硬件层抽象的基本配置(基于 umask、eventsel),并将 PMC 的 id 做编码空间隔离。
  • 事件使能逻辑,根据 PMC 的 id,进一步设置事件硬件层抽象(本质上对应的就是一个 PMC)的寄存器信息,并写入硬件寄存器,实际使能此 PMC。
  • 事件读取逻辑,通过 RDPMC 读出事件对应 PMC 的数值,并做 64 bit 符号扩展。
  • 事件禁能逻辑,与事件使能逻辑相反,写入硬件寄存器禁能此 PMC。

6. 伏笔

行文至此,本文不仅没有解答上一篇文章 “5. 伏笔”一节的任何问题,反而引入了更多问题:
  • 我们所监控事件的 umask、eventsel 都是自己查 SDM 得来的,内核还支持通过事件的通用 ID 来指定,它是怎么实现的(《[perf 1] perf 前端》“3.2 事件类型”)?
  • 我们对事件的 PMC 分配采用的是手动方式(手动指定 ID 以及是否使用 fixed PMC),内核 perf 框架显然不可能也这么干,它是怎么实现的?
  • 本文指定的两个事件,即可以通过 generic PMC,也可以通过 fixed PMC,如果是内核 perf 框架,它会怎么做选择?
  • 我们指定的事件之间并无事件组关系,而我们知道内核 perf 框架还支持事件组的采集,它是怎么实现的?
  • 我们的模型只是采集某个 cpu 的 PMU 事件,perf 框架还支持 per-task 的 PMU 事件采集,它是怎么实现的?
这些问题的解答留待内核 perf 框架的后续解构文章。
实际上,搞清楚了问题是什么,就已经搞清楚了问题总体的百分之八十

7. 附录

#include #include #include #include 
static struct task_struct *loop_task;
/* * Performance event hw details: */#define MSR_ARCH_PERFMON_EVENTSEL0 0x186#define MSR_ARCH_PERFMON_PERFCTR0 0xc1
#define ARCH_PERFMON_EVENTSEL_EVENT 0x000000FFULL#define ARCH_PERFMON_EVENTSEL_UMASK 0x0000FF00ULL#define ARCH_PERFMON_EVENTSEL_USR (1ULL << 16)#define ARCH_PERFMON_EVENTSEL_OS (1ULL << 17)#define ARCH_PERFMON_EVENTSEL_EDGE (1ULL << 18)#define ARCH_PERFMON_EVENTSEL_PIN_CONTROL (1ULL << 19)#define ARCH_PERFMON_EVENTSEL_INT (1ULL << 20)#define ARCH_PERFMON_EVENTSEL_ANY (1ULL << 21)#define ARCH_PERFMON_EVENTSEL_ENABLE (1ULL << 22)#define ARCH_PERFMON_EVENTSEL_INV (1ULL << 23)#define ARCH_PERFMON_EVENTSEL_CMASK 0xFF000000ULL
#define X86_RAW_EVENT_MASK \ (ARCH_PERFMON_EVENTSEL_EVENT | \ ARCH_PERFMON_EVENTSEL_UMASK | \ ARCH_PERFMON_EVENTSEL_EDGE | \ ARCH_PERFMON_EVENTSEL_INV | \ ARCH_PERFMON_EVENTSEL_CMASK)
/* * All the fixed-mode PMCs are configured via this single MSR: */#define MSR_ARCH_PERFMON_FIXED_CTR_CTRL 0x38d#define MSR_ARCH_PERFMON_FIXED_CTR0 0x309
/* RDPMC offset for Fixed PMCs */#define INTEL_PMC_FIXED_RDPMC_BASE (1 << 30)
#define INTEL_PMC_IDX_FIXED 32
struct my_hw_perf_event { struct { /* hardware */ u64 config; unsigned long config_base; unsigned long event_base; int event_base_rdpmc; int idx; }; local64_t prev_count;};
struct my_perf_event_attr { u64 config;};
struct my_perf_event { local64_t count; struct my_perf_event_attr attr; struct my_hw_perf_event hw;};
struct my_x86_pmu { int version; unsigned eventsel; unsigned perfctr; int num_counters; int num_counters_fixed; int cntval_bits; int (*rdpmc_index)(int index);
void (*enable)(struct my_perf_event *); void (*disable)(struct my_perf_event *); void (*read)(struct my_perf_event *event); void (*hw_config)(struct my_perf_event *event);};
static void intel_pmu_enable_event(struct my_perf_event *event);static void intel_pmu_disable_event(struct my_perf_event *event);static void intel_pmu_read_event(struct my_perf_event *event);static void intel_pmu_hw_config(struct my_perf_event *event);
static struct my_x86_pmu x86_pmu = { .eventsel = MSR_ARCH_PERFMON_EVENTSEL0, .perfctr = MSR_ARCH_PERFMON_PERFCTR0,
.enable = intel_pmu_enable_event, .disable = intel_pmu_disable_event, .read = intel_pmu_read_event, .hw_config = intel_pmu_hw_config,};
static inline unsigned int x86_pmu_config_addr(int index){ return x86_pmu.eventsel + index;}
static inline unsigned int x86_pmu_event_addr(int index){ return x86_pmu.perfctr + index;}
static inline int x86_pmu_rdpmc_index(int index){ return x86_pmu.rdpmc_index ? x86_pmu.rdpmc_index(index) : index;}
static void x86_assign_hw_event(struct my_perf_event *event){ struct my_hw_perf_event *hwc = &event->hw;
switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: hwc->config_base = x86_pmu_config_addr(hwc->idx); hwc->event_base = x86_pmu_event_addr(hwc->idx); hwc->event_base_rdpmc = x86_pmu_rdpmc_index(hwc->idx); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: hwc->config_base = MSR_ARCH_PERFMON_FIXED_CTR_CTRL; hwc->event_base = MSR_ARCH_PERFMON_FIXED_CTR0; hwc->event_base_rdpmc = (hwc->idx - INTEL_PMC_IDX_FIXED) | INTEL_PMC_FIXED_RDPMC_BASE; break; default: return; };}
void intel_pmu_init(void){ union cpuid10_edx edx; union cpuid10_eax eax; union cpuid10_ebx ebx; unsigned int unused; int version;
cpuid(10, &eax.full, &ebx.full, &unused, &edx.full);
version = eax.split.version_id;
x86_pmu.version = version; x86_pmu.num_counters = eax.split.num_counters; x86_pmu.cntval_bits = eax.split.bit_width;
if (version > 1) { int assume = 3 * !boot_cpu_has(X86_FEATURE_HYPERVISOR);
x86_pmu.num_counters_fixed = max((int)edx.split.num_counters_fixed, assume); }
printk("... version: %d\n", x86_pmu.version); printk("... bit width: %d\n", x86_pmu.cntval_bits); printk("... generic registers: %d\n", x86_pmu.num_counters); printk("... fixed-purpose events: %d\n", x86_pmu.num_counters_fixed); printk("... x86_model: %d\n", boot_cpu_data.x86_model);}
///////////////////////////////// read logicu64 x86_perf_event_update(struct my_perf_event *event){ struct my_hw_perf_event *hwc = &event->hw; int shift = 64 - x86_pmu.cntval_bits; u64 prev_raw_count, new_raw_count; u64 delta;
again: prev_raw_count = local64_read(&hwc->prev_count); rdpmcl(hwc->event_base_rdpmc, new_raw_count);
if (local64_cmpxchg(&hwc->prev_count, prev_raw_count, new_raw_count) != prev_raw_count) goto again;
/* * Now we have the new raw value and have updated the prev * timestamp already. We can now calculate the elapsed delta * (event-)time and add that to the generic event. * * Careful, not all hw sign-extends above the physical width * of the count. */ delta = (new_raw_count << shift) - (prev_raw_count << shift); delta >>= shift;
local64_add(delta, &event->count);
return new_raw_count;}
static void intel_pmu_read_event(struct my_perf_event *event){ x86_perf_event_update(event);}
static void x86_pmu_read(struct my_perf_event *event){ x86_pmu.read(event);}
static void __perf_event_read(void *info){ struct my_perf_event *event = info;
x86_pmu_read(event);}
static void perf_event_read(int target_cpu, struct my_perf_event *event){ (void)smp_call_function_single(target_cpu, __perf_event_read, event, 1);}
static u64 perf_event_count(struct my_perf_event *event){ return local64_read(&event->count);}
u64 my_perf_event_read(int target_cpu, struct my_perf_event *event){ perf_event_read(target_cpu, event); return perf_event_count(event);}
///////////////////////////////// disable logicstatic inline void x86_pmu_disable_event(struct my_perf_event *event){ struct my_hw_perf_event *hwc = &event->hw;
wrmsrl(hwc->config_base, hwc->config);}
static void intel_pmu_disable_fixed(struct my_perf_event *event){ struct my_hw_perf_event *hwc = &event->hw; u64 ctrl_val, mask; int idx = hwc->idx;
mask = 0xfULL << ((idx - INTEL_PMC_IDX_FIXED) * 4); rdmsrl(hwc->config_base, ctrl_val); ctrl_val &= ~mask; wrmsrl(hwc->config_base, ctrl_val);}
static void intel_pmu_disable_event(struct my_perf_event *event){ struct my_hw_perf_event *hwc = &event->hw;
switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: x86_pmu_disable_event(event); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: intel_pmu_disable_fixed(event); break; };}
static void x86_pmu_disable(struct my_perf_event *event){ x86_pmu.disable(event);}
static void __perf_event_disable(void *info){ struct my_perf_event *event = info;
x86_pmu_disable(event);}
static void my_perf_event_disable(int target_cpu, struct my_perf_event *event){ smp_call_function_single(target_cpu, __perf_event_disable, event, 1);}
///////////////////////////////// enable logicstatic void __x86_pmu_enable_event(struct my_hw_perf_event *hwc, u64 enable_mask){ wrmsrl(hwc->config_base, hwc->config | enable_mask);}
static void intel_pmu_enable_fixed(struct my_perf_event *event){ u64 ctrl_val, mask, bits = 0; struct my_hw_perf_event *hwc = &event->hw; int idx = hwc->idx;
bits |= 0x8; if (hwc->config & ARCH_PERFMON_EVENTSEL_USR) bits |= 0x2; if (hwc->config & ARCH_PERFMON_EVENTSEL_OS) bits |= 0x1;
idx -= INTEL_PMC_IDX_FIXED; bits <<= (idx * 4); mask = 0xfULL << (idx * 4);
rdmsrl(hwc->config_base, ctrl_val); ctrl_val &= ~mask; ctrl_val |= bits; wrmsrl(hwc->config_base, ctrl_val);}
static void intel_pmu_enable_event(struct my_perf_event *event){ struct my_hw_perf_event *hwc = &event->hw;
switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: __x86_pmu_enable_event(hwc, ARCH_PERFMON_EVENTSEL_ENABLE); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: intel_pmu_enable_fixed(event); break; };}
static void x86_pmu_enable(struct my_perf_event *event){ x86_assign_hw_event(event); x86_pmu.enable(event);}
static void __my_perf_event_enable(void *info){ struct my_perf_event *event = info;
x86_pmu_enable(event);}
static void my_perf_event_enable(int target_cpu, struct my_perf_event *event){ (void)smp_call_function_single(target_cpu, __my_perf_event_enable, event, 1);}
///////////////////////////////// init logicstatic void intel_pmu_hw_config(struct my_perf_event *event){ event->hw.config = ARCH_PERFMON_EVENTSEL_INT;
event->hw.config |= ARCH_PERFMON_EVENTSEL_USR; event->hw.config |= ARCH_PERFMON_EVENTSEL_OS; event->hw.config |= event->attr.config & X86_RAW_EVENT_MASK;}
static void x86_pmu_event_init(struct my_perf_event *event){ x86_pmu.hw_config(event);}
static void perf_init_event(struct my_perf_event *event){ x86_pmu_event_init(event);}
static void my_sys_perf_event_open(struct my_perf_event *event, struct my_perf_event_attr *attr, int idx, bool fixed){ memset(event, 0, sizeof(*event));
event->attr = *attr;
if (fixed) event->hw.idx = idx + INTEL_PMC_IDX_FIXED; else event->hw.idx = idx;
perf_init_event(event);}
static void my_perf_event_init(struct my_perf_event *event, u64 umask, u64 eventsel, int idx, bool fixed){ struct my_perf_event_attr attr;
memset(&attr, 0, sizeof(attr)); attr.config = (umask << 4) | eventsel;
my_sys_perf_event_open(event, &attr, idx, fixed);}
static int test_loop(void *__){ unsigned long long prev_count[4] = { 0 }, now_count[4]; struct my_perf_event events[4]; int target_cpu = 3, i;
// suppose we are using the #2 generic pmc for 'Instruction Retired', umask 00h, event C0h // suppose we are using the #3 generic pmc for 'UnHalted Core Cycles', umask 00h, event 3Ch my_perf_event_init(&events[0], 0x00, 0xc0, 2, false); my_perf_event_init(&events[1], 0x00, 0x3c, 3, false);
// suppose we are using the #0 fixed pmc for 'Instruction Retired', event C0h // suppose we are using the #1 fixed pmc for 'UnHalted Core Cycles', event 3Ch my_perf_event_init(&events[2], 0x00, 0xc0, 0, true); my_perf_event_init(&events[3], 0x00, 0x3c, 1, true);
for (i = 0; i < ARRAY_SIZE(events); ++i) my_perf_event_enable(target_cpu, &events[i]);
for (i = 0; i < ARRAY_SIZE(events); ++i) prev_count[i] = my_perf_event_read(target_cpu, &events[i]);
while (!kthread_should_stop()) { for (i = 0; i < ARRAY_SIZE(events); ++i) { now_count[i] = my_perf_event_read(target_cpu, &events[i]); printk("event %d: %llu\n", i, now_count[i] - prev_count[i]); prev_count[i] = now_count[i]; }
msleep_interruptible(5000); }
for (i = 0; i < ARRAY_SIZE(events); ++i) my_perf_event_disable(target_cpu, &events[i]); return 0;}
static int __init test_module_init(void){ intel_pmu_init(); loop_task = kthread_run(test_loop, NULL, "test_loop");
return 0;}
static void __exit test_module_exit(void){ kthread_stop(loop_task);}
module_init(test_module_init);module_exit(test_module_exit);
MODULE_LICENSE("GPL");

Linux阅码场 专业的Linux技术社区和Linux操作系统学习平台,内容涉及Linux内核,Linux内存管理,Linux进程管理,Linux文件系统和IO,Linux性能调优,Linux设备驱动以及Linux虚拟化和云计算等各方各面.
评论
  • 数字隔离芯片是现代电气工程师在进行电路设计时所必须考虑的一种电子元件,主要用于保护低压控制电路中敏感电子设备的稳定运行与操作人员的人身安全。其不仅能隔离两个或多个高低压回路之间的电气联系,还能防止漏电流、共模噪声与浪涌等干扰信号的传播,有效增强电路间信号传输的抗干扰能力,同时提升电子系统的电磁兼容性与通信稳定性。容耦隔离芯片的典型应用原理图值得一提的是,在电子电路中引入隔离措施会带来传输延迟、功耗增加、成本增加与尺寸增加等问题,而数字隔离芯片的目标就是尽可能消除这些不利影响,同时满足安全法规的要
    华普微HOPERF 2025-01-15 09:48 107浏览
  • PNT、GNSS、GPS均是卫星定位和导航相关领域中的常见缩写词,他们经常会被用到,且在很多情况下会被等同使用或替换使用。我们会把定位导航功能测试叫做PNT性能测试,也会叫做GNSS性能测试。我们会把定位导航终端叫做GNSS模块,也会叫做GPS模块。但是实际上他们之间是有一些重要的区别。伴随着技术发展与越发深入,我们有必要对这三个词汇做以清晰的区分。一、什么是GPS?GPS是Global Positioning System(全球定位系统)的缩写,它是美国建立的全球卫星定位导航系统,是GNSS概
    德思特测试测量 2025-01-13 15:42 527浏览
  • 随着数字化的不断推进,LED显示屏行业对4K、8K等超高清画质的需求日益提升。与此同时,Mini及Micro LED技术的日益成熟,推动了间距小于1.2 Pitch的Mini、Micro LED显示屏的快速发展。这类显示屏不仅画质卓越,而且尺寸适中,通常在110至1000英寸之间,非常适合应用于电影院、监控中心、大型会议、以及电影拍摄等多种室内场景。鉴于室内LED显示屏与用户距离较近,因此对于噪音控制、体积小型化、冗余备份能力及电气安全性的要求尤为严格。为满足这一市场需求,开关电源技术推出了专为
    晶台光耦 2025-01-13 10:42 525浏览
  • ARMv8-A是ARM公司为满足新需求而重新设计的一个架构,是近20年来ARM架构变动最大的一次。以下是对ARMv8-A的详细介绍: 1. 背景介绍    ARM公司最初并未涉足PC市场,其产品主要针对功耗敏感的移动设备。     随着技术的发展和市场需求的变化,ARM开始扩展到企业设备、服务器等领域,这要求其架构能够支持更大的内存和更复杂的计算任务。 2. 架构特点    ARMv8-A引入了Execution State(执行状
    丙丁先生 2025-01-12 10:30 486浏览
  • 根据Global Info Research(环洋市场咨询)项目团队最新调研,预计2030年全球无人机电池和电源产值达到2834百万美元,2024-2030年期间年复合增长率CAGR为10.1%。 无人机电池是为无人机提供动力并使其飞行的关键。无人机使用的电池类型因无人机的大小和型号而异。一些常见的无人机电池类型包括锂聚合物(LiPo)电池、锂离子电池和镍氢(NiMH)电池。锂聚合物电池是最常用的无人机电池类型,因为其能量密度高、设计轻巧。这些电池以输出功率大、飞行时间长而著称。不过,它们需要
    GIRtina 2025-01-13 10:49 211浏览
  • 食物浪费已成为全球亟待解决的严峻挑战,并对环境和经济造成了重大影响。最新统计数据显示,全球高达三分之一的粮食在生产过程中损失或被无谓浪费,这不仅导致了资源消耗,还加剧了温室气体排放,并带来了巨大经济损失。全球领先的光学解决方案供应商艾迈斯欧司朗(SIX:AMS)近日宣布,艾迈斯欧司朗基于AS7341多光谱传感器开发的创新应用来解决食物浪费这一全球性难题。其多光谱传感解决方案为农业与食品行业带来深远变革,该技术通过精确判定最佳收获时机,提升质量控制水平,并在整个供应链中有效减少浪费。 在2024
    艾迈斯欧司朗 2025-01-14 18:45 82浏览
  • 新年伊始,又到了对去年做总结,对今年做展望的时刻 不知道你在2024年初立的Flag都实现了吗? 2025年对自己又有什么新的期待呢? 2024年注定是不平凡的一年, 一年里我测评了50余块开发板, 写出了很多科普文章, 从一个小小的工作室成长为科工公司。 展望2025年, 中国香河英茂科工, 会继续深耕于,具身机器人、飞行器、物联网等方面的研发, 我觉得,要向未来学习未来, 未来是什么? 是掌握在孩子们生活中的发现,和精历, 把最好的技术带给孩子,
    丙丁先生 2025-01-11 11:35 466浏览
  •   在信号处理过程中,由于信号的时域截断会导致频谱扩展泄露现象。那么导致频谱泄露发生的根本原因是什么?又该采取什么样的改善方法。本文以ADC性能指标的测试场景为例,探讨了对ADC的输出结果进行非周期截断所带来的影响及问题总结。 两个点   为了更好的分析或处理信号,实际应用时需要从频域而非时域的角度观察原信号。但物理意义上只能直接获取信号的时域信息,为了得到信号的频域信息需要利用傅里叶变换这个工具计算出原信号的频谱函数。但对于计算机来说实现这种计算需要面对两个问题: 1.
    TIAN301 2025-01-14 14:15 123浏览
  • 流量传感器是实现对燃气、废气、生活用水、污水、冷却液、石油等各种流体流量精准计量的关键手段。但随着工业自动化、数字化、智能化与低碳化进程的不断加速,采用传统机械式检测方式的流量传感器已不能满足当代流体计量行业对于测量精度、测量范围、使用寿命与维护成本等方面的精细需求。流量传感器的应用场景(部分)超声波流量传感器,是一种利用超声波技术测量流体流量的新型传感器,其主要通过发射超声波信号并接收反射回来的信号,根据超声波在流体中传播的时间、幅度或相位变化等参数,间接计算流体的流量,具有非侵入式测量、高精
    华普微HOPERF 2025-01-13 14:18 514浏览
  • 01. 什么是过程能力分析?过程能力研究利用生产过程中初始一批产品的数据,预测制造过程是否能够稳定地生产符合规格的产品。可以把它想象成一种预测。通过历史数据的分析,推断未来是否可以依赖该工艺持续生产高质量产品。客户可能会要求将过程能力研究作为生产件批准程序 (PPAP) 的一部分。这是为了确保制造过程能够持续稳定地生产合格的产品。02. 基本概念在定义制造过程时,目标是确保生产的零件符合上下规格限 (USL 和 LSL)。过程能力衡量制造过程能多大程度上稳定地生产符合规格的产品。核心概念很简单:
    优思学院 2025-01-12 15:43 557浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦