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以内(忽略代码中计算带来的精度损失)。这样的计数精度,在内核中相对来说已经很高了,而且使用也简单灵活,足以满足部分特殊的使用场景。

<think>好的,我现在需要回答用户关于Linux平台上高精度时间函数的问题。首先,用户希望了解有哪些高精度的时间函数,以及它们的用法和适用场景。我需要结合用户提供的三个引用内容,特别是引用[2]和引用[3],因为它们提到了Linux的时钟系统和内核文档。 首先,我应该回忆一下Linux中常用的时间函数。常见的如gettimeofday,但它的精度可能只有微秒级。更高精度的可能需要clock_gettime函数,它支持不同的时钟类型,比如CLOCK_REALTIME和CLOCK_MONOTONIC,还有CLOCK_MONOTONIC_RAW以及CLOCK_BOOTTIME等。另外,可能需要提到POSIX标准中的这些函数,以及Linux特有的特性。 然后,根据引用[2],里面提到了clocksource和timekeeper,这些属于内核中的时间子系统。用户可能需要知道,高精度时间函数在内核中的实现依赖于这些机制。比如,clocksource提供不同精度的计时源,而timekeeper负责管理时间。因此,clock_gettime的精度取决于底层clocksource的选择,比如TSC、HPET等,这些硬件提供的精度更高。 引用[3]提到Linux内核文档,可能需要参考内核文档中关于时间管理的部分,确认clock_gettime的细节,比如支持的时钟类型和精度。同时,用户可能需要知道如何选择适合的时钟类型,比如CLOCK_REALTIME是系统实时时间,但可能受NTP调整影响,而CLOCK_MONOTONIC是单调递增的,适合测量时间间隔。 另外,可能需要提到高精度定时器相关的函数,比如timerfd_create,它可以与epoll结合使用,提供微秒级的定时精度。还有clock_getres函数,用来获取时钟的实际精度。 用户的问题可能还涉及到如何编写代码来获取高精度时间,所以需要给出示例代码,比如使用clock_gettime,并展示如何计算时间差。同时,需要注意不同时钟源的特性和适用场景,比如实时应用、性能测试等。 需要检查是否有其他相关函数,比如rdtsc指令读取时间戳计数器,但这是平台相关的,可能不够便携。或者新的API如clock_gettime的扩展,比如支持纳秒级的精度。 总结回答的结构应该是:先介绍主要的函数(clock_gettime),说明支持的时钟类型,各自的优缺点,示例代码,以及如何选择适合的时钟类型。然后可能提到其他相关函数如timerfd_create,最后给出注意事项,如权限、硬件支持等。 另外,用户提供的引用[1]提到定时器的精度和性能,所以在回答中需要强调不同函数的精度差异以及对系统资源的影响,比如某些高精度函数可能消耗更多资源。 最后,生成相关问题,比如如何选择时钟类型、如何优化定时器性能、与硬件时间源的关系等。</think>在Linux平台上实现高精度时间测量,主要依赖以下核心函数和机制: ### 一、核心时间函数 1. **clock_gettime()** 支持多种时钟源,精度可达纳秒级: ```c #include <time.h> int clock_gettime(clockid_t clk_id, struct timespec *tp); ``` 常用时钟类型: - `CLOCK_REALTIME`:系统实时时间(受NTP调整影响)[^2] - `CLOCK_MONOTONIC`:单调递增时钟(忽略时间跳变) - `CLOCK_MONOTONIC_RAW`:原始硬件单调时钟(不受NTP影响) - `CLOCK_BOOTTIME`:包含系统休眠时间的单调时钟 2. **timerfd_create()** 结合epoll可实现微秒级定时: ```c #include <sys/timerfd.h> int timerfd_create(int clockid, int flags); ``` ### 二、精度层级对比 | 函数/机制 | 典型精度 | 适用场景 | |---------------------|----------------|--------------------------| | gettimeofday() | 1微秒 | 传统时间戳 | | clock_gettime() | 1纳秒 | 高精度测量/同步 | | TSC寄存器 | CPU周期级 | 内核内部计时 | | RTC硬件时钟 | 秒级 | 系统启动基础时钟[^2] | ### 三、示例代码(纳秒级时间差计算) ```c #include <stdio.h> #include <time.h> int main() { struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); // 被测代码 clock_gettime(CLOCK_MONOTONIC, &end); long diff_ns = (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec); printf("耗时: %ld 纳秒\n", diff_ns); return 0; } ``` ### 四、关键注意事项 1. 硬件依赖性:实际精度受CPU的TSC、HPET等时钟源影响 2. 权限要求:部分时钟源需要`CAP_SYS_TIME`权限 3. 性能损耗:高频调用(>10K次/秒)可能影响系统性能[^1] 4. 时钟漂移:建议配合NTP/PTP进行时间同步
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值