x86架构下,我们要想在用户态通过定时器方式来对代码执行时间做衡量可以使用Time Stamp Counter(TSC)寄存器,可精确到CPU Cycle级别。
由于ARM架构支持的是完全不同的指令集,因此获取TSC的方法存在差异。近些年来随着arm架构的广泛使用,以前基于x86架构运行的应用,现在要跑在ARM架构下,当运行的应用需要获取TSC时,则需要汇编级的代码移植,这对上层应用编写者提出了不小的挑战。为此,本文分享Arm64架构下如何获取TSC的方法,以方便大家移植使用。
首先,我们介绍一种利用Arm64架构的System counter来实现提供TSC的方法。System counter是Arm64下独立于CPU core的计数器,在系统上电时,会给此计数器设置固定的频率。一个映射system counter计数器内容的寄存器为CNTVCT_EL0,可在用户态下读取此寄存器获取counter值。而CNTFRQ_EL0保存的是counter的频率值。
以c/c++语言为例给出实现代码:
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();
}
通过以上函数实现获取counter值及频率值,我们是可以实现对某些任务做相应的时间衡量的。但是system counter的精度一般不会超过100MHz,一般是达不到CPU cycle级别的精度。
考虑到利用system counter来实现提供TSC的方法可能满足不了某些任务的需求,我们给出方法二来实现提供TSC并能达到CPU cycle级别的精度要求。此方法是借助PMU系列寄存器中的PMCCNTR_EL0,读取此寄存器就可以知道当前CPU已运行了多少 cycle。
以c/c++语言为例给出实现代码:
static inline uint64_t
arm64_pmccntr(void)
{
uint64_t tsc;
asm volatile("mrs %0, pmccntr_el0" : "=r"(tsc));
return tsc;
}
static inline uint64_t
rdtsc(void)
{
return arm64_pmccntr();
}
通过以上函数实现,我们获取的TSC可以达到CPU cycle级别的精度。但是Linux系统下,用户态默认没有开启对PMCCNTR_EL0寄存器的读权限,需要通过内核态使能后才能读取。
以c/c++语言为例给出核心使能代码:
static void enable_pmu_pmccntr(void)
{
u64 val = 0;
asm volatile("msr pmintenset_el1, %0" : : "r" ((u64)(0 << 31)));
asm volatile("msr pmcntenset_el0, %0" :: "r" ((u64)(1 << 31)));
asm volatile("msr pmuserenr_el0, %0" : : "r"((u64)(1 << 0) | (u64)(1 << 2)));
asm volatile("mrs %0, pmcr_el0" : "=r" (val));
val |= ((u64)(1 << 0) | (u64)(1 << 2));
isb();
asm volatile("msr pmcr_el0, %0" : : "r" (val));
}
详细使能步骤可参考如下链接:
[https://ilinuxkernel.com/?p=1755]