Arm通用计时器简介

所有使用Arm处理器的系统中都会包含一个标准化的通用定时器(Generic Timer)框架。这个通用定时器系统提供了一个系统计数器(System Counter)和一组定时器(Timer)。其结构如下图:

可以看到,系统计数器是全局唯一的,并且全局共享,对系统中的所有Arm核心进行广播。这个计数器以一个固定的频率递增(频率范围通常从1MHz到50MHz不等)。同时,这个系统计数器还是一直存在的,哪怕系统处于待机状态,所有内核都被关闭了,它仍然可以工作。计数器的宽度有56位至64位,计数到达最大值后会回滚。

每一个Arm核都配备一组专门为自己服务的定时器。定时器到期了之后会通过私有的PPI(Private Peripheral Interrupt)向通用中断控制器发中断请求。按照不同的指令集扩展,每组都有最多7个定时器,但无论如何最基本的都会提供4个,它们分别是:

  • EL1 physical timer:给操作系统用的物理定时器;
  • EL1 virtual timer:给操作系统用的虚拟定时器;
  • Non-secure EL2 physical timer:给虚拟机的宿主系统用的定时器;
  • EL3 physical timer:给运行于EL3内的固件程序使用的定时器。

对于系统计数器来说,可以通过读取控制寄存器CNTPCT_EL0来获得当前的系统计数值(无论处于哪个异常级别),也就是通过以下汇编指令:

MRS Xn, CNTPCT_EL0

这条指令是可以乱序执行的,使用的时候要适当保护。确切的说,这是读取物理计数器的值,系统中其实还存在一个虚拟计数器的值,这个虚拟计数器主要也是给虚拟机的宿主系统用的。虚拟计数器的值和物理计数器的值有如下对应关系:

虚拟计数器 = 物理计数器 - 偏移

这个偏移的值是通过控制寄存器CNTVOFF_EL2设置的,看名字就知道只能在EL2或EL3层才有权限设置和访问,如果不设置的话,默认值是0,也就是虚拟计数器和物理计数器的值一致。如果想得到虚拟计数器的值,可以通过读取CNTVCT_EL0控制寄存器来获得。

系统计数器的频率主要通过控制寄存器CNTFRQ_EL0来控制。频率是可以随意设定的,但只能在EL3下设置,也就是说在系统固件程序里。在其它的异常级别里(EL2到EL0)都不能设置,但是可以通过读取这个CNTFRQ_EL0寄存器来获得在固件中设置好的频率。

对于Arm定时器来说,总体有两种工作方式:

  1. 到一个绝对时间之后就触发;
  2. 从现在开始再过一定时间间隔之后触发。

Arm定时器通过两类寄存器来实现以上两种工作方式。一类叫做比较寄存器(CVAL),还有一类叫做定时寄存器(TVAL)。

比较寄存器有64位,如果设置了之后,当系统计数器达到或超过了这个值之后(CVAL<系统计数器),就会触发定时器中断。通过这种方式来实现第一类定时任务,。

定时寄存器有32位,如果设置了之后,会将比较寄存器设置成当前系统计数器加上设置的定时寄存器的值(CVAL=系统计数器+TVAL),后面就一样了,当系统计数器达到或超过了这个值后,就会触发定时中断。通过这种方式来实现第二种定时任务。

可以看出来,无论那种类型的定时器都是单次出发的(One Shot),如果想要周期触发,必须在中断处理程序中重新设置。这也刚好满足Linux系统中对于高精度定时器的要求。

除了设置定时条件的寄存器,其实每组定时器都还有一个控制寄存器(CTL),其只有最低三位有意义,其它的60位全是保留的,设置成0:

最低三位分别是:

  • ENABLE:是否打开定时器,使其工作;
  • IMASK:中断掩码,如果设置成1,则即使定时器是工作的,仍然不会发出中断;
  • ISTATUS:如果定时器打开的话,且满足了触发条件,则将这一位设置成1。

所以很简单,如果想让定时器按照要求发出中断的话,必须将Enable位设置成1,且IMASK位必须设置成0。

定时中断满足触发条件后,其并不会自己消失。如果在中断处理程序中不做处理的话,那同一个触发条件会不停的触发中断。

前面说到了,每个Arm核都有4个私有定时器,每个定时器都有一个比较寄存器、一个定时寄存器、一个控制寄存器,所以一共应该有12个寄存器可以操作,将它们的命名总结如下:

定时器类型比较寄存器名定时寄存器名控制寄存器名访问异常级别
EL1 physical timerCNTP_CVAL_EL0CNTP_TVAL_EL0CNTP_CTL_EL0EL0和EL1
EL1 virtual timerCNTV_CVAL_EL0CNTV_TVAL_EL0CNTV_CTL_EL0EL0和EL1
Non-secure EL2 physical timerCNTHP_CVAL_EL2CNTHP_TVAL_EL2CNTHP_CTL_EL2NS.EL2
EL3 physical timerCNTPS_CVAL_EL1CNTPS_TVAL_EL1CNTPS_CTL_EL1EL3和S.EL1

 

