1 背景介绍

在x86架构中,我们对Time Stamp Counter (TSC) 寄存器非常熟悉,通过这个寄存器对代码执行时间的衡量可精确到CPU Cycle级别。
但在ARM/ARMv8/aarch64架构中,并没有与x86 TSC对应的寄存器和直接对应的汇编指令rdtsc。
若想在ARMv8架构中,统计计算代码执行时间达到CPU Cycle级别,也需要读取类似x86的TSC寄存器。在ARMv8中,有Performance Monitors Control Register系列寄存器,其中PMCCNTR_EL0就类似于x86的TSC寄存器。本文介绍Linux下读取ARM TSC方法。
读取这个PMCCNTR_EL0寄存器值,就可以知道当前CPU已运行了多少Cycle。但在ARM下读取CPU Cycle和x86有所不同:
x86用户态代码可以随便读取TSC值。但在ARM,默认情况是用户态是不可以读的,需要在内核态使能后,用户态才能读取。
开关在由寄存器PMCR_EL0控制。实际上这个寄存器控制整个PMU寄存器在用户态是否可读写,不仅仅是PMCCNTR_EL0。

2 PMU读写使能

使能TSC需要在内核权限下,因此有两种方式,一种为module的方式,另一种为将代码块移植入kernel,一下对两种方式进行介绍;

2.1 module方式使能

/* Enable user-mode ARM performance counter access. */                                                                           #include                                                       #include                                                       #include  #define ARMV8_PMCR_MASK         0x3f                                                                    #define ARMV8_PMCR_E            (1 << 0) /* Enable all counters */#define ARMV8_PMCR_LC           (1 << 6) /* Cycle Counter 64bit overflow*/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 &= ARMV8_PMCR_MASK;                                                                         isb();                                                                                          asm volatile("msr pmcr_el0, %0" : : "r" ((u64)val));                                            }        static inline  long long armv8_read_CNTPCT_EL0(void){   long long val;   asm volatile("mrs %0, CNTVCT_EL0" : "=r" (val));   return val;}static void                                                                                            enable_cpu_counters(void* data)                                                                         {                                                          asm volatile("msr pmuserenr_el0, %0" : : "r"(0xf));    armv8pmu_pmcr_write(ARMV8_PMCR_LC|ARMV8_PMCR_E);                                                          asm volatile("msr PMCNTENSET_EL0, %0" :: "r" ((u32)(1<<31)));    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)                                                                                              {/*u64 cval;u32 val;isb();asm volatile("mrs %0, PMCCNTR_EL0" : "=r"(cval));printk("\nCPU Cycle count:%llu \n", cval);asm volatile("mrs %0, PMCNTENSET_EL0" : "=r"(val));printk("PMCNTENSET_EL0:%X ", val);asm volatile("mrs %0, PMCR_EL0" : "=r"(val));printk("\nPMCR_EL0 Register:%X ", val);*/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");MODULE_AUTHOR("alan");

2.2 将代码块添加至内核

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));    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");}

将以上代码段放入init/main.c文件中,在init/main.c文件的kernel_init()接口进行调用,如下图所示:

3 应用层调用

#define _GNU_SOURCE#include #include #include #include #include #include #include #include #include #define CPU_MASK 0static 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();}// 进程亲和-OK#if 0void cpu_affinty_set(void){    cpu_set_t mask;                 //CPU掩码    CPU_ZERO(&mask);    //初始化set集,将set置为空            CPU_SET(CPU_MASK, &mask);  //将本进程绑定到CPU0上    if (sched_setaffinity(0, sizeof(mask), &mask) == -1)     {        printf("Set CPU affinity failue, ERROR:%s\n", strerror(errno));    }}int main(void){cpu_affinty_set();while (1){uint64_t val = rdtsc();printf("rdtsc:%lu\n", val);usleep(1000);}}#endif// 使用线程方式绑定核0static int app_set_affinity(int coreid){cpu_set_t cpuset;CPU_ZERO(&cpuset);CPU_SET(coreid, &cpuset);return pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);}static void* get_cycle_loop(void *arg){if (app_set_affinity(0)){perror("app_set_affinity failed");}while (1){uint64_t val = rdtsc();printf("rdtsc:%lu\n", val);usleep(1000);}}int main(void){pthread_t tid;if (pthread_create(&tid, NULL, get_cycle_loop, NULL)){perror("pthread_create failed");}while (1){sleep(10);}}

4 注意事项

a. 在多核系统中,每个CPU有自己独立的PMU寄存器,并且每个CPU的cycle值是不一样的,所以在获取cycle值时,前后应该位于同一个线程,该线程需要亲和到某个CPU上;
b. 如果将使能代码段放于内核代码块时,需要注意enable_pmu_pmccntr接口调用位置,应该在内核完全启动后进行调用,例如在start_kernel调用时,只使能了主核的cycle获取功能,当应用层将线程绑定到其他核时,获取cycle值出现指令非法问题;

5 参考文章

https://ilinuxkernel.com/?p=1755