替换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