Linux PowerPC PMU 事件分支(EBB)详解:基于 perf_events 的用户态性能监控中断机制
2026/9/13 20:22:52 网站建设 项目流程

Linux PowerPC PMU 事件分支(EBB)详解:基于 perf_events 的用户态性能监控中断机制

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

导读

Event Based Branches(EBB,事件分支)是 Power 处理器提供的一项硬件特性:当指定事件(如 PMU 计数器溢出)发生时,硬件直接跳转到用户空间预先指定的地址执行处理代码,全程无需陷入内核。本文以 Linux 内核源码树中 Documentation/arch/powerpc/pmu-ebb.rst 为主线,结合 arch/powerpc/perf/core-book3s.c 的 EBB 实现与 tools/testing/selftests/powerpc/pmu/ebb/ 测试套件,系统讲解如何通过 Linuxperf_eventsAPI 配置 Power PMU 生成 EBB,包括事件创建约束、启用判定、处理器编写规范以及 fork 语义,并深入剖析内核层的调度与上下文切换实现。

什么是 EBB:硬件直接分支到用户态地址

EBB(Event Based Branches)允许硬件在特定事件发生时直接分支到用户空间指定的地址执行代码,全程无需陷入内核。文档明确说明,其完整规范定义于 Power ISA v2.07。当前仓库的 arch/powerpc/include/asm/reg.h 中定义了三组与 EBB 直接相关的特殊寄存器:

#define SPRN_EBBHR 804 /* Event based branch handler register */ #define SPRN_EBBRR 805 /* Event based branch return register */ #define SPRN_BESCR 806 /* Branch event status and control register */ #define BESCR_GE 0x8000000000000000ULL /* Global Enable */
  • EBBHR:EBB 处理程序的入口地址(Handler Register),由用户态通过mtspr(SPRN_EBBHR, entry)写入。
  • EBBRR:EBB 返回地址寄存器(Return Register),事件发生后硬件会把被打断的指令地址保存在这里,处理程序用rfebb指令返回。
  • BESCR:分支事件状态与控制寄存器(Branch Event Status and Control Register),其 bit 0 为全局使能位BESCR_GE(Global Enable)。此外还有BESCR_PME(PMU EBB 使能)与BESCR_PMEO(PMU EBB 溢出状态)等位,详见 selftests 中的使用。

需要 EBB 配置的事件类型之一是PMU 异常:当性能计数器溢出时,硬件不是进入内核中断处理,而是把控制权直接交给用户态注册的 EBB 处理程序。这正是本文主题——通过 Linuxperf_eventsAPI 为 Power PMU 配置 EBB。

术语约定:"EBB 事件"

文档约定,全文所称的 "EBB event"(EBB 事件)指的就是一个在attr.config中设置了 EBB 标志的struct perf_event。硬件 PMU 上所有可配置的事件类型都可以作为 EBB 事件。

内核侧对该标志的判定位于 arch/powerpc/perf/core-book3s.c 的is_ebb_event()

static bool is_ebb_event(struct perf_event *event) { /* * This could be a per-PMU callback, but we'd rather avoid the cost. We * check that the PMU supports EBB, meaning those that don't can still * use bit 63 of the event code for something else if they wish. */ return (ppmu->flags & PPMU_ARCH_207S) && ((event->attr.config >> PERF_EVENT_CONFIG_EBB_SHIFT) & 1); }

判定有两个前提:其一是当前 PMU 必须具备 Power ISA 2.07(ARCH 207S)能力标志,其二是事件编码的 bit 63 被置位。UAPI 头文件 arch/powerpc/include/uapi/asm/perf_event.h 给出了该位的确切定义:

/* * We use bit 63 of perf_event_attr.config as a flag to request EBB. */ #define PERF_EVENT_CONFIG_EBB_SHIFT 63

值得注意的是,内核特意把"PMU 是否支持 EBB"与"bit 63 的语义"分开处理:不支持 EBB 的 PMU 仍可自由使用 bit 63 编码其它含义,互不冲突。

设计约束:EBB 只适合自我监控

