腾讯网站建设分析,沈阳单页网站制作,wordpress 主题末班,仿wordpress站博主未授权任何人或组织机构转载博主任何原创文章#xff0c;感谢各位对原创的支持#xff01; 博主链接 本人就职于国际知名终端厂商#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作#xff0c;目前牵头6G算力网络技术标准研究。 博客…博主未授权任何人或组织机构转载博主任何原创文章感谢各位对原创的支持 博主链接 本人就职于国际知名终端厂商负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作目前牵头6G算力网络技术标准研究。 博客内容主要围绕 5G/6G协议讲解 算力网络讲解云计算边缘计算端计算 高级C语言讲解 Rust语言讲解 文章目录 如何在ARM上实现x86的rdtsc()函数一、使用ARMv8提供的独立定时器CNTVCT_EL0二、使用ARMv8的PMU计数器PMCCNTR_EL02.1 关键寄存器介绍PMCCNTR_EL0Performance Monitors Cycle Count RegisterPMCR_EL0Performance Monitors Control RegisterPMUSERENR_EL0Performance Monitors User Enable RegisterPMCNTENCLR_EL0Performance Monitors Count Enable Clear register 2.2 内核使能代码2.3 用户态代码2.4 测试时遇到的问题 如何在ARM上实现x86的rdtsc()函数 一、使用ARMv8提供的独立定时器CNTVCT_EL0 System counter是Arm64下独立于CPU core的计数器在系统上电时会给此计数器设置固定的频率。一个映射System counter计数器内容的寄存器为CNTVCT_EL0可在用户态下读取此寄存器获取counter值。而CNTFRQ_EL0保存的是counter的频率值详细内容参考《【ARMv8】通用定时器总结》。通过下面的函数实现获取counter值及频率值
static inline uint64_t
arm64_cntvct(void)
{ uint64_t tsc; asm volatile(mrs %0, cntvct_el0 : r (tsc)); return tsc;
} static inline uint64_t
arm64_cntfrq(void)
{ uint64_t freq; asm volatile(mrs %0, cntfrq_el0 : r (freq)); return freq;
} static inline uint64_t
rdtsc(void)
{return arm64_cntvct();
}但是System counter的精度从Armv8.0到Armv8.5范围通常在1-50MHz从Armv8.6开始以1GHz的固定频率递增。虽然1GHz的频率已经足够高了但是还是达不到CPU cycle级别的精度。 二、使用ARMv8的PMU计数器PMCCNTR_EL0 在ARMv8中有Performance Monitors Control Register系列寄存器其中PMCCNTR_EL0就类似于x86的TSC寄存器。但是如果想在用户态访问这些寄存器需要在内核代码中开启PMU用户态访问开关。
2.1 关键寄存器介绍
PMCCNTR_EL0Performance Monitors Cycle Count Register
保存了处理器周期计数器的值其结构如下
PMCR_EL0Performance Monitors Control Register
PMU配置寄存器其结果如下 其中和我们关系密切的几个参数含义
LC设置为1表示开启64bit的周期计数器否则使用32bit的计数器32bit的已经摒弃D设置为1表示每64个时钟周期计时器累加一次已经摒弃否则每个时钟周期计数器累加一次C设置为1表示重置计数器E设置为1表示开启计数器PMCCNTR_EL0
PMUSERENR_EL0Performance Monitors User Enable Register
用于开启或关闭用户态下是否可以访问PMU寄存器相关结构如下 其中和我们关系密切的几个参数含义
ER设置为1表示用户态下可以读写PMU寄存器否则不可以读写EN设置为1表示用户态软件可以访问所有PMU特定的寄存器
PMCNTENCLR_EL0Performance Monitors Count Enable Clear register
设置启用的计数器和事件计数器相关结构如下 其中和我们关系密切的几个参数含义
C设置为1表示启用PMCCNTR_EL0计数器
2.2 内核使能代码
/* * Enable user-mode ARM performance counter access. */
#include linux/kernel.h
#include linux/module.h
#include linux/smp.h #define ARMV8_PMCR_MASK 0x3f
#define ARMV8_PMCR_E (1 0) /* Enable all counters */
#define ARMV8_PMCR_P (1 1) /* Reset all counters */
#define ARMV8_PMCR_C (1 2) /* Cycle counter reset */
#define ARMV8_PMCR_D (1 3) /* CCNT counts every 64th cpu cycle */
#define ARMV8_PMCR_X (1 4) /* Export to ETM */
#define ARMV8_PMCR_DP (1 5) /* Disable CCNT if non-invasive debug*/
#define ARMV8_PMCR_LC (1 6) /* Cycle Counter 64bit overflow*/
#define ARMV8_PMCR_N_SHIFT 11 /* Number of counters supported */
#define ARMV8_PMCR_N_MASK 0x1f #define ARMV8_PMUSERENR_EN_EL0 (1 0) /* EL0 access enable */
#define ARMV8_PMUSERENR_CR (1 2) /* Cycle counter read enable */
#define ARMV8_PMUSERENR_ER (1 3) /* Event counter read enable */ static inline u32 armv8pmu_pmcr_read(void)
{ u64 val0; asm volatile(mrs %0, pmcr_el0 : r (val)); return (u32)val;
}
static inline void armv8pmu_pmcr_write(u32 val)
{ val ARMV8_PMCR_MASK; isb(); asm volatile(msr pmcr_el0, %0 : : r ((u64)val));
} static void
enable_cpu_counters(void* data)
{ u32 val0; asm volatile(msr pmuserenr_el0, %0 : : r(0xf)); asm volatile(msr PMCNTENSET_EL0, %0 :: r ((u32)(131)));armv8pmu_pmcr_write(armv8pmu_pmcr_read() | ARMV8_PMCR_E|ARMV8_PMCR_LC); printk(\nCPU:%d , smp_processor_id());
} static void
disable_cpu_counters(void* data)
{ printk(KERN_INFO \ndisabling user-mode PMU access on CPU #%d, smp_processor_id()); /* Program PMU and disable all counters */ armv8pmu_pmcr_write(armv8pmu_pmcr_read() |~ARMV8_PMCR_E); asm volatile(msr pmuserenr_el0, %0 : : r((u64)0));
} static int __init
init(void)
{ isb();on_each_cpu(enable_cpu_counters, NULL, 1); printk(KERN_INFO Enable Access PMU Initialized); return 0;
} static void __exit
fini(void)
{ on_each_cpu(disable_cpu_counters, NULL, 1); printk(KERN_INFO Access PMU Disabled);
} module_init(init);
module_exit(fini);
module_license(GPL);2.3 用户态代码
#include stdio.h#define u64 unsigned long long
#define isb() asm volatile(isb : : : memory)static inline u64 arch_counter_get_cntpct(void)
{u64 cval;isb();asm volatile(mrs %0, PMCCNTR_EL0 : r(cval));return cval;
}2.4 测试时遇到的问题
可能有同学会用下面的代码测试定时精度
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;
}但是会发现使用统计的计数值与CPU当前的始终频率计算后时间不是1s。这是因为Linux的省电功能导致的sleep会使当前进程让出CPU如果此时CPU任务队列中没有任务就会进入低功耗例如WFI甚至offline如果进入上述状态PMU计数器就会停止计数导致计数值不准确。
毕竟PMU是为调式使用的如果此时CPU没有任务也确实没有必要继续统计了。所以使用PMU寄存器计数是不应该有主动让出CPU的行为可能会导致计数不准确。
可以尝试关闭省电模式
echo 1 /sys/devices/system/cpu/cpuX/cpuidle/stateY/disable