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
而不是arm64
和x86_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 架构模型 )
Android 逆向arm 汇编 ( 使用 IDA 解析 arm 架构的动态库文件 | 分析 malloc 函数的 arm 汇编语言 )
Android 逆向arm 汇编 ( 使用 IDA 解析 arm 架构的动态库文件 | 分析 malloc 函数的 arm 汇编语言 )