You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ARM64架构下PMCCNTR频率与CPU频率不匹配的原因及解决方法

树莓派3B+ PMCCNTR频率与CPU频率不匹配的问题解决

问题场景

在树莓派3B+上尝试测量单条指令的CPU周期,已通过以下内核态代码启用用户态对PMCCNTR的访问:

static inline u32 armv8pmu_pmcr_read(void)
{
    u64 val=0;
    asm volatile("mrs %0, pmcr_el0" : "=r" (val));
    return (u32)val;
}

static inline void armv8pmu_pmcr_write(u32 val)
{                                                                                                     
    val &= 0x3f;
    isb();
    asm volatile("msr pmcr_el0, %0" : : "r" ((u64)val));
}

static inline void enable_cpu_counters(void* data)
{
    asm volatile("msr pmuserenr_el0, %0" : : "r"(0xf));
    armv8pmu_pmcr_write((1 << 6) | (1 << 0));
    asm volatile("msr PMCNTENSET_EL0, %0" :: "r" ((u32)(1 << 31)));
    armv8pmu_pmcr_write(armv8pmu_pmcr_read() | (1 << 0) | (1 << 6));
    asm volatile("isb");
    asm volatile("msr pmccfiltr_el0, %0" :: "r" (1 << 27));
    printk("\nCPU:%d ", smp_processor_id());
}

static inline void enable_pmu_pmccntr(void)
{
    on_each_cpu(enable_cpu_counters, NULL, 1);
    printk(KERN_INFO "Enable Access PMU Initialized");
}

同时通过config.txt固定CPU频率为1GHz:

arm_freq=1000
over_voltage=4
core_freq=500
force_turbo=1

使用以下测试代码验证时,发现计数器增长值与cntfeq(系统计数器频率)一致,而非CPU的1GHz:

int main()
{
    u64 begin,end;
    
    begin = arch_counter_get_cntpct();
    sleep(1);
    end= arch_counter_get_cntpct();
    
    printf("The count is %llu.\n",end-begin);

    return 0;
}

原因分析

  1. 测试代码混淆了计数器类型
    arch_counter_get_cntpct()读取的是系统计数器(CNTPCT_EL0),这个计数器的频率由cntfeq定义,是固定的系统时钟频率,和CPU核心频率无关。需要读取的是PMU的周期计数器PMCCNTR_EL0,而非系统计数器。

  2. PMCCNTR默认时钟源为系统时钟
    树莓派3B+搭载的Cortex-A53架构中,PMCCNTR的时钟源由PMCR_EL0寄存器的D位(第3位)控制:

    • D=0(默认):计数器使用系统时钟(与cntfeq一致)
    • D=1:计数器使用CPU核心时钟(与arm_freq一致)
      当前代码未配置D位,因此PMCCNTR默认使用系统时钟,即使正确读取,计数频率也不会匹配CPU频率。

解决方法

1. 修正测试代码:读取PMCCNTR而非系统计数器

在用户态实现读取PMCCNTR_EL0的函数:

static inline u64 read_pmccntr(void)
{
    u64 val;
    // 读取PMCCNTR_EL0寄存器
    asm volatile("mrs %0, pmccntr_el0" : "=r"(val));
    return val;
}

同时替换测试代码中的计数器读取函数,并且避免使用sleep()(sleep会让CPU进入休眠状态,若使用CPU核心时钟,计数器会停止计数),改用忙等测试:

int main()
{
    u64 begin, end;
    
    begin = read_pmccntr();
    // 忙等一段时间,确保CPU保持运行
    for (volatile int i = 0; i < 100000000; i++);
    end = read_pmccntr();
    
    printf("The count is %llu.\n", end - begin);
    return 0;
}

2. 修改PMU配置,让PMCCNTR使用CPU核心时钟

修改enable_cpu_counters函数中armv8pmu_pmcr_write的参数,添加D位(1<<3)的配置:

static inline void enable_cpu_counters(void* data)
{
    asm volatile("msr pmuserenr_el0, %0" : : "r"(0xf));
    // 配置PMCR_EL0:设置D位(使用CPU核心时钟)、E位(启用计数器)、C位(复位计数器)
    armv8pmu_pmcr_write((1 << 6) | (1 << 3) | (1 << 0));
    asm volatile("msr PMCNTENSET_EL0, %0" :: "r" ((u32)(1 << 31)));
    // 再次写入时保留D位
    armv8pmu_pmcr_write(armv8pmu_pmcr_read() | (1 << 3) | (1 << 0) | (1 << 6));
    asm volatile("isb");
    asm volatile("msr pmccfiltr_el0, %0" :: "r" (1 << 27));
    printk("\nCPU:%d ", smp_processor_id());
}

这里(1<<3)就是设置D位,让PMCCNTR使用CPU核心时钟。

3. 验证配置生效

重新编译内核模块并加载,运行测试代码,此时计数器增长值应接近CPU频率(1GHz)乘以忙等时间,比如100000000次循环大约对应0.1秒,计数器增长约100M左右。


内容的提问来源于stack exchange,提问作者Lqs66

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 18:34:50