读取PMCCNTR_EL0寄存器遇非法指令错误,疑为访问频率限制?
ARM平台直接读取PMCCNTR_EL0触发非法指令问题排查
常见诱因
1. 进程跨CPU调度导致权限缺失
ARM的PMUSERENR_EL0是每CPU独立寄存器,内核只会为perf_event_open绑定的CPU开启用户态访问权限。你调用perf_event_open时指定cpu=-1,允许进程在任意CPU运行,一旦进程被调度到未配置权限的CPU,执行mrs读取PMCCNTR_EL0就会触发非法指令。
2. perf事件初始化失败未被检测
当系统PMU资源耗尽(比如同时开启的硬件事件数超过CPU支持的计数器上限),perf_event_open会返回-1。如果没检查返回值就继续执行后续的ioctl启用操作,事件实际未生效,用户态无权限访问PMU寄存器,自然报错。
3. 内核权限意外回收
部分内核版本中,PMU计数器溢出、事件被临时挂起,或者资源回收逻辑存在缺陷时,可能会意外清除PMUSERENR_EL0的用户访问位,导致后续读取失败。
修复方案
1. 绑定进程到固定CPU
通过CPU亲和性把进程锁在单个CPU上,同时在perf_event_open时指定该CPU,确保始终在已配置权限的CPU运行:
#include <sched.h> // 获取当前CPU并绑定 int target_cpu = sched_getcpu(); cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(target_cpu, &cpuset); if (sched_setaffinity(0, sizeof(cpuset), &cpuset) == -1) { perror("sched_setaffinity failed"); exit(EXIT_FAILURE); } // 打开perf事件时指定目标CPU int fd = syscall(SYS_perf_event_open, &attr, 0, target_cpu, -1, 0);
2. 严格校验系统调用返回值
在perf_event_open和所有ioctl调用后立即检查错误,避免在事件未正确启用时读取寄存器:
int fd = syscall(SYS_perf_event_open, &attr, 0, target_cpu, -1, 0); if (fd == -1) { perror("perf_event_open failed"); exit(EXIT_FAILURE); } if (ioctl(fd, PERF_EVENT_IOC_RESET, 0) == -1) { perror("PERF_EVENT_IOC_RESET failed"); close(fd); exit(EXIT_FAILURE); } if (ioctl(fd, PERF_EVENT_IOC_ENABLE, 0) == -1) { perror("PERF_EVENT_IOC_ENABLE failed"); close(fd); exit(EXIT_FAILURE); }
3. 复用perf事件文件描述符
如果是循环执行性能分析,不要每次都关闭并重新打开fd,复用同一个fd可以避免频繁创建/销毁事件带来的权限波动:
// 初始化一次事件 static struct perf_event_attr attr = { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CPU_CYCLES, .exclude_kernel = 1, .exclude_hv = 1, .config1 = 3, // 开启用户访问权限 }; int fd = syscall(SYS_perf_event_open, &attr, 0, target_cpu, -1, 0); // 校验fd... // 循环执行分析 for (int i = 0; i < YOUR_LOOP_COUNT; i++) { ioctl(fd, PERF_EVENT_IOC_RESET, 0); ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); // 待性能分析的代码 read_register_directly(); ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); } // 最后统一关闭fd close(fd);
4. 提前校验PMU权限状态
在读取PMCCNTR_EL0前,先读取PMUSERENR_EL0确认权限是否正常,提前排查问题:
uint64_t read_pmuserenr() { uint64_t val; asm volatile("mrs %0, PMUSERENR_EL0" : "=r"(val)); return val; } // 在读取计数器前检查权限 uint64_t enr = read_pmuserenr(); if ((enr & 0x3) != 0x3) { // 检查USER_EN和CR_EN位是否都置位 fprintf(stderr, "PMU user access disabled: 0x%lx\n", enr); // 可尝试重新启用事件或退出 }
内容的提问来源于stack exchange,提问作者Kron
相关产品推荐
相关产品推荐

