博主未授權(quán)任何人或組織機(jī)構(gòu)轉(zhuǎn)載博主任何原創(chuàng)文章,感謝各位對(duì)原創(chuàng)的支持!
博主鏈接
本人就職于國(guó)際知名終端廠商,負(fù)責(zé)modem芯片研發(fā)。
在5G早期負(fù)責(zé)終端數(shù)據(jù)業(yè)務(wù)層、核心網(wǎng)相關(guān)的開(kāi)發(fā)工作,目前牽頭6G算力網(wǎng)絡(luò)技術(shù)標(biāo)準(zhǔn)研究。
博客內(nèi)容主要圍繞:
???????5G/6G協(xié)議講解
???????算力網(wǎng)絡(luò)講解(云計(jì)算,邊緣計(jì)算,端計(jì)算)
???????高級(jí)C語(yǔ)言講解
???????Rust語(yǔ)言講解
如何在ARM上實(shí)現(xiàn)x86的rdtsc()函數(shù)
一、使用ARMv8提供的獨(dú)立定時(shí)器CNTVCT_EL0
???????System counter是Arm64下獨(dú)立于CPU core的計(jì)數(shù)器,在系統(tǒng)上電時(shí),會(huì)給此計(jì)數(shù)器設(shè)置固定的頻率。一個(gè)映射System counter計(jì)數(shù)器內(nèi)容的寄存器為CNTVCT_EL0
,可在用戶(hù)態(tài)下讀取此寄存器獲取counter值。而CNTFRQ_EL0
保存的是counter的頻率值(詳細(xì)內(nèi)容參考《【ARMv8】通用定時(shí)器總結(jié)》)。通過(guò)下面的函數(shù)實(shí)現(xiàn)獲取counter值及頻率值:
static inline uint64_t
arm64_cntvct(void)
{
uint64_t tsc;
asm volatile("mrs %0, cntvct_el0" : "=r" (tsc));
return tsc;
}
static inline uint64_t
arm64_cntfrq(void)
{
uint64_t freq;
asm volatile("mrs %0, cntfrq_el0" : "=r" (freq));
return freq;
}
static inline uint64_t
rdtsc(void)
{
return arm64_cntvct();
}
但是System counter的精度從Armv8.0到Armv8.5,范圍通常在1-50MHz;從Armv8.6開(kāi)始,以1GHz的固定頻率遞增。雖然1GHz的頻率已經(jīng)足夠高了,但是還是達(dá)不到CPU cycle級(jí)別的精度。
二、使用ARMv8的PMU計(jì)數(shù)器PMCCNTR_EL0
???????在ARMv8中,有Performance Monitors Control Register系列寄存器,其中PMCCNTR_EL0就類(lèi)似于x86的TSC寄存器。但是如果想在用戶(hù)態(tài)訪問(wèn)這些寄存器,需要在內(nèi)核代碼中開(kāi)啟PMU用戶(hù)態(tài)訪問(wèn)開(kāi)關(guān)。
2.1 關(guān)鍵寄存器介紹
PMCCNTR_EL0(Performance Monitors Cycle Count Register)
保存了處理器周期計(jì)數(shù)器的值,其結(jié)構(gòu)如下:
PMCR_EL0(Performance Monitors Control Register)
PMU配置寄存器,其結(jié)果如下:
其中和我們關(guān)系密切的幾個(gè)參數(shù)含義:
- LC:設(shè)置為1,表示開(kāi)啟64bit的周期計(jì)數(shù)器;否則,使用32bit的計(jì)數(shù)器(32bit的已經(jīng)摒棄);
- D:設(shè)置為1,表示每64個(gè)時(shí)鐘周期,計(jì)時(shí)器累加一次(已經(jīng)摒棄);否則,每個(gè)時(shí)鐘周期計(jì)數(shù)器累加一次;
- C:設(shè)置為1,表示重置計(jì)數(shù)器;
- E:設(shè)置為1,表示開(kāi)啟計(jì)數(shù)器PMCCNTR_EL0;
PMUSERENR_EL0(Performance Monitors User Enable Register)
用于開(kāi)啟或關(guān)閉用戶(hù)態(tài)下是否可以訪問(wèn)PMU寄存器,相關(guān)結(jié)構(gòu)如下:
其中和我們關(guān)系密切的幾個(gè)參數(shù)含義:
- ER:設(shè)置為1,表示用戶(hù)態(tài)下可以讀寫(xiě)PMU寄存器;否則不可以讀寫(xiě);
- EN:設(shè)置為1,表示用戶(hù)態(tài)軟件可以訪問(wèn)所有PMU特定的寄存器;
PMCNTENCLR_EL0(Performance Monitors Count Enable Clear register)
設(shè)置啟用的計(jì)數(shù)器和事件計(jì)數(shù)器,相關(guān)結(jié)構(gòu)如下:
其中和我們關(guān)系密切的幾個(gè)參數(shù)含義:
- C:設(shè)置為1,表示啟用PMCCNTR_EL0計(jì)數(shù)器;
2.2 內(nèi)核使能代碼
/*
* 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_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 void
enable_cpu_counters(void* data)
{
u32 val=0;
asm volatile("msr pmuserenr_el0, %0" : : "r"(0xf));
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)
{
isb();
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");
2.3 用戶(hù)態(tài)代碼
#include <stdio.h>
#define u64 unsigned long long
#define isb() asm volatile("isb" : : : "memory")
static inline u64 arch_counter_get_cntpct(void)
{
u64 cval;
isb();
asm volatile("mrs %0, PMCCNTR_EL0" : "+r"(cval));
return cval;
}
2.4 測(cè)試時(shí)遇到的問(wèn)題
可能有同學(xué)會(huì)用下面的代碼測(cè)試定時(shí)精度,
int main()
{
u64 begin,end;
begin = arch_counter_get_cntpct();
sleep(1);
end= arch_counter_get_cntpct();
printf("The count is %llu.\n",end-begin);
return 0;
}
但是會(huì)發(fā)現(xiàn)使用統(tǒng)計(jì)的計(jì)數(shù)值與CPU當(dāng)前的始終頻率計(jì)算后,時(shí)間不是1s。這是因?yàn)長(zhǎng)inux的省電功能導(dǎo)致的,sleep會(huì)使當(dāng)前進(jìn)程讓出CPU,如果此時(shí)CPU任務(wù)隊(duì)列中沒(méi)有任務(wù),就會(huì)進(jìn)入低功耗(例如,WFI)甚至offline,如果進(jìn)入上述狀態(tài)PMU計(jì)數(shù)器就會(huì)停止計(jì)數(shù),導(dǎo)致計(jì)數(shù)值不準(zhǔn)確。
畢竟PMU是為調(diào)式使用的,如果此時(shí)CPU沒(méi)有任務(wù),也確實(shí)沒(méi)有必要繼續(xù)統(tǒng)計(jì)了。所以使用PMU寄存器計(jì)數(shù)是,不應(yīng)該有主動(dòng)讓出CPU的行為,可能會(huì)導(dǎo)致計(jì)數(shù)不準(zhǔn)確。
可以嘗試關(guān)閉省電模式:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-744228.html
echo 1 > /sys/devices/system/cpu/cpu<X>/cpuidle/state<Y>/disable
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-744228.html
到了這里,關(guān)于【ARM v8】如何在ARM上實(shí)現(xiàn)x86的rdtsc()函數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!