基础可参考:
Linux内核定时器相关内容总结-CSDN博客
定时器来源
定时器也是来源于芯片的硬件定时器,属于内部外设,有些可能也会用外部定时器,不管咋样,都属于芯片外设,既然是外设,那么我们也要编写对应的定时器驱动,不过一般常用的芯片比如ARM,都已经有了现成的定时器驱动,所以我们常常关注不到这块。
在 ARM 架构的 Linux 内核中,硬件定时器的来源(即选择哪个硬件定时器作为系统时钟源)是通过设备树(Device Tree)配置和内核编译选项共同决定的,核心是指定一个硬件定时器作为系统的 “时钟事件设备”(clock event device)和 “时钟源设备”(clock source device)。
一、硬件定时器的硬件基础
ARM 架构的处理器或 SOC 通常集成多种硬件定时器,常见的包括:
- ARM 通用定时器(ARM Generic Timer):
- 集成在 ARMv7-A/R、ARMv8-A 等架构中,是推荐的系统定时器,支持物理计数(CNTPCT)和虚拟计数(CNTVCT),精度高(通常为 64 位)。
- 全局定时器(Global Timer):
- 部分多核 ARM 处理器(如 Cortex-A9)集成,可用于多核系统的全局时间同步。
- 本地定时器(Local Timer):
- 每个 CPU 核心独立的定时器,如 ARM11 的本地定时器,用于核内私有定时任务。
- SOC 厂商自定义定时器:
- 如 TI 的 OMAP 定时器、三星的 S3C 定时器等,由芯片厂商在 ARM 架构基础上额外设计。
二、硬件定时器的配置与初始化流程
1. 设备树(Device Tree)配置:指定可用定时器
设备树(
.dts
或.dtsi
文件)是 ARM 架构中描述硬件的核心,需在其中声明系统中存在的硬件定时器,例如:// 以ARM Generic Timer为例 timer {compatible = "arm,armv7-timer"; // 兼容属性,匹配内核驱动interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,<GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,<GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,<GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;// 其他属性(如时钟频率等) };// 以厂商自定义定时器为例(如三星S3C2410) timer@10000000 {compatible = "samsung,s3c2410-timer";reg = <0x10000000 0x10>; // 寄存器地址和大小interrupts = <10>; // 中断号clocks = <&pclk>; // 时钟源 };
compatible
属性是关键,内核通过该属性匹配对应的定时器驱动(如drivers/clocksource/arm_generic_timer.c
)。- 不同定时器的设备树节点格式由其驱动定义,需参考芯片手册和内核文档。
2. 内核驱动:注册定时器设备
内核通过时钟源框架(clocksource framework)管理硬件定时器,驱动需将硬件定时器注册为两种角色:
- 时钟源设备(clocksource):提供高精度时间计数(如用于日历时间更新、高精度延时以及高精度定时器等等),通过
clocksource_register()
注册。- 时钟事件设备(clockevent):支持定时中断(如用于
jiffies
更新、调度器节拍、ms级别定时器触发等等),通过clockevents_register_device()
注册。以 ARM Generic Timer 为例,其驱动(
drivers/clocksource/arm_generic_timer.c
)的核心逻辑:// 注册时钟源 static struct clocksource arm_gt_clocksource = {.name = "arm_global_timer",.rating = 400, // 评级(越高越优先被选中).read = gt_clocksource_read, // 读取当前计数的函数.mask = CLOCKSOURCE_MASK(64), // 64位计数器.flags = CLOCK_SOURCE_IS_CONTINUOUS, };// 注册时钟事件设备 static struct clock_event_device arm_gt_clockevent = {.name = "arm_global_timer",.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,.set_mode = gt_clockevent_set_mode, // 设置模式(周期/单次).set_next_event = gt_clockevent_set_next, // 设置下一次中断时间.rating = 400, };// 驱动初始化时注册 static int __init arm_generic_timer_init(void) {clocksource_register(&arm_gt_clocksource);clockevents_register_device(&arm_gt_clockevent);return 0; }
3. 内核选择:确定系统默认定时器
内核启动时,会根据以下规则选择最终使用的定时器:
- 评级(rating):每个时钟源 / 事件设备有一个评级(如 100~500),内核优先选择评级最高的设备(精度和可靠性更好)。
- 兼容性:通过设备树的
compatible
属性匹配驱动后,驱动会自动注册设备并参与评级竞争。- 编译选项:部分架构可通过
CONFIG_*
选项强制启用 / 禁用特定定时器(如CONFIG_ARM_GENERIC_TIMER
控制是否支持 ARM 通用定时器)。最终被选中的时钟源设备则作为系统的时间基准(用于获取高精度时间),时钟事件设备会作为系统的周期性中断源(用于更新
jiffies
、调度器节拍等)。4. 核心配置文件与目录
- 设备树文件:
arch/arm/boot/dts/
(不同 SOC 的设备树,如vexpress-v2p-ca9.dts
)。- 定时器驱动:
drivers/clocksource/
(如arm_generic_timer.c
、s3c2410_timer.c
)。- 时钟框架核心:
kernel/time/clocksource.c
(时钟源管理)、kernel/time/clockevents.c
(时钟事件管理)。- 编译选项:
arch/arm/Kconfig
(与 ARM 定时器相关的配置,如CONFIG_ARM_ARCH_TIMER
)。总结
在 ARM 架构的 Linux 内核中,硬件定时器的来源通过设备树声明硬件信息,由对应驱动注册为时钟源 / 事件设备,最终内核根据评级和兼容性自动选择最优定时器作为系统时钟。核心配置点是设备树的定时器节点和内核对定时器驱动的编译启用,两者共同决定了系统使用哪个硬件定时器。
“时钟源”(clock event device)和 “时钟事件设备”(clock source device)不是同一个东西吗,有啥区别
“时钟源”(clocksource device)和 “时钟事件设备”(clock event device)不是同一个东西,它们在 Linux 内核中有着不同的功能和作用,具体区别如下:
功能用途:
- 时钟源设备:主要用于为系统提供一个精确的时间基准,它是一个单调递增的计数器,通常是一个硬件计数器,会不断地计数,内核通过读取该计数器的值来获取当前的时间信息,用于更新维护系统时间等服务,比如执行
date
命令获取时间时,最终会读取时钟源来确定具体时间。- 时钟事件设备:主要用于产生时钟事件,例如周期性中断或单次中断。它可以被编程设置在未来某个指定的时间点触发事件,主要用于实现普通定时器和高精度定时器,也用于产生 tick 事件,为进程调度子系统提供时钟滴答,以驱动进程调度、更新
jiffies
等操作。数据结构与关键属性:
- 时钟源设备:在内核中用
struct clocksource
表示,包含一个表示该时钟源质量的rating
域,值越大质量越好,系统会从所有的时钟源中选择一个质量最好的作为当前时钟源。还包含read
函数指针用于读取时钟值,以及mult
和shift
等用于在时钟周期和纳秒之间进行转换的成员。- 时钟事件设备:由
struct clock_event_device
表示,其关键在于能注册未来指定时间点发生的事件,提供了设置时钟事件模式(周期或单次)的函数指针,如set_mode
,还有用于设置下一次事件触发时间的set_next_event
函数指针等。硬件相关性:
- 时钟源设备:通常是一些精度较高、稳定性好的硬件,如实时时钟(RTC)、时间戳计数器(TSC)、高精度事件定时器(HPET)等,这些硬件为系统提供一个稳定的计时基础,其计数通常不会被轻易修改,以保证时间的准确性和单调性。
- 时钟事件设备:虽然有些时钟事件设备可能基于与时钟源相同的硬件,但它更侧重于利用硬件的中断功能,在 SMP(对称多处理)系统中,通常每个 CPU 核心会有一个独立的时钟事件设备,以便每个核心能独立触发事件,而无需依赖其他核心,减少处理器间的通信开销。
内核中的管理方式:
- 时钟源设备:系统中所有的时钟源都会被存放于一个全局的链表
clocksource_list
中,系统启动期间会从所有时钟源中选取一个最好的,curr_clocksource
用于保存当前系统使用的时钟源,可通过/sys/devices/system/clocksource/clocksource0/current_clocksource
来指定优先选择的时钟源,通过clocksource_register
函数向系统中添加时钟源。- 时钟事件设备:内核提供了一套框架来管理时钟事件设备,通过
clockevents_register_device
函数向系统添加一个时钟事件设备。在动态时钟模式下,会涉及到时钟事件设备的模式切换以及广播时钟设备等相关概念,例如tick_broadcast_device
用于保存当前使用的广播时钟设备,以在必要时为其他 CPU 提供时钟事件服务。时钟源设备为内核提供一个时间基线,通常实现为一个由固定时钟频率驱动的计数器,其值单调增加。当使用 date 命令获取当前时间时,内核会读取当前的时钟源,将其计数值转换为合适的时间单位后返回给用户空间。
系统启动时,内核会先从实时时钟(RTC)读取时间来初始化系统时间。RTC 是一种特殊的时钟源设备,即使系统断电也能靠电池维持计时。之后,系统主要通过选定的时钟源设备(如时间戳计数器 TSC 等)来更新实时时间信息,不再频繁读取 RTC。而时钟事件设备主要用于产生时钟事件,如周期性中断或单次中断,以驱动进程调度、更新 jiffies 等,并不直接提供日期时间信息。
RTC通常不会作为时钟源
RTC是时钟源的一种,主要是为了维持系统断电后时间能继续往下走,但是通常不会被优先选中作为Linux系统时间的时钟源,因为RTC 虽然可以作为时钟源设备,但它存在一些局限性,如读取成本较高,与基于 CPU 频率的时钟源相比分辨率通常更低。Linux 内核首选的时钟源是时间戳计数器(TSC),在系统启动进程时,通常会依赖它。在无痒系统上,TSC 可能不稳定,此时内核会切换到高精度事件定时器(HPET),它是仅次于 TSC 的首选时钟源。不过,在某些特定情况下,如系统中其他高精度时钟源不可用,或对时间精度要求不高且更看重断电保持时间功能时,也可以将 RTC 作为时钟源设备。
二者常常配合工作
RTC(实时时钟)与时钟源设备是相互关联的,并非完全独立。RTC 为时钟源设备提供初始时间基准,时钟源设备则在系统运行过程中负责更新系统时间,二者共同为 Linux 内核提供准确的时间信息。具体关系如下:
RTC 为时钟源设备提供初始时间:RTC 通常由电池供电,可在系统断电时持续计时,能记录当前的日期和时间,一般是记录自 1970 年 1 月 1 日起经历的秒数。Linux 系统启动时,内核会读取 RTC 中的时间来初始化系统时间,该时间会用于设置时钟源设备相关的时间变量,如
xtime
。此时,时钟源设备基于 RTC 提供的初始时间开始计时。时钟源设备更新系统时间:时钟源设备抽象了能够提供计时功能的系统硬件,如 RTC、时间戳计数器(TSC)、高精度事件定时器(HPET)等,其通常实现为一个由固定时钟频率驱动的计数器,计数器只能单调地增加,直到溢出为止。系统启动后,在大多数情况下,内核会通过选定的时钟源来更新实时时间信息(墙上时间),而不再频繁读取 RTC 的时间。例如,若选定的时钟源是基于定时器中断的,那么会通过定时器中断处理程序来不断更新系统时间,使得系统时间能够随着时间推移而准确变化。
二者共同维持系统时间:RTC 是系统时间的基础时间基准,而时钟源设备则基于此基准,在系统运行过程中通过不断计数和更新,为内核提供准确的当前时间信息。当系统时间因用户调整等操作发生变化时,内核可能会将更新后的时间写回 RTC,以保持 RTC 与系统时间的同步,确保下次系统启动时仍能获取到准确的初始时间,二者相互配合维持系统时间的准确性和连续性。
核心流程
核心流程:从启动到运行
1. 启动阶段:初始化时间硬件
步骤 1:读取 RTC 时间
内核启动早期(start_kernel()
→time_init()
),通过 RTC 驱动(如drivers/rtc/rtc-ds1307.c
)读取硬件时钟,初始化系统时间(xtime
变量)。步骤 2:注册时钟源和时钟事件设备
- 通用定时器驱动探测到硬件后,注册
clocksource
和clock_event_device
。- 内核通过
clocksource_select()
选定最佳时钟源,通过clockevents_config_and_register()
激活时钟事件设备。步骤 3:初始化滴答(tick)机制
- 启动
tick_init()
,为每个 CPU 绑定时钟事件设备,默认工作在周期性模式(CLOCK_EVT_MODE_PERIODIC
),每1/HZ
秒产生一次中断(tick
)。- 中断处理函数(如
tick_handle_periodic()
)更新jiffies
,触发定时器软中断(TIMER_SOFTIRQ
),驱动进程调度(更新进程时间片)。运行阶段:时间维护与定时服务
系统时间更新
- 周期性
tick
中断中,内核通过update_wall_time()
更新xtime
(基于时钟源的计数差值),最终反映为date
命令看到的系统时间。- 高精度模式(
CONFIG_HIGH_RES_TIMERS
启用)下,tick
会动态调整为单次触发模式,减少不必要的中断。定时器服务
- 普通定时器(
timer_list
):基于jiffies
,通过红黑树管理,在TIMER_SOFTIRQ
中处理超时回调。- 高精度定时器(
hrtimer
):基于ktime_t
,通过独立红黑树管理,依赖时钟事件设备的单次中断,在HRTIMER_SOFTIRQ
中处理(精度达纳秒级)。时间同步
- 通过 NTP(Network Time Protocol)服务调整
xtime
,内核提供do_settimeofday64()
系统调用接口。- 对于虚拟化场景(如 KVM),ARM 通用定时器支持虚拟计数(CNTVCT),由 Hypervisor 维护,确保客户机时间与主机同步。
关键配置与调试
编译选项:
CONFIG_HIGH_RES_TIMERS
:启用高精度定时器。CONFIG_ARM_ARCH_TIMER
:启用 ARM 通用定时器支持。HZ
:定义在include/asm-generic/param.h
,默认值由架构决定(如 ARM 通常为 1000)。调试接口:
/proc/timer_list
:查看所有定时器状态。/sys/devices/system/clocksource/clocksource0/available_clocksource
:列出可用时钟源。dmesg | grep -i timer
:查看定时器初始化日志。总结
ARM 架构 Linux 的时间管理机制是硬件定时器与内核框架的深度结合:
- 以 ARM 通用定时器为核心硬件,提供高精度计数和中断能力;
- 通过时钟源 / 时钟事件框架抽象硬件,统一时间管理接口;
- 维护
jiffies
和ktime_t
两套时间体系,分别服务于普通和高精度场景;- 依赖周期性 / 单次中断驱动时间更新和定时器触发,支撑进程调度、系统时间等核心功能。
这种设计既利用了 ARM 硬件的高效特性,又通过内核抽象保证了跨平台兼容性和可扩展性。
常规定时器的实现机制
Linux 内核定时器(
timer_list
)的实现机制基于系统时钟中断和红黑树(rbtree) 数据结构,核心是高效管理大量定时任务并在超时时刻触发回调函数。以下是其底层实现的关键机制:一、核心数据结构
定时器结构体
timer_list
每个定时器都由该结构体描述,定义在include/linux/timer.h
中:struct timer_list {struct hlist_node entry; // 哈希表节点(用于临时存储)unsigned long expires; // 超时时间(以jiffies为单位)void (*function)(struct timer_list *); // 超时回调函数u32 flags; // 标志(如TIMER_IRQSAFE)// 其他辅助字段(如base、slack等) };
expires
:存储超时时刻的jiffies
值(系统启动后的时钟节拍数)。function
:超时后执行的回调函数(用户自定义逻辑)。定时器管理核心:
timer_base
内核为每个 CPU 维护一个timer_base
结构体(避免多核竞争),定义在kernel/time/timer.c
中,核心字段包括:struct timer_base {struct rb_root_cached active; // 红黑树:存储待触发的定时器unsigned long clk; // 当前CPU的时钟节拍(jiffies)// 其他同步字段(如锁、软中断等) };
active
:红黑树的根节点,所有未超时的定时器按expires
从小到大排序,确保快速查找最早到期的定时器。二、核心工作流程
1. 定时器的注册与添加(
timer_setup
+add_timer
)
- 初始化:通过
timer_setup(timer, func, flags)
初始化定时器,绑定回调函数和标志。- 设置超时时间:手动赋值
timer->expires = jiffies + delay
(delay
为延时的节拍数)。- 添加到红黑树:调用
add_timer(timer)
时,内核会:
- 对当前 CPU 的
timer_base
加锁(避免并发修改)。- 将定时器插入红黑树(按
expires
排序,保证有序性)。- 若插入的定时器是最早到期的,更新系统时钟中断的下一次触发时间(确保及时处理)。
2. 时钟中断:触发定时器检查
系统时钟以固定频率(
HZ
,如 1000Hz)产生中断,中断处理函数(timer_interrupt
)会触发定时器检查:
- 内核先更新全局
jiffies
(每次中断 + 1)。- 调用
run_local_timers()
,触发定时器软中断(TIMER_SOFTIRQ
)。3. 软中断处理:执行超时定时器(
run_timer_softirq
)软中断上下文(优先级低于硬件中断,高于进程)中,
run_timer_softirq
函数批量处理超时定时器:
- 遍历当前 CPU
timer_base
的红黑树,提取所有expires <= 当前jiffies
的定时器。- 将这些定时器从红黑树移除,放入临时哈希表(避免处理时红黑树结构变化)。
- 解锁后,依次调用每个定时器的
function
回调函数(执行用户逻辑)。- 若定时器是周期性的(需手动在回调中重新设置
expires
并调用mod_timer
),会被重新加入红黑树。4. 定时器的修改与删除(
mod_timer
/del_timer
)
- 修改超时时间:
mod_timer(timer, new_expires)
会先删除旧定时器,再按新expires
重新插入红黑树。- 删除定时器:
del_timer(timer)
将定时器从红黑树中移除,确保不会被触发(返回是否已超时)。三、关键机制与优化
红黑树的作用
红黑树是平衡二叉搜索树,支持O(log n)
时间复杂度的插入、删除和查找操作。内核通过红黑树快速定位最早到期的定时器,避免遍历所有定时器,大幅提升效率。CPU 本地定时器
每个 CPU 管理自己的timer_base
和红黑树,减少多核间的锁竞争(仅在跨 CPU 操作时需要全局同步)。软中断延迟处理
定时器回调函数在软中断中执行,而非硬件中断上下文,避免了中断处理时间过长影响系统响应(硬件中断需快速完成)。
jiffies
与超时精度
定时器的最小精度是1/HZ
秒(如 HZ=1000 时为 1ms),若需更高精度(如微秒级),需使用 高精度定时器(hrtimer)。四、局限性
- 精度有限:依赖
jiffies
和HZ
,无法满足微秒级定时需求(需hrtimer
补充)。- 回调函数限制:回调函数运行在软中断上下文,不能睡眠(不可调用
schedule()
、获取互斥锁等)。- 超时可能延迟:若系统负载过高,软中断处理被推迟,定时器实际触发时间可能晚于
expires
。总结
Linux 内核定时器通过红黑树管理定时任务,结合时钟中断触发检查和软中断执行回调,实现了高效的低精度定时功能。其设计兼顾了性能(红黑树 + CPU 本地管理)和可靠性(锁机制 + 软中断延迟),是内核中处理延时任务的基础组件。
高精度定时器的实现机制
Linux 中的高精度定时器(hrtimer,High-Resolution Timer)是为满足微秒(μs)甚至纳秒(ns)级精度的定时需求而设计的,其实现机制与普通内核定时器(基于
jiffies
)有显著差异,核心依赖硬件提供的高精度时钟源和高效的时间管理框架。以下是其实现机制的关键细节:一、核心设计目标
高精度定时器解决普通定时器(
timer_list
)的精度限制(最低精度为1/HZ
秒,如HZ=1000
时为 1ms),主要用于:
- 需微秒级定时的场景(如音频处理、实时控制)。
- 替代
udelay()
等忙等待延时函数,提高 CPU 利用率。二、核心数据结构
高精度定时器结构体
struct hrtimer
定义在include/linux/hrtimer.h
中,核心字段:struct hrtimer {struct rb_node node; // 红黑树节点(按到期时间排序)ktime_t expires; // 到期时间(ktime_t类型,纳秒级)enum hrtimer_mode mode; // 模式(相对时间/绝对时间)enum hrtimer_restart (*function)(struct hrtimer *); // 回调函数struct hrtimer_clock_base *base; // 关联的时钟基准// 其他辅助字段(如状态标志、优先级等) };
ktime_t
:内核中表示时间的数据类型,以纳秒为单位(64 位,支持大范围时间)。mode
:区分定时模式(HRTIMER_MODE_REL
相对时间,HRTIMER_MODE_ABS
绝对时间)。时钟基准
struct hrtimer_clock_base
内核为不同时间域(如实时时间、单调时间)维护独立的时钟基准,每个基准包含:struct hrtimer_clock_base {struct rb_root_cached active; // 红黑树:存储未到期的hrtimerktime_t resolution; // 时钟精度(如1ns、10ns)struct hrtimer *next_timer; // 下一个即将到期的定时器// 与硬件时钟源的绑定信息 };
- 常见时间域:
CLOCK_REALTIME
(系统实时时间,可被修改)、CLOCK_MONOTONIC
(单调递增时间,不受系统时间调整影响)。三、核心实现机制
1. 依赖的硬件基础
高精度定时器依赖支持纳秒级计数的硬件时钟源,如:
- ARM Generic Timer:64 位计数器,支持纳秒级精度(通过
CNTPCT
寄存器计数)。- x86 TSC(Time Stamp Counter):处理器内置计数器,频率与 CPU 相关,可达到纳秒级。
- HPET(High Precision Event Timer):外部硬件定时器,提供稳定的纳秒级定时。
这些时钟源需被注册为内核的
clocksource
,且rating
评级较高(通常≥1000)。2. 定时器的注册与调度
初始化与启动:
通过hrtimer_init()
初始化定时器(指定时间域和模式),再通过hrtimer_start()
设置到期时间并加入红黑树:struct hrtimer my_timer; ktime_t delay = ktime_set(0, 500000); // 500μs(0秒+500,000纳秒)hrtimer_init(&my_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); my_timer.function = my_callback; // 设置回调函数 hrtimer_start(&my_timer, delay, HRTIMER_MODE_REL);
红黑树管理:
所有未到期的hrtimer
按expires
(纳秒级)在红黑树中排序,确保内核能快速定位下一个即将到期的定时器(next_timer
)。3. 触发与回调执行
高精度定时器的触发依赖时钟事件设备(
clockevent device
)的单次中断能力:
设置硬件中断:
当添加或修改
hrtimer
时,内核计算最近到期时间(next_timer->expires
),并通过时钟事件设备的set_next_event()
函数,编程硬件在该时间点产生单次中断(而非周期性中断)。中断处理:
硬件中断触发后,内核进入中断处理函数,执行以下操作:
- 读取当前高精度时间(通过
clocksource
)。- 遍历红黑树,将所有
expires <= 当前时间
的定时器从树中移除。- 对每个到期定时器,调用其
function
回调函数(在软中断上下文执行)。- 若回调函数返回
HRTIMER_RESTART
(需重启),则重新计算expires
并将其插回红黑树。- 重新计算下一个到期时间,设置硬件触发下一次中断。
4. 精度保证机制
避免周期性中断误差:
普通定时器依赖固定频率的jiffies
中断,累积误差较大;而 hrtimer 通过动态设置单次中断,每次根据实际时间调整下一次触发点,消除累积误差。时间转换与校准:
内核通过clocksource
提供的mult
和shift
参数,将硬件计数器的原始值(如时钟周期)精确转换为纳秒,确保时间计算的准确性。优先级机制:
高精度定时器的软中断(HRTIMER_SOFTIRQ
)优先级高于普通定时器软中断(TIMER_SOFTIRQ
),减少回调执行的延迟。四、与普通定时器的关键区别
特性 普通定时器( timer_list
)高精度定时器( hrtimer
)时间单位 jiffies
(毫秒级,依赖HZ
)纳秒级( ktime_t
)精度 最低 1/HZ
秒(如 1ms)微秒 / 纳秒级(取决于硬件) 硬件依赖 普通时钟事件设备(周期性中断) 高精度时钟源 + 支持单次中断的设备 数据结构 红黑树(按 jiffies
排序)红黑树(按纳秒时间排序) 触发方式 依赖 jiffies
中断批量处理动态设置单次中断,精准触发 适用场景 低精度需求(如秒级延时) 高精度需求(如音频、实时控制) 五、核心代码与配置
- 头文件:
include/linux/hrtimer.h
(数据结构和 API 定义)。- 实现代码:
kernel/time/hrtimer.c
(核心逻辑,如添加、触发、回调管理)。- 编译选项:
CONFIG_HIGH_RES_TIMERS
(启用高精度定时器支持,默认开启)。- 用户接口:通过
timer_create()
(CLOCK_MONOTONIC
等)在用户态使用,内核态直接调用hrtimer_*
系列函数。总结
Linux 高精度定时器通过纳秒级时间表示、红黑树高效管理、单次中断精准触发和高精度硬件时钟源,实现了微秒 / 纳秒级的定时精度。其设计避开了普通定时器依赖
jiffies
的固有缺陷,为对时间敏感的应用提供了可靠支持,是实时系统和高性能应用的核心组件。
滴答tick机制
在 Linux 中,滴答(tick)机制是时间管理的重要组成部分,用于实现系统时间更新、进程调度、定时器管理等功能。以下是具体介绍:
- 基本定义:Tick 是指时钟中断的时间间隔,其长度为
1/HZ
秒,HZ
是系统定义的时钟滴答频率。不同平台上HZ
的值可能不同,例如在一些常见平台上HZ
为 1000,此时 1 个 tick 就是 1 毫秒。- 工作原理:Linux 系统中有一个时钟振荡器,通常由可编程的时钟设备充当 clock event 设备。内核启动时可设置其发出中断的周期,时钟振荡器会周期性地发出中断,每次中断即为一个 tick。在单处理器系统中,每个 tick 只产生一次时钟中断,中断处理程序会完成更新系统时间、统计、定时器等功能。在多处理器系统下,时钟中断分为全局时钟中断和本地时钟中断,每个 tick 每个 CPU 要处理一次本地时钟中断,其中一个 CPU 还要处理一次全局时钟中断。
- 相关变量:内核通过全局变量
jiffies
(64 位系统中为jiffies_64
)记录系统启动后经历的 tick 总数。每发生一次 tick,对应的时钟中断处理程序会将jiffies_64
加 1,通过该变量可计算时间间隔、实现相对定时等操作。- 主要作用:Tick 机制为 Linux 内核提供了基本的时间基准,是内核实现时间相关功能的基础。它可触发中断处理程序,进而更新系统时间、检查定时器是否到期、进行进程调度等。例如,时间片轮转调度算法就是基于 tick 计数,来确定每个进程使用 CPU 的时间片长度;性能分析时,也可通过统计代码段执行的 tick 数,来评估其执行时间。
时钟中断处理函数是 Linux 内核时间管理的核心,负责响应硬件定时器产生的中断,驱动系统时间更新、进程调度、定时器触发等关键功能。其实现与硬件架构相关,但核心逻辑具有通用性,以下是详细解析:
一、时钟中断的来源
时钟中断由时钟事件设备(clock event device) 产生,常见硬件包括:
- ARM 架构的通用定时器(ARM Generic Timer)
- x86 架构的 PIT(可编程间隔定时器)、HPET(高精度事件定时器)
- 其他架构的专用定时器硬件
这些设备按固定频率(
HZ
,如 1000Hz)产生周期性中断,或按内核动态设置的时间产生单次中断(高精度模式)。二、中断处理函数的执行流程
时钟中断处理函数分为硬件中断处理和软中断后续处理两个阶段,以 ARM 架构为例:
1. 硬件中断处理(汇编入口)
- 入口点:中断向量表中的定时器中断入口(如
vector_irq
),最终跳转到handle_irq
通用中断处理函数。- 核心操作:
- 保存中断现场(寄存器状态)。
- 调用对应时钟事件设备的中断处理函数(如
arm_generic_timer_interrupt
)。- 清除硬件中断标志(避免重复触发)。
- 触发软中断(
TIMER_SOFTIRQ
或HRTIMER_SOFTIRQ
)。- 恢复中断现场,返回被中断的程序。
2. 核心处理函数(C 语言实现)
以周期性时钟中断为例,核心逻辑在
tick_handle_periodic
(定义于kernel/time/tick-common.c
):void tick_handle_periodic(struct clock_event_device *dev) {// 1. 更新jiffies(系统节拍计数)jiffies_64++;// 2. 处理全局tick(多CPU系统中,仅一个CPU执行)if (tick_do_timer_cpu == smp_processor_id()) {write_seqlock(&xtime_lock);// 更新系统时间(wall time)update_wall_time();write_sequnlock(&xtime_lock);}// 3. 触发定时器软中断(处理到期的timer_list)raise_softirq(TIMER_SOFTIRQ);// 4. 通知调度器,可能需要进行进程切换scheduler_tick();// 5. 若支持周期性模式,重置硬件中断(下一个tick)if (dev->features & CLOCK_EVT_FEAT_PERIODIC)dev->set_mode(CLOCK_EVT_MODE_PERIODIC, dev); }
3. 软中断后续处理
硬件中断处理完成后,内核在软中断上下文执行后续任务:
TIMER_SOFTIRQ
:由run_timer_softirq
处理,遍历并执行所有到期的普通定时器(timer_list
)回调函数。HRTIMER_SOFTIRQ
:由run_hrtimer_softirq
处理,执行高精度定时器(hrtimer
)的回调函数(高精度模式下启用)。三、关键功能与作用
更新系统时间
- 通过
update_wall_time
函数,根据时钟源(clocksource
)的计数差值更新系统实时时间(xtime
)。- 同步
jiffies
与实际时间的对应关系。驱动进程调度
- 调用
scheduler_tick
函数,更新当前进程的时间片计数。- 若进程时间片耗尽,设置调度标志(
TIF_NEED_RESCHED
),触发进程切换。处理定时器
- 触发软中断,批量处理到期的定时器(普通定时器和高精度定时器)。
- 对于周期性定时器,重新计算超时时间并添加到定时器队列。
维护统计信息
- 更新 CPU 负载、进程运行时间等统计数据(如
task_struct
中的utime
、stime
)。- 支持
getrusage
等系统调用获取进程时间信息。四、高精度模式与动态 tick
高精度模式(CONFIG_HIGH_RES_TIMERS):
时钟事件设备工作在单次触发模式(CLOCK_EVT_MODE_ONESHOT
),中断处理函数会根据下一个到期定时器的时间动态设置下一次中断,而非固定周期,从而实现纳秒级精度。动态 tick(CONFIG_NO_HZ_FULL):
对于空闲 CPU,暂停周期性 tick,仅在有定时器到期或进程唤醒时才产生中断,减少不必要的中断开销,降低系统功耗。五、核心代码与配置
硬件中断入口:
- ARM 架构:
arch/arm/kernel/irq.c
(通用中断处理)、drivers/clocksource/arm_generic_timer.c
(定时器中断处理)。- x86 架构:
arch/x86/kernel/apic_timer.c
(APIC 定时器中断)。核心处理函数:
kernel/time/tick-common.c
:tick_handle_periodic
(周期性处理)、tick_handle_oneshot
(单次触发处理)。kernel/time/timer.c
:run_timer_softirq
(普通定时器处理)。kernel/time/hrtimer.c
:run_hrtimer_softirq
(高精度定时器处理)。配置选项:
CONFIG_HZ
:设置时钟频率(如 100、250、1000)。CONFIG_HIGH_RES_TIMERS
:启用高精度定时器支持。CONFIG_NO_HZ_FULL
:启用动态 tick 模式。总结
时钟中断处理函数是 Linux 时间管理的 “心脏”,通过硬件中断快速响应和软中断延迟处理的结合,既保证了时间敏感操作的及时性,又避免了中断上下文过长影响系统响应。其核心功能包括更新系统时间、驱动进程调度、处理定时器和维护统计信息,同时支持高精度和动态 tick 等优化,平衡了精度、性能和功耗。
Linux 进程调度的时间片管理主要由tick 机制驱动。具体来说,进程时间片的消耗、更新和调度触发,都依赖于时钟中断(tick)的周期性触发,这是时间片轮转调度(RR)和公平调度(CFS)的核心工作基础。
具体机制:
时间片的消耗与更新
每个进程的时间片(或 CFS 中的虚拟运行时间)会在每次时钟中断(tick)时被更新:
- 对于实时进程(RR 调度类):每次 tick 会减少其剩余时间片,当时间片耗尽时,进程会被放到就绪队列末尾,等待下一次调度。
- 对于普通进程(CFS 调度类):tick 会更新进程的虚拟运行时间(
vruntime
),当vruntime
累积到超过调度实体的公平份额时,触发调度器重新选择下一个进程。调度触发的关键函数
时钟中断处理过程中,会调用scheduler_tick()
函数(定义在kernel/sched/core.c
),这是时间片驱动调度的核心:void scheduler_tick(void) {struct rq *rq = this_rq(); // 获取当前CPU的运行队列struct task_struct *curr = rq->curr; // 当前运行的进程// 更新当前进程的时间统计(如utime、stime)update_process_times(curr);// 调用对应调度类的tick处理函数(如CFS的task_tick_fair)curr->sched_class->task_tick(rq, curr, 0);// 检查是否需要重新调度(如时间片耗尽)trigger_load_balance(rq); }
task_tick_fair
(CFS 调度类):更新进程的vruntime
,并判断是否需要设置重调度标志(TIF_NEED_RESCHED
)。task_tick_rt
(实时调度类):减少时间片,若耗尽则触发重新调度。重调度的触发
当scheduler_tick()
判断当前进程时间片耗尽(或vruntime
失衡)时,会设置TIF_NEED_RESCHED
标志。当中断返回用户态或内核态调度点时,内核会检查该标志,调用schedule()
函数完成进程切换。特殊情况:无 tick 模式(
CONFIG_NO_HZ_FULL
)在支持动态 tick(如
NO_HZ_FULL
)的系统中,空闲 CPU 会暂停周期性 tick,以减少中断开销。但此时:
- 若 CPU 上有运行的进程,仍会通过高精度定时器(hrtimer)模拟 tick,确保时间片正常消耗。
- 当进程主动睡眠或时间片耗尽时,仍会触发调度,避免进程无限期占用 CPU。
总结
Linux 进程调度的时间片管理本质上依赖 tick 机制:
- 周期性 tick 是时间片消耗的 “计量器”,驱动进程运行时间的统计和更新。
scheduler_tick()
函数是连接 tick 与调度器的核心,负责判断是否需要触发进程切换。- 即使在动态 tick 模式下,仍会通过其他机制(如 hrtimer)保证时间片驱动的调度逻辑正常工作。
因此,tick 是进程时间片管理和调度触发的基础动力来源。
墙上时间
Linux 内核主要通过实时时钟(RTC)和系统定时器相互配合的机制来提供日历时间。具体如下:
- 实时时钟(RTC):RTC 是由纽扣电池供电的硬件时钟,即使系统关机也能持续计时。内核在启动时会读取 RTC 中的时间来初始化系统的墙上时间(wall time),该时间存放在
xtime
变量中。此外,系统关机时,内核会将当前系统时间写回 RTC,以保持两者同步。- 系统定时器:系统定时器是内核时间机制的重要组成部分,它以
HZ
(时钟节拍率)为频率自行触发时钟中断。当时钟中断发生时,内核会调用时钟中断处理程序timer_interrupt()
。全局变量jiffies
用于记录自系统启动以来产生的节拍总数,每发生一次时钟中断,jiffies
的值就会加 。通过jiffies
和HZ
,可以计算出系统运行时间。同时,时钟中断处理程序还会根据jiffies
的值来更新xtime
,从而实现对日历时间的持续维护。- 相关函数与接口:内核提供了
do_gettimeofday()
函数来读取墙上时间,用户空间程序可通过系统调用获取当前的日历时间。另外,timekeeper
模块建立在时钟源管理模块之上,负责维护 Linux 整个系统的时间,包括提供读取系统时间接口ktime_get
等供内核和系统调用使用。与 Linux 内核日历时间相关的目录文件主要有以下这些:
- /dev/rtc:这是 RTC 设备文件,用户空间程序可以通过对该文件进行读写、ioctl 等操作来访问 RTC 硬件,获取或设置时间,以及使用 RTC 的定时器等功能。
- /sys/class/rtc/:该目录下包含了与 RTC 相关的属性文件和子目录,提供了一种以文件操作方式访问 RTC 信息的接口。例如,通过读取
/sys/class/rtc/date
文件,可以获取当前的 RTC 时间;也可以向该文件写入合适的时间格式来设置 RTC 时间。- linux/kernel/time/:这是 Linux 内核时间子系统的源文件目录,包含了与时间管理相关的内核代码,如时钟源、时钟事件、jiffies 相关处理等代码文件,是内核实现时间管理机制的重要代码所在位置。
主要对象和目录文件总结
Linux 中定时器和时间管理机制涉及大量的数据结构、核心文件和关键函数,它们共同协作实现时间跟踪、定时任务调度等功能。以下按类别整理主要内容:
一、核心数据结构
1. 定时器相关
struct timer_list
(普通内核定时器)
定义:include/linux/timer.h
作用:描述一个基础定时器,用于毫秒级精度的定时任务。
核心字段:struct timer_list {struct hlist_node entry; // 哈希表节点(临时存储)unsigned long expires; // 超时时间(jiffies 单位)void (*function)(struct timer_list *); // 超时回调函数u32 flags; // 标志(如 TIMER_IRQSAFE) };
struct hrtimer
(高精度定时器)
定义:include/linux/hrtimer.h
作用:支持纳秒级精度的定时器,用于高精度场景(如音频处理)。
核心字段:struct hrtimer {struct rb_node node; // 红黑树节点(按到期时间排序)ktime_t expires; // 超时时间(纳秒级,ktime_t 类型)enum hrtimer_mode mode; // 模式(相对时间/绝对时间)enum hrtimer_restart (*function)(struct hrtimer *); // 回调函数struct hrtimer_clock_base *base; // 关联的时钟基准 };
struct hrtimer_clock_base
(高精度定时器时钟基准)
定义:include/linux/hrtimer.h
作用:管理同一时间域(如CLOCK_MONOTONIC
)的所有高精度定时器。
核心字段:struct hrtimer_clock_base {struct rb_root_cached active; // 红黑树(存储未到期的 hrtimer)ktime_t resolution; // 时钟精度(如 1ns)struct hrtimer *next_timer; // 下一个即将到期的定时器 };
2. 时间源与时钟事件相关
struct clocksource
(时钟源)
定义:include/linux/clocksource.h
作用:提供高精度时间计数(如硬件计数器),作为系统时间的基准。
核心字段:struct clocksource {const char *name; // 时钟源名称(如 "arm,armv7-timer")unsigned int rating; // 评级(越高越优先,最高 500)cycle_t (*read)(struct clocksource *cs); // 读取当前计数值u64 mask; // 计数器位宽掩码(如 64 位为 0xffffffffffffffff)u32 mult, shift; // 时间转换参数(cycle → 纳秒) };
struct clock_event_device
(时钟事件设备)
定义:include/linux/clockchips.h
作用:提供定时中断能力(周期性或单次),驱动定时器和调度器。
核心字段:struct clock_event_device {const char *name; // 设备名称unsigned int rating; // 评级int (*set_next_event)(unsigned long evt, struct clock_event_device *ce); // 设置下一次中断void (*set_mode)(enum clock_event_mode mode, struct clock_event_device *ce); // 设置模式(周期/单次)enum clock_event_features features; // 支持的特性(如周期性触发) };
3. 系统时间管理相关
struct timekeeper
(时间 keeper)
定义:kernel/time/timekeeping.c
(内部结构)
作用:维护系统时间的核心结构,协调时钟源与系统时间的同步。
核心字段:struct timekeeper {struct clocksource *clock; // 当前选中的时钟源u64 xtime_sec; // 秒级系统时间(自 epoch 起)u64 nsec; // 纳秒偏移(0 ~ 1e9-1)ktime_t wall_to_monotonic; // 实时时间到单调时间的偏移 };
jiffies
与jiffies_64
定义:include/linux/jiffies.h
作用:记录系统启动后的时钟节拍数(jiffies
为 32 位,jiffies_64
为 64 位扩展),是普通定时器的时间基准。二、主要目录与文件
1. 核心实现目录
kernel/time/
:时间管理和定时器的核心实现
timer.c
:普通定时器(timer_list
)的注册、触发逻辑。hrtimer.c
:高精度定时器(hrtimer
)的管理。timekeeping.c
:系统时间(xtime
)的维护和更新。clocksource.c
:时钟源框架的实现(注册、选择等)。clockevents.c
:时钟事件设备的管理(模式设置、中断触发等)。tick-common.c
:系统滴答(tick)机制的通用逻辑(如周期性中断处理)。
drivers/clocksource/
:时钟源硬件驱动
- 如
arm_generic_timer.c
(ARM 通用定时器驱动)、x86_tsc.c
(x86 TSC 定时器驱动)。
drivers/rtc/
:实时时钟(RTC)驱动
- 如
rtc-ds1307.c
(常见 RTC 芯片驱动),负责系统断电后时间的保存。2. 头文件目录
include/linux/
timer.h
:timer_list
结构体及普通定时器 API(timer_setup()
、add_timer()
等)。hrtimer.h
:hrtimer
结构体及高精度定时器 API(hrtimer_init()
、hrtimer_start()
等)。clocksource.h
:clocksource
结构体及时钟源 API。clockchips.h
:clock_event_device
结构体及时钟事件 API。jiffies.h
:jiffies
相关定义和转换函数(如jiffies_to_msecs()
)。ktime.h
:ktime_t
类型(纳秒级时间)及操作函数(如ktime_set()
、ktime_get()
)。3. 架构相关文件
arch/arm/kernel/time.c
:ARM 架构的时间初始化(如时钟源注册、中断绑定)。arch/x86/kernel/tsc.c
:x86 架构 TSC 时钟源的实现。三、关键函数
1. 定时器操作函数
普通定时器
timer_setup(struct timer_list *timer, void (*func)(struct timer_list *), unsigned int flags)
:初始化定时器,绑定回调函数。add_timer(struct timer_list *timer)
:将定时器添加到系统,开始计时。mod_timer(struct timer_list *timer, unsigned long expires)
:修改定时器的超时时间。del_timer(struct timer_list *timer)
:删除定时器,阻止其触发。高精度定时器
hrtimer_init(struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode mode)
:初始化高精度定时器,指定时间域(如CLOCK_MONOTONIC
)和模式。hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)
:启动高精度定时器,设置超时时间(纳秒级)。hrtimer_cancel(struct hrtimer *timer)
:取消高精度定时器。2. 时钟源与时钟事件函数
时钟源管理
clocksource_register(struct clocksource *cs)
:注册时钟源到系统。clocksource_select(void)
:从已注册的时钟源中选择评级最高的作为当前时钟源。clocksource_read_time(struct clocksource *cs)
:读取时钟源的当前时间(转换为纳秒)。时钟事件设备管理
clockevents_register_device(struct clock_event_device *ced)
:注册时钟事件设备。clockevents_config(struct clock_event_device *dev, u32 freq)
:配置时钟事件设备的频率。clockevents_set_mode(struct clock_event_device *dev, enum clock_event_mode mode)
:设置时钟事件设备的模式(周期性 / 单次)。3. 系统时间管理函数
ktime_get(void)
:获取当前时间(ktime_t
类型,纳秒级,基于单调时钟)。get_jiffies_64(void)
:安全获取 64 位jiffies_64
值。do_gettimeofday(struct timeval *tv)
:获取当前系统时间(秒 + 微秒)。update_wall_time(void)
:在时钟中断中更新系统时间(xtime
),由时间 keeper 驱动。4. 中断与滴答函数
tick_init(void)
:初始化系统滴答(tick)机制,绑定时钟事件设备到 CPU。tick_handle_periodic(struct clock_event_device *dev)
:处理周期性滴答中断,更新jiffies
并触发定时器软中断。run_timer_softirq(struct softirq_action *h)
:定时器软中断处理函数,执行到期的普通定时器回调。run_hrtimer_softirq(struct softirq_action *h)
:高精度定时器软中断处理函数,执行到期的hrtimer
回调。总结
Linux 时间管理机制通过分层设计(硬件驱动 → 框架抽象 → 应用接口)实现了高效的时间跟踪和定时服务:
- 数据结构(如
timer_list
、clocksource
)描述了定时器和时间源的属性;- 核心文件(如
kernel/time/timer.c
、drivers/clocksource/
)实现了底层逻辑;- 关键函数(如
add_timer()
、clocksource_register()
)提供了操作接口。这些组件共同支撑了从毫秒级到纳秒级的定时需求,是内核进程调度、网络协议、设备驱动等功能的时间基础。