ARM架构获取精确时间的方法

Posted 18189298828

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ARM架构获取精确时间的方法相关的知识,希望对你有一定的参考价值。

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

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

4 注意事项

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

5 参考文章

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

检查 dockerfile 中的架构以获取 amd/arm

【中文标题】检查 dockerfile 中的架构以获取 amd/arm【英文标题】:Check architecture in dockerfile to get amd/arm 【发布时间】:2022-01-18 23:23:05 【问题描述】:

我们正在使用 Windows 和 Mac M1 机器使用 Docker 进行本地开发,并且需要在我们的 docker 环境中获取和安装 .deb 包。

软件包需要 amd64/arm64,具体取决于所使用的架构。

有没有办法在 docker 文件中确定这一点,即

if xyz === 'arm64'
    RUN wget http://...../arm64.deb
else 
    RUN wget http://...../amd64.deb

【问题讨论】:

【参考方案1】:

首先,您需要检查是否没有其他(更简单的)方法可以使用包管理器进行操作。

您可以使用arch 命令获取架构(相当于uname -m)。问题是它没有返回您期望的值(aarch64 而不是arm64x86_64 而不是amd64)。所以你必须转换它,我已经通过sed完成了。

RUN arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) && \
    wget "http://...../$arch.deb"

注意:您应该在运行此命令之前添加以下代码,以防止管道链发生故障时出现意外行为。请参阅Hadolint DL4006 了解更多信息。

SHELL ["/bin/bash", "-o", "pipefail", "-c"]

【讨论】:

这是很棒的东西。像魅力一样工作,我明天会在 Windows 上试一试,但在手臂上完美工作。非常感谢。

以上是关于ARM架构获取精确时间的方法的主要内容,如果未能解决你的问题,请参考以下文章

PK服务器arm架构 Filebeat 7.4.1安装和部署

Android 逆向ARM CPU 架构体系 ( ARM 处理器工作模式 | ARM 架构模型 )

Android 逆向ARM CPU 架构体系 ( ARM 处理器工作模式 | ARM 架构模型 )

Aapt 不在 arm 架构上运行

Android 逆向arm 汇编 ( 使用 IDA 解析 arm 架构的动态库文件 | 分析 malloc 函数的 arm 汇编语言 )

Android 逆向arm 汇编 ( 使用 IDA 解析 arm 架构的动态库文件 | 分析 malloc 函数的 arm 汇编语言 )