<think>好的,我现在需要帮助用户解决他们在编译时遇到的汇编错误。用户提供的错误信息是关于两个指令:isb 和 mrs,在AArch64架构下出现的问题。首先,我得仔细看看用户提供的代码和错误信息。 首先看错误信息,提示“invalid instruction mnemonic 'isb'”和“invalid instruction mnemonic 'mrs'”。这两个指令在ARM架构中是存在的,所以可能问题出在语法或者操作数上。用户的代码中有内联汇编: __asm__ volatile( "isb sy \t\n" "mrs %0, pmccntr_el0 " : "=r"(cycle_counter) :: "memory" ); 这里有两个指令:isb和mrs。首先检查isb指令的用法。ARM的ISB指令用于指令同步屏障,通常的格式是isb,后面可以带选项,比如isb sy。在AArch64中,sy是默认的选项,所以可能可以省略。不过用户这里显式写了sy,是否正确? 然后检查mrs指令。MRS用于将系统寄存器的值移动到通用寄存器。pmccntr_el0是性能计数器寄存器,需要确认在用户的目标平台上是否有权限访问这个寄存器。另外,mrs的语法是否正确,比如是否应该用大写的寄存器名? 接下来考虑编译器的兼容性。用户可能使用的编译器(比如GCC或Clang)是否支持这些指令的写法。有时候,不同的编译器对内联汇编的支持可能有差异,或者需要特定的编译选项,比如启用特定的处理器特性或权限级别。 另一个可能性是权限问题。pmccntr_el0是EL0(用户模式)下可访问的寄存器吗?在ARMv8架构中,某些寄存器需要更高的异常级别(如EL1或更高)才能访问。如果用户程序运行在用户态(EL0),可能没有权限访问这个寄存器,从而导致指令错误。这时候可能需要内核模块或者修改权限设置,但这通常不适用于用户空间程序。 此外,检查是否缺少必要的编译选项。例如,可能需要指定-mcpu=某个支持该寄存器的处理器架构,或者启用某些特性。 还有可能是指令拼写错误或语法问题。比如,是否应该使用大写字母,如ISB或MRS?不过在GCC内联汇编中通常使用小写。另外,检查是否有正确的操作数分隔符,比如逗号是否正确使用。 用户提到错误发生在libavutil/aarch64/timer.h中,这可能是FFmpeg的某个部分。可能需要确认使用的FFmpeg版本是否支持当前的编译环境和目标平台。有时候,特定平台的代码可能需要条件编译,或者依赖特定的编译器版本。 现在,综合这些可能性,我需要给出解决方案的步骤。首先,确认isb指令是否需要sy选项,可以尝试删除sy,只保留isb。其次,检查pmccntr_el0的访问权限,可能需要使用其他寄存器或在更高权限下运行。另外,检查编译器选项,可能需要添加-march=armv8-a或者其他相关参数。如果权限是问题,可能需要寻找替代方法读取计数器,比如使用内核提供的接口或者换用其他用户空间可访问的寄存器。 最后,如果用户的环境不支持直接读取pmccntr_el0,可能需要修改代码,使用其他方法获取时间戳,例如调用系统调用或使用库函数。例如,在Linux下可以使用clock_gettime函数,或者ARMv8的CNTVCT_EL0寄存器(但同样需要权限检查)。 总结可能的解决步骤:调整isb指令的语法,检查mrs的寄存器名称和权限,添加编译选项,或者更换时间获取方法。</think>这个问题涉及在ARMv8架构(aarch64)下使用内联汇编读取性能计数器时遇到的指令不识别错误。以下是逐步分析及解决方案: --- ### **1. 错误原因分析** - **ISB SY 指令** `isb sy` 是ARMv7的写法,在ARMv8架构中需简化为 `isb`(SY选项已默认隐含)。编译器会因无效指令选项报错。 - **PMCCNTR_EL0 寄存器权限** 该寄存器需要EL1(内核态)权限才能访问。用户态程序(EL0)直接读取会触发非法指令异常。这是Linux内核的安全限制。 - **编译器兼容性** 某些编译器可能要求严格的寄存器名称大小写(如`PMCCNTR_EL0`必须全大写)。 --- ### **2. 解决方案** #### **方案一:修改汇编指令(仅解决ISB错误)** ```c __asm__ volatile( "isb \t\n" // 移除'sy'选项 "mrs %0, PMCCNTR_EL0 \t" // 使用大写寄存器名 : "=r"(cycle_counter) :: "memory"); ``` #### **方案二:使用用户态可访问的寄存器(推荐)** 改用 `CNTVCT_EL0`(需内核启用用户态访问): ```c __asm__ volatile( "isb \t\n" "mrs %0, cntvct_el0 \t" : "=r"(cycle_counter) :: "memory"); ``` *注意:需内核配置 `CONFIG_ARM_ARCH_TIMER=y` 并启用 `CNTVCT_EL0` 的用户态访问权限*。 #### **方案三:通过内核接口获取** ```c #include <time.h> uint64_t read_time(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec; } ``` #### **方案四:编译选项调整** 添加 `-march=armv8-a` 确保目标架构正确: ```bash CFLAGS="-march=armv8-a" ./configure --arch=aarch64 ``` --- ### **3. FFmpeg相关代码的修复建议** 如果是编译FFmpeg时遇到此错误: 1. 检查FFmpeg版本,升级到最新代码(可能已修复) 2. 临时补丁: ```diff - "isb sy \t\n" + "isb \t\n" - "mrs %0, pmccntr_el0" + "mrs %0, PMCCNTR_EL0" ``` --- ### **总结** 根本矛盾在于用户态程序无法直接访问 `PMCCNTR_EL0`。推荐优先使用内核接口(如`clock_gettime`)或切换至用户态可访问的计数器寄存器。若必须使用性能计数器,需修改内核权限或驱动模块。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值