赞
踩
在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。
使能TSC需要在内核权限下,因此有两种方式,一种为module的方式,另一种为将代码块移植入kernel,一下对两种方式进行介绍;
/* 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_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");
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()接口进行调用,如下图所示:
#define _GNU_SOURCE #include <sched.h> #include <stdint.h> #include <inttypes.h> #include <stdio.h> #include <string.h> #include <sys/time.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #define CPU_MASK 0 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(); } // 进程亲和-OK #if 0 void 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 // 使用线程方式绑定核0 static 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); } }
a. 在多核系统中,每个CPU有自己独立的PMU寄存器,并且每个CPU的cycle值是不一样的,所以在获取cycle值时,前后应该位于同一个线程,该线程需要亲和到某个CPU上;
b. 如果将使能代码段放于内核代码块时,需要注意enable_pmu_pmccntr接口调用位置,应该在内核完全启动后进行调用,例如在start_kernel调用时,只使能了主核的cycle获取功能,当应用层将线程绑定到其他核时,获取cycle值出现指令非法问题;
https://ilinuxkernel.com/?p=1755
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。