文档在 Background 一节阐述了 EBB 的几个根本性设计约束:

  1. EBB 投递给当前运行进程:当 PMU EBB 发生时,它被投递给当前正在运行的进程,因此 EBB 只能被程序用于自我监控(self-monitoring)。

  2. 跨进程附加事件是可能的perf_events特性允许(在标准权限检查下)为其它进程创建事件,EBB 事件也不例外。但是,如果目标进程自己没有通过mtspr(BESCR)启用 EBB,EBB 永远不会被投递。因此理论上可以出现这样的场景:进程 A 为自己启用 EBB 但不配置任何事件,之后进程 B 把 EBB 事件附加到进程 A 上,从而向 A 投递 EBB。文档坦言"尚不清楚这种做法是否真的有用"(It's not clear if this is actually useful),这属于 API 能力的自然延伸而非推荐用法。

  3. PMU 全局独占:一旦 PMU 被配置为 EBB 模式,所有 PMU 中断都投递给用户进程。这意味着只要有一个 EBB 事件被调度到 PMU 上,就不能再配置任何非 EBB 事件——EBB 事件无法与常规perf命令或任何其它 perf 事件并发运行。反过来,在使用了 EBB 的进程上运行perf命令是安全的:内核一般会正常调度 EBB 事件,而perf会收到"它的事件无法运行"的通知。

独占与优先级:pinned + exclusive 的互斥实现

EBB 事件与常规事件之间的互斥,是通过perf_events既有的"pinned"(钉住)与"exclusive"(独占)属性实现的:

  • EBB 事件优先于其它事件被调度,除非对方也是 pinned 事件;
  • 若一个 EBB 事件与一个常规事件都是 pinned,则先启用的那个被调度成功,另一个进入错误状态(error state)。

这层语义在内核端由ebb_event_check()(arch/powerpc/perf/core-book3s.c)强制校验,并在"启用 EBB 事件"一节进一步说明其可观测行为。

创建 EBB 事件:严格的属性约束

要请求一个事件按 EBB 方式计数,需满足文档列出的以下规则:

1. 事件编码 bit 63 置位

e->attr.config |= (1ull << 63);

这正是 selftests 中 ebb.c 的event_ebb_init()的实现,与 UAPI 的PERF_EVENT_CONFIG_EBB_SHIFT完全对应。

2. 必须设置 pinned 与 exclusive

EBB 事件必须以 "pinned" 和 "exclusive" 属性创建。若创建的是事件组,只有组领导者(leader)可以设置这两个属性。selftests 中 ebb.c 的event_leader_ebb_init()展示了完整初始化方式:

void event_leader_ebb_init(struct event *e) { event_ebb_init(e); e->attr.exclusive = 1; e->attr.pinned = 1; }

3. 禁止设置的属性

EBB 事件不得设置以下任一属性:

禁止属性含义原因
inherit子进程继承事件EBB 不跨 fork 继承(见后文),且会破坏任务绑定语义
sample_period采样周期EBB 由硬件直接投递,无需内核采样
freq频率模式同上,内核无法介入频率计算
enable_on_execexec 时自动启用EBB 使能必须由用户态显式控制

内核在ebb_event_check()中逐项校验这些规则:

static int ebb_event_check(struct perf_event *event) { struct perf_event *leader = event->group_leader; /* Event and group leader must agree on EBB */ if (is_ebb_event(leader) != is_ebb_event(event)) return -EINVAL; if (is_ebb_event(event)) { if (!(event->attach_state & PERF_ATTACH_TASK)) return -EINVAL; if (!leader->attr.pinned || !leader->attr.exclusive) return -EINVAL; if (event->attr.freq || event->attr.inherit || event->attr.sample_type || event->attr.sample_period || event->attr.enable_on_exec) return -EINVAL; } return 0; }

可见校验共四条:组内 EBB 一致性、必须挂到任务(PERF_ATTACH_TASK)、组长必须 pinned+exclusive、禁止上述属性。任何一条不满足都返回-EINVALevent_attributes_test.c测试程序即围绕这些约束展开正反用例验证(见 event_attributes_test.c)。

4. 必须附加到任务

EBB 事件必须附加到一个任务上,通过向perf_event_open()传入pid值指定,通常传0表示当前任务。这与PERF_ATTACH_TASK的校验一致——没有任务上下文,EBB 无处投递。

5. 组内事件必须统一

事件组内所有成员必须就 EBB 达成一致:要么全部请求 EBB,要么全部不请求。内核在ebb_event_check()中通过比较is_ebb_event(leader)is_ebb_event(event)强制这一点。值得补充的是,从该函数可见:非领导成员可以有自己的config,但组的 pinned/exclusive 属性只看 leader,因此一个事件组中只有 leader 的 config bit 63 决定组属性是否合法。

6. 必须指定 PMC

EBB 事件必须指明要计数所在的 PMC(Performance Monitor Counter),这样用户态才能可靠地判断事件被调度到哪一个 PMC 上,从而在 EBB 处理程序中读取正确的SPRN_PMCn寄存器。selftests 通过ebb_enable_pmc_counting(pmc)(记录到ebb_state.pmc_enable[])与write_pmc()/read_pmc()(ebb.c)体现这一约定。

启用 EBB 事件:ioctl / prctl 与 read() 判定

事件成功 open 之后,需要启用它。文档给出两种途径:

  • ioctl() 接口:对事件 fd 调用PERF_EVENT_IOC_ENABLE
  • prctl() 接口:通过PR_TASK_PERF_EVENTS_ENABLE启用任务的全部事件。

selftests 中 ebb.c 的event_enable()走 ioctl 路径,而其调用序列展示了完整的启用流程:

FAIL_IF(event_open(&event)); ebb_enable_pmc_counting(1); setup_ebb_handler(standard_ebb_callee); ebb_global_enable(); FAIL_IF(event_enable(&event)); if (event_read(&event)) { /* ... EBB 未能调度,通知父进程错误 ... */ return 2; }

这里的关键是文档强调的语义:

由于perf_eventsAPI 的设计,启用事件并不保证它已被调度到 PMU 上。要确认 EBB 事件确实被调度,必须对事件执行一次read()。若read()返回 EOF,说明事件没有被调度,EBB 并未启用。

event_read()正是包装了对事件 fd 的read()调用。这一行为的根源是 EBB 事件同时具备 pinned 与 exclusive 属性:

  • EBB 事件被启用时,会强制把所有非 pinned 事件赶下 PMU,此时启用成功;
  • 但如果 PMU 上已经有一个 pinned 事件,那么本次启用不成功——于是read()返回 EOF,调用方据此得知 EBB 未生效。

一个完整的 EBB 使能序列(来自 selftests)

ebb.c 的ebb_global_enable()setup_ebb_handler()展示了用户在事件创建/启用之外必须完成的硬件准备工作:

void ebb_global_enable(void) { /* Enable EBBs globally and PMU EBBs */ mtspr(SPRN_BESCR, 0x8000000100000000ull); mb(); }

写入BESCR的值0x8000000100000000含义清晰:最高位即BESCR_GE(全局使能),bit 32 即 PMU EBB 使能位(BESCR_PME)。同时,用户态还必须先写好 EBBHR:

void setup_ebb_handler(void (*callee)(void)) { ... /* Ensure ebb_user_func is set before we set the handler */ mb(); mtspr(SPRN_EBBHR, entry); /* Make sure the handler is set before we return */ mb(); }

两个mb()内存屏障分别保证"用户回调先于 handler 地址写入"和"handler 地址写入先于后续操作可见",避免竞态。这些步骤对应文档 Background 中"目标进程通过mtspr(BESCR)启用 EBB"的硬件侧描述。

读取与关闭 EBB 事件

  • 读取:可以对 EBB 事件执行read(),但结果是毫无意义的。因为中断被直接投递给用户进程,内核无法统计该事件,只会返回一个垃圾值(junk value)。read()在此的唯一用途就是上一节所述的"调度判定"(EOF 表示未调度)。
  • 关闭:用完后像常规事件一样close()即可。如果这是最后一个 EBB 事件,PMU 将被解除 EBB 配置,之后不再投递任何 PMU EBB。

selftests 中的 close_clears_pmcc_test.c 专门验证了关闭事件后用户态对 PMC 的读/写权限(MMCR0[PMCC])被正确回收,防止权限残留。

EBB 处理程序:以中断处理器的风格编写用户态代码

EBB 处理程序本质上是普通用户空间代码,但必须以中断处理程序的风格编写:进入 handler 时所有寄存器都是"活的"(possibly live),在调用任何其它代码之前必须先把它们保存起来。具体怎么保存由程序自行决定;对 C 程序而言,一个相对简单的方案是在栈上建立一个中断帧(interrupt frame)并保存寄存器。

仓库中的 ebb_handler.S 给出了完整的参考实现,其框架极具教学价值:

栈布局设计(注释中绘制的示意图):在用户栈上依次分配 Red zone / ABI Gap、VSR0–VSR63 与 VSCR/FSCR、GPR r0–r31 及 XER/CTR/LR/CCR 保存区、调用者帧,总计:

#define SAVE_AREA ((NR_GPR + NR_SPR) * 8 + (NR_VSR * 16)) #define CALLER_FRAME 112 #define STACK_FRAME (ABIGAP + SAVE_AREA + CALLER_FRAME)

入口处理stdu r1,-STACK_FRAME(r1)压栈建帧,然后依次保存 GPR(SAVE_GPR(n))、XER/CTR/LR/CCR,再通过stxvd2x保存全部 64 个 VSR 向量寄存器与 FSCR/VSCR 状态位。之后故意用lis n,0xaaaa破坏(TRASH)除 r1、r13 外的所有 GPR——这验证了 EBB handler 不能依赖任何调用者寄存器的存活值,是"所有寄存器都可能是活的"的直接体现。

调用用户回调:恢复 TOC 指针(RESTORE_TOC)后调用 C 侧钩子ebb_hook,而 ebb.c 中:

void ebb_hook(void) { if (ebb_user_func) ebb_user_func(); }

将控制权转交给用户注册的standard_ebb_callee之类的回调。

返回路径:按相反顺序恢复 FSCR、VSCR、全部 VSR、XER/CTR/LR/CCR、GPR,最后addi r1,r1,STACK_FRAME撤销栈帧,并以特殊指令返回:

#define RFEBB .long 0x4c000924

0x4c000924rfebb(Return From Event Based Branch)指令编码。它从 EBBRR 恢复被打断的指令流,这正是 EBB 与普通中断最大的不同——由硬件完成上下文切换的返回,无需内核参与

standard_ebb_callee(ebb.c)展示了处理逻辑的典型写法:读SPRN_BESCR检查BESCR_PMEO(PMU EBB 溢出位),无溢出则记为 spurious(伪中断);否则递增ebb_count、读取 MMCR0、遍历所有启用的 PMC 用count_pmc()累加计数值,最后调用reset_ebb()复位:

void reset_ebb_with_clear_mask(unsigned long mmcr0_clear_mask) { u64 val; /* 2) clear MMCR0[PMAO] - docs say BESCR[PMEO] should do this */ /* 3) set MMCR0[PMAE] - docs say BESCR[PME] should do this */ val = mfspr(SPRN_MMCR0); mtspr(SPRN_MMCR0, (val & ~mmcr0_clear_mask) | MMCR0_PMAE); /* 4) clear BESCR[PMEO] */ mtspr(SPRN_BESCRR, BESCR_PMEO); /* 5) set BESCR[PME] */ mtspr(SPRN_BESCRS, BESCR_PME); /* 6) rfebb 1 - done in our caller */ }

这个序列恰好对应硬件手册对 EBB handler 退出前的复位要求:清除MMCR0[PMAO]/FC(冻结标志)、重新置位MMCR0[PMAE]、清BESCR[PMEO]、置BESCR[PME],随后由汇编层执行rfebb

fork 语义:EBB 不跨进程继承

文档明确两条 fork 规则:

  1. 事件不继承:EBB 事件不随fork()继承。子进程若想使用 EBB,必须为自己重新打开事件。
  2. 寄存器状态清零BESCR/EBBHR/EBBRR中的 EBB 状态在fork()时被清除。

selftests 中 fork_cleanup_test.c 与 ebb_on_child_test.c 分别验证了"fork 后子进程 EBB 状态被清理"与"子进程自愿重新初始化 EBB"两条路径;ebb_on_willing_child_test.c 则测试了配合协作的子进程场景。

内核调度实现:pinned/exclusive 如何在 PMU 上落地

文档描述的互斥语义,在 arch/powerpc/perf/core-book3s.c 中有完整的调度期实现。

首次挂入时的used_ebb标记——ebb_event_add()

static void ebb_event_add(struct perf_event *event) { if (!is_ebb_event(event) || current->thread.used_ebb) return; /* * IFF this is the first time we've added an EBB event, set * PMXE in the user MMCR0 so we can detect when it's cleared by * userspace. We need this so that we can context switch while * userspace is in the EBB handler (where PMXE is 0). */ current->thread.used_ebb = 1; current->thread.mmcr0 |= MMCR0_PMXE; }

首次添加 EBB 事件时,内核在用户态可见的 MMCR0 中置位MMCR0_PMXE(性能监控异常使能),目的是在用户态处于 EBB handler(PMXE 为 0)时也能正确完成上下文切换。

调度切入ebb_switch_in()——当 PMU 以 EBB 模式切入时,向 MMCR0 追加多个用户态可访问位(reg.h 定义):

/* Enable EBB and read/write to all 6 PMCs and BHRB for userspace */ mmcr0 |= MMCR0_EBE | MMCR0_BHRBA | MMCR0_PMCC_U6;
  • MMCR0_EBE(0x00100000):Event based branch enable,EBB 主开关;
  • MMCR0_BHRBA(0x00200000):允许用户态访问 BHRB(分支历史记录缓冲);
  • MMCR0_PMCC_U6(0x00080000):PMC1–6 对用户态可读写。

随后把用户态保存的 SIAR/SIER/SDAR 与 MMCR2 合并写回(mtspr),并为 POWER10/12 等 PPMU_ARCH_31 平台追加 MMCR3/SIER2/SIER3。注释明确合并语义:用户 MMCR2 只能置位(冻结计数器)而不能清位,若任务需要自行清位解冻计数器,则不应在事件上设置exclude_xxx而应由用户态全权管理 MMCR2。

调度切出ebb_switch_out()——当MMCR0_EBE置位时,把 SIAR/SIER/SDAR、MMCR0(仅保留MMCR0_USER_MASK = FC|PMXE|PMAO位)、MMCR2 等保存回current->thread,供下次切回时恢复:

#define MMCR0_USER_MASK (MMCR0_FC | MMCR0_PMXE | MMCR0_PMAO) #define MMCR2_USER_MASK 0x4020100804020000UL /* (FC1P|FC2P|FC3P|FC4P|FC5P|FC6P) */ #define SIER_USER_MASK 0x7fffffUL

这些掩码表明:EBB 模式下,用户态只对 MMCR0/MMCR2/SIER 的用户相关子集拥有可见性,内核与用户态各自维护自己的那部分状态,上下文切换时按位合并——这是 EBB 能在零内核介入下工作的关键机制。

POWER8E 硬件缺陷补偿(pmao_restore_workaround——该函数处理 POWER8E 的一个已知 PMU 上下文切换缺陷:计数器溢出后 PMXE 被清、MMCR0 置 FC/PMAO,但 PMU 异常可能延迟到达;若在此之前发生上下文切换,异常会丢失。解决方式是检测PMAO置位而PMAO_SYNC未置位的状态(EBB 场景还要求BESCR_GE置位),通过让 PMC6 逼近溢出制造一个新的待决异常,待返回用户态时再投递。这一实现细节表明:EBB 在真实硬件上的可靠性依赖内核与硬件的协同补偿,并非简单的"全硬件路径"。

测试验证:selftests 中的 EBB 覆盖矩阵

内核为 EBB 提供了完整的自测套件,位于 tools/testing/selftests/powerpc/pmu/ebb/,Makefile 定义了全部 25 个测试程序,覆盖了本文讨论的几乎所有语义:

测试程序验证要点
reg_access_test/regs_access_pmccext_test用户态对 EBB 相关 SPR 与 PMC 的读写访问
event_attributes_test创建 EBB 事件的属性约束(pinned/exclusive、禁用属性等正反用例)
cycles_test最基本的 EBB 周期计数
cycles_with_freeze_test与计数器冻结(MMCR0[FC])交互
cycles_with_mmcr2_test用户态 MMCR2 冻结位语义
pmc56_overflow_testPMC5/6 溢出路径
ebb_vs_cpu_event_test/cpu_event_vs_ebb_testEBB 与 CPU 级事件的互斥
cpu_event_pinned_vs_ebb_testEBB 与 pinned CPU 事件同时请求时的仲裁(先启用者胜)
task_event_vs_ebb_test/task_event_pinned_vs_ebb_testEBB 与任务级事件的互斥
multi_counter_test多 PMC 同时计数
multi_ebb_procs_test多进程 EBB
pmae_handling_testPMAE/PMAO 处理
close_clears_pmcc_testclose 后回收用户态 PMC 权限
instruction_count_test指令计数事件
fork_cleanup_testfork 后 EBB 状态清理
ebb_on_child_test/ebb_on_willing_child_test子进程 EBB 场景
back_to_back_ebbs_test连续 EBB 触发
lost_exception_test异常丢失场景
no_handler_test未注册 handler 的行为

测试统一以 64 位编译(CFLAGS += -m64),并显式关闭 PIE(-no-pie),因为 ebb_handler.S 中的汇编代码与绝对地址处理依赖非 PIE 布局。运行这些测试前,可通过ebb_is_supported()(检查PPC_FEATURE2_EBBhwcap2 位,见 ebb.c)确认硬件支持 EBB——EBB 至少要求 POWER8 及以后的处理器。这一 hwcap 检查与内核侧PPMU_ARCH_207S能力标志(is_ebb_event)互为印证:文档所述的 Power ISA v2.07 特性在软件栈中由这两个地方共同把关。

小结:EBB 的完整工作链路

综合文档与源码,一条 PMU EBB 的完整生命周期可归纳为:

  1. 用户态初始化:设置attr.configbit 63(PERF_EVENT_CONFIG_EBB_SHIFT),leader 设置pinned+exclusive,组内统一 EBB 意愿,指定 PMC,通过perf_event_open()(pid=当前任务)创建任务级事件——内核ebb_event_check()校验全部约束;
  2. 用户态硬件准备mtspr(SPRN_EBBHR, handler)注册处理程序,mtspr(SPRN_BESCR, GE|PME)全局使能;
  3. 启用与确认:ioctl/prctl 启用事件后,read()返回非 EOF 才表明事件已调度上 PMU(pinned/exclusive 互斥仲裁);
  4. 事件触发:PMC 溢出时硬件直接分支到 EBBHR 指向的用户态地址,处理程序(如 ebb_handler.S 的框架)保存全部寄存器、读取 BESCR/PMC、复位 MMCR0/BESCR 后以rfebb返回;
  5. 内核兜底:调度切入/切出时通过ebb_switch_in()/ebb_switch_out()按位合并用户态与内核态的 MMCR0/MMCR2/SIER 状态,并以pmao_restore_workaround()补偿 POWER8E 硬件缺陷;
  6. 清理close()最后一个 EBB 事件后 PMU 退出 EBB 模式;fork()不会继承任何 EBB 状态,子进程需重新初始化。

EBB 的价值在于把性能监控中断的投递路径从"内核中断 → 采样 → 信号/通知"缩短为"硬件 → 用户态 handler",为低延迟、高精度的用户态自监控(例如运行时剖析器、硬件事件驱动的自适应优化)提供了 Linux 下的官方支持路径。对开发者而言,pmu-ebb.rst 是 API 层面的权威指南,而 core-book3s.c 与 selftests 套件则分别提供了实现级与可运行示例级的完整参照。

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询