Linux内核中ns级别的高精度计时方法

在Linux内核中,由于系统本身的实时性限制,以及系统当中存在的各种中断带来的不确定因素,使得Linux内核提供的计时精度无法做到ns级别,只能勉强达到us级别且定时不稳定存在较大误差。因此在ns级别的精确计时场景下无法使用常规的内核高精度计时器,只能通过其他非常规方式进行灵活处理。下面按照处理器平台介绍一种非常规的精确计时方式,精度可以达到1us级别,个别方式可以达到ns级别。

x86平台下的精确计时

从pentium开始,x86平台开始提供一个时间戳计数器,简称TSC,这是一个64位的用作时间戳计数的寄存器。它记录自上电启动以来CPU执行的周期数,在每个时钟信号到来的时候,TSC会自动加一。假设CPU的主频是2GHz,则经过换算后CPU执行一条指令的时间就是0.5ns,因为它依赖底层寄存器不受上层系统软件的干扰,因此只要读取这个寄存器就可以知道当前CPU总共执行了多少条指令,而将该指令总数累积起来换算成时间,就可以得到代码的执行耗时。例如:在代码段的A点和B点分别读取这个寄存器,两者的差值再乘以指令周期就得到A点和B点的代码执行耗时。

在Linux内核中也提供了现成的函数供我们读取该寄存器(以下函数截图采用的Linux内核版本是5.4.151)。

rdtsc()函数返回当前读取到的TSC值。

上图是amd.c当中一个真实使用该函数测量时间的例子,目的是测量代码段的执行耗时。我们也可以参照使用这个接口来测量任何一个我们感兴趣的代码段的执行时间,而且在中断上下文、进程上下文都可以使用,不过为了防止不同的CPU出现TSC不同步的情况,使用该函数时尽量在同一个CPU上使用。

ARM平台下的精确计时

定时计数器

TSC是x86平台下提供的,理解和使用起来也比较简单,但是在ARM平台下没有提供读取TSC的指令,这是否意味着ARM平台下没有类似的精确计时方法呢?

接触过STM32等单片机的同学可能会知道,在STM32当中会提供多个定时器,比如普通定时器、高级定时器等,这些都是硬件定时器,可以提供计数、计数比较功能,计数频率由系统时钟经过分频后决定,而且计数过程中不受系统软件的影响,可以实现较高精度的计时。

上面这张图是STM32F10x系列基本定时器的介绍。ARM架构的芯片也提供了类似的计数器(STM32也是属于ARM架构),提供定时计数的功能,它是独立于CPU core的一个计数器。我们看一下ARM平台下的定时器是什么样的,以下ARM平台下的寄存器截图都来自ARM官方文档《DDI0487G_a_armv8_arm.pdf》。

在定时器的使用会涉及频率、控制、计数等寄存器,这三个是最基本也是最关键的寄存器。文档同时提供了AArch64和AArch32的寄存器介绍,在这里只讲解AArch64部分,AArch32架构平台下的使用方法类似。

CNTFRQ_EL0是计数器频率控制器,它直接决定了计数器的计数频率。这个值在设备树中应该是可以更改的(不过关于这点我自己没有修改验证过,而是采用了默认值)。

这是一个64位的寄存器,32~63位保留,0~31位是系统计数器时钟频率,单位是Hz,在系统上电时候在相关的启动日志也有对应输出(以我手中的firefly-rk3399板卡为例)。这个寄存器在驱动中也可以直接访问,后面会有例子介绍。以24MHz计数频率为例,1s计24000000个数,换算为us就是1us计24个数。

CNTP_CTL_EL0是物理计数器控制寄存器,有计数器的控制位和状态标记位。

计数器一旦使能后就开始计数,即使ENABLE位置0停止定时器,计数器依然会计数,只是不会输出信号,在驱动中也验证了这一点。在论坛上也有网友指出,这个计数器一旦使能后只要不断电就不会停止计数。

CNTPCT_EL0是计数寄存器,里面存储计数值,也是64位长度,在驱动中可以直接读取。

前面提到,以24MHz计数频率作为例子,1s中会计24000000个数,我们写一个驱动demo来实际验证下这个计数器的精度(注意:驱动没有实际功能作用和效益,仅仅用作验证CounterTimer的计数功能)。

Armv8_CNTFRQ_EL0_read()读取计数器频率:

Armv8_CNTPCT_EL0_read()读取计数值:

为了提高读取计数寄存器的及时性,驱动中直接创建了一个内核线程,死循环轮询计数寄存器。

当计数大于等于24000000也就是1s后打印一次。输出结果如下:

读取计数器频率寄存器得到的频率就是24000000Hz,并且每1s的计数误差为4个数。前面换算时候知道,1us计数24个,那么计一个数大概是42ns,4个数总误差168ns左右,精度还是非常高的。如果提升计数器的频率,计数精度还可以达到更高。

性能监控寄存器

除了定时计数器可以用来做精确计时之外,ARM平台还提供了Performance Monitors registers(性能监控寄存器),也有计数功能。这组寄存器在Linux内核中结合PMU单元(Performance Monitor Unit)一起使用,主要用作CPU的性能监控,关于PMU以及使用后面有时间再介绍。ARM平台提供的这些寄存器,它们会对处理器执行指令时发生的事件进行计数,并且提供关于处理器的重要信息,比如发生了多少次 I-cache 失效、完成了多少个指令等等,我们主要借助于它的计数功能。先看一下相关的寄存器介绍。

PMCR_EL0, Performance Monitors Control Register:

控制寄存器,控制事件计数器的使能和禁止,同时提供更多的控制细节。

PMCNTENSET_EL0, Performance Monitors Count Enable Set register:

计数使能寄存器,第31位控制cpu cycle计数动作的启动和停止。

PMCCNTR_EL0, Performance Monitors Cycle Count Register:

Cycle Count寄存器,存储计数值,在驱动程序中读取这个寄存器的值就可以知道从使能开始,cpu运行了多少cycle。

按照以上寄存器说明,在驱动中实际操作读取cpu cycle实测一下精度。说明:RK3399主频为1.8GHz,程序中创建了一个线程循环读取寄存器值进行判断。

实际读取结果如下:

经过换算对比,1s内的计数误差在10ns以内(忽略代码中计算带来的精度损失)。这样的计数精度,在内核中相对来说已经很高了,而且使用也简单灵活,足以满足部分特殊的使用场景。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值