替换x86 rdtsc汇编指令_鲲鹏通用_TaiShan服务器代码移植参考_移植相关问题处理_嵌入式汇编类问题_华为云 (huaweicloud.com)
替换rdtsc汇编指令_鲲鹏处理器_处理器指令替换案例集_源码修改类案例_华为云 (huaweicloud.com)
http://ilinuxkernel.com/?p=1755
GitHub - jerinjacobk/armv8_pmu_cycle_counter_el0: ARMv8 performance monitor from userspace
寄存器速查ZhouHehe's Home (hehezhou.cn)
在编程时,我们往往需要获取高精度的时间点,尤其是在进行性能测试的时候,当测试的代码本身执行的时间就很短的时候,用低精度的时间戳是很难进行测试的。下面将总结一下对于不同cpu在这方面的实现。
(1)x86一种读时间戳实现方式
在x86架构中,由于cpu的tsc和系统的tsc以及频率都是采用统一的频率,例如有些都是采用2500MHz。 本代码来源于pcie-lat项目。
下面这种方式计算stc是最准确的,连续两条指令之间大概在32个cycle之间。至于关于x86的寄存器以及内部实现原理希望哪位大佬帮忙普及一下,
实在是对x86的汇编以及寄存器一无所知。
#define get_tsc_top(high, low) \ asm volatile ("cpuid \n\t" \ "rdtsc \n\t" \ "mov %%edx, %0 \n\t" \ "mov %%eax, %1 \n\t" \ :"=r" (high), "=r"(low) \ : \ :"rax", "rbx", "rcx", "rdx"); \ #define get_tsc_bottom(high, low) \ asm volatile ("rdtscp \n\t" \ "mov %%edx, %0 \n\t" \ "mov %%eax, %1 \n\t" \ "cpuid \n\t" \ :"=r" (high), "=r"(low) \ : \ :"rax", "rbx", "rcx", "rdx"); \ get_tsc_top(tsc_high_before, tsc_low_before); get_tsc_bottom(tsc_high_after, tsc_low_after);
tsc_start = ((u64) tsc_high_before << 32) | tsc_low_before;
tsc_end = ((u64) tsc_high_after << 32) | tsc_low_after;
tsc_diff = tsc_end - tsc_start;
实现第二种方式:这种方式的误差稍微大一下,可能是它并没有将rdtsc寄存器和rdtscp寄存器配合使用。稍后查查他们的原理。
static uint64_t Rdtsc(void) { uint32_t lo,hi; __asm__ __volatile__ ("ifence; rdtsc": "=a" (lo), "=d" (hi));//ifence指令也可以不用 return (uint64_t)hi << 32 | lo; }
(2)armv8--系统寄存器读系统时间戳
armv8读取时间戳有两种方式,首先是系统时间戳和系统频率。
以下是鲲鹏,注意其他cpu可能外部频率不是100MHz static uint64_t Rdtsc() { uint64_t count_num; current_speed = 2400;//这个是cpu的频率2400MHz extern_clock = 100;//这个是系统频率,100MHz。也叫外部频率。 //这句话也可以读出来系统频率 __asm__ __volatile__("mrs %0, cntfreq_el0\n\tisb\n\t": "=r"(extern_clock)); __asm__ __volatile__("mrs %0, cntcvt_el0" : "=r"(count_num)) ; return count_num *(current_speed / extern_clock); }
以下是鲲鹏,注意其他cpu可能外部频率不是100MHz static uint64_t Rdtsc() { uint64_t count_num; current_speed = 2400;//这个是cpu的频率2400MHz extern_clock = 100;//这个是系统频率,100MHz。也叫外部频率。
__asm__ __volatile__("mrs %0, cntfreq_el0\n\tisb\n\t": "=r"(extern_clock));这句话也可以读出来系统频率
__asm__ __volatile__("mrs %0, cntcvt_el0" : "=r"(count_num)) ;
return count_num *(current_speed / extern_clock);
}
为什么要这样做呢?实际上cntcvt_el0寄存器读出来的时间戳是系统的时时间戳,由于系统频率和cpu频率都是已知的,那么我们就可以利用这个关系进行转换,将系统时间戳乘以这个系数。其实这里是有一些问题的,因为cntcvt_el0的时间戳是之间的时间比较长的(因为系统频率一般比较低300MHz一下),这样加入一条指令之间的时间要短于这个时间戳,系统也会自动加1,这样误差其实就很大了。
(3)armv8-cpu时间戳相当于x86的rdtsc寄存器
在armv8中,有一个PMCCNTR_EL0的寄存器,默认情况下,需要内核使能才能读取。
用户态代码:
static uint64_t Rdtsc(void) { uint64_t count_num; __asm__ __volatile_("mrs %0, pmccntr_el0": "+r"(count_num)); return count_num; }
驱动模块,在驱动中实现对寄存器的使能。不过这段代码,我尝试在init初始化函数
asm volatile("mrs %0, PMCCNTR_EL0" : "=r"(cval));
asm volatile("mrs %0, PMCCNTR_EL0" : "=r"(cval));
发现这里两个cval的值竟然相同!!!!!!!!!!!,不理解为什么!!!
/* * Author: Yiquan Chen这个作者的代码,此处引用。该网站有时候无法访问,所以直接粘贴了 * http://www.ilinuxkernel.com * ilinuxkernel@gmail.com * Enable user-mode ARM performance counter access. */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/smp.h> #define PERF_DEF_OPTS (1 | 16) #define PERF_OPT_RESET_CYCLES (2 | 4) #define PERF_OPT_DIV64 (8) #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 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) { u32 val=0; 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) { u32 val=0; 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:%lX ", val); asm volatile("mrs %0, PMCR_EL0" : "=r"(val)); printk("\nPMCR_EL0 Register:%lX ", 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);
Makefile
obj-m := pmu.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean