[toc]

kernel/time/hrtimer.c 高精度定时器(High-Resolution Timers) 纳秒级精度的事件调度

历史与背景

这项技术是为了解决什么特定问题而诞生的?

kernel/time/hrtimer.c 实现了**高精度定时器(High-Resolution Timers)框架,它的诞生是为了解决内核中日益增长的、对亚毫秒级(sub-millisecond)甚至纳秒级(nanosecond)**定时精度的需求。

传统的jiffies定时器(timer_list)虽然开销极低,但其精度受限于系统的心跳频率HZ(通常在4ms到10ms之间)。这对于许多现代应用来说是完全不够的:

  1. 用户空间API支持:POSIX标准定义了nanosleep()clock_nanosleep()和高精度POSIX定时器,这些API要求内核能够提供远超jiffies精度的定时能力。
  2. 多媒体与音频:专业的音频应用为了实现低延迟处理,需要在非常精确的时间点(例如每1-2毫秒)被唤醒来填充声卡缓冲区。
  3. 网络与金融:高频交易和某些低延迟网络协议需要对超时进行微秒级的精确控制。
  4. 内核内部需求:随着无滴答内核(Tickless Kernel, NO_HZ的出现,内核需要一种方法来精确地编程下一次需要唤醒CPU的事件,而不是依赖于一个固定的周期性“滴答”。

hrtimer框架就是为了提供一个统一的、高精度的、可满足所有这些需求的定时器基础设施而设计的。

它的发展经历了哪些重要的里程碑或版本迭代?

hrtimer的引入是Linux内核时间子系统的一次革命性升级。

  • 诞生 (Kernel 2.6.16):由Thomas Gleixner和Ingo Molnar主导开发,hrtimer框架被正式合并。它从根本上改变了Linux处理高精度时间的方式。
  • 成为用户空间API的后端hrtimer迅速成为nanosleep()setitimer()(在ITIMER_REAL模式下)、以及POSIX定时器等高精度API的内核后端实现。
  • 驱动调度器时钟(Scheduler Tick):在支持NO_HZ的内核中,周期性的调度器时钟中断不再由一个固定的硬件定时器触发,而是由一个**周期性的高精度定时器(hrtimer)**来模拟。这使得时钟可以被灵活地停止和启动。
  • ktime的统一hrtimer完全基于ktime_t(一个表示纳秒的64位数据类型)进行工作,这使得它与内核中其他高精度时间测量(如sched_clock)保持了一致,并从根本上避免了jiffies的32位回绕问题。

目前该技术的社区活跃度和主流应用情况如何?

hrtimer是现代Linux内核中一个绝对核心、不可或缺的组件。

  • 主流应用
    • 所有需要精确睡眠的用户空间程序(sleep一微秒)。
    • 所有实时应用和低延迟多媒体应用。
    • 内核自身的调度和CPU空闲管理。
    • 需要精确、短时间超时的设备驱动程序。

核心原理与设计

它的核心工作原理是什么?

hrtimer的实现远比jiffies的定时器轮复杂,它结合了高效的数据结构和对底层硬件的精确控制。

  1. 时间表示:所有时间都使用ktime_t来表示,单位是纳秒。

  2. 核心数据结构:红黑树(Red-Black Tree)

    • 每个CPU核心都有一个自己的hrtimer_cpu_base结构,其中最重要的就是一个ktime_t类型的到期时间为键值排序的红黑树
    • 所有在该CPU上排队的hrtimer都存在于这棵树中。
    • 使用红黑树的优势在于:查找下一个即将到期的定时器的操作极其高效——它永远是树的最左边的节点,获取它的时间复杂度是O(1)。插入和删除一个定时器的操作是O(log N)
  3. 与硬件的交互(Clock Event Devices)

    • hrtimer框架依赖于一个硬件抽象层,称为时钟事件设备(Clock Event Devices)。这是一个能够被编程,在未来的一个精确时间点触发一次性中断的硬件定时器。
    • 当一个hrtimer被启动(hrtimer_start())时,框架会将其插入到对应CPU的红黑树中。
    • 然后,它会检查这棵树的最左边节点(即最早到期的定时器)。
    • 它将这个最早的到期时间,通过时钟事件设备驱动,编程到硬件定时器中,让硬件在那个精确的纳秒时刻触发一个中断。
  4. 中断处理

    • 当硬件定时器中断发生时,hrtimer的中断处理函数会被调用。
    • 该函数会检查红黑树,将所有已经到期的定时器(可能有多个,如果它们的到期时间非常接近)从树中移除。
    • 对于每个到期的定时器,它会执行其预先注册好的回调函数
    • 在处理完所有到期的定时器后,它会再次查看红黑树中新的“最早到期”的定时器,并用它的到期时间重新编程硬件

它的主要优势体现在哪些方面?

  • 高分辨率和高精度:能够提供纳秒级的定时精度,其最终精度受限于底层硬件的能力。
  • 不受jiffies回绕影响:使用64位的ktime_t,不存在回绕问题。
  • 支持多种时钟基准:可以基于CLOCK_MONOTONIC(单调时钟)或CLOCK_REALTIME(真实世界时钟)来设置定时器。
  • 是现代内核特性的基础:是实现NO_HZ等高级电源管理和调度功能的基石。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 更高的开销:这是与jiffies定时器相比最主要的缺点。管理红黑树、频繁地重新编程硬件定时器,都比jiffies定时器轮简单的链表操作和固定的周期性中断要昂贵得多。
  • 中断风暴:如果大量定时器被设置在非常接近的时间点到期,可能会导致中断和回调函数集中执行,对系统延迟产生影响。
  • 硬件依赖:其性能和精度完全取决于底层硬件(时钟事件设备)的能力。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

hrtimer是所有需要亚毫秒级精度定时的场景的首选解决方案。

  • 实现nanosleep():当用户程序需要睡眠几微秒时,内核会设置一个hrtimer来在精确的时间点后唤醒它。
  • 高频轮询:一个需要每500微秒检查一次硬件状态的驱动程序,会使用一个周期性的hrtimer
  • 调度器时钟tick_sched_timer就是一个周期性的hrtimer,用于触发进程调度。
  • 精确超时:一个延迟敏感的网络协议栈,在等待ACK时可能会设置一个几百微秒的hrtimer超时。

是否有不推荐使用该技术的场景?为什么?

  • 低精度、长周期的定时:如果你的超时需求是以几十毫-秒甚至秒为单位的(例如,一个200毫秒的超时),使用hrtimer就是一种不必要的性能浪费。在这种场景下,应该使用开销更低的**jiffies定时器(timer_list)**。

对比分析

请将其 与 其他相似技术 进行详细对比。

特性 高精度定时器 (hrtimer) jiffies / timer_list (传统定时器) sched_clock()
核心用途 高精度未来事件调度 (定时器)。 低精度未来事件调度 (定时器)。 极低开销当前时间戳获取。
分辨率 (纳秒级)。 (毫秒级, 由HZ决定)。 (纳秒级)。
功能 安排一个回调函数在未来被执行。 安排一个回调函数在未来被执行。 读取当前时间。不能安排未来事件。
数据结构 红黑树 定时器轮 (哈希链表)。 N/A (直接读取硬件计数器)。
开销 较高 (树操作 + 硬件重编程)。 较低 (链表操作 + 周期性中断)。 极低
适用场景 nanosleep, 实时应用, NO_HZ 绝大多数非精度敏感的内核超时。 调度器、ftrace等需要频繁获取时间戳的性能敏感路径。

include/linux/hrtimer_defs.h

hrtimer_base_type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/*
* 这是一个枚举类型,定义了所有高精度定时器(hrtimer)所支持的时钟基准类型。
* 每个成员都作为一个索引,用于访问per-cpu的hrtimer_cpu_base结构中的
* clock_base数组。
*/
enum hrtimer_base_type {
/*
* HRTIMER_BASE_MONOTONIC:
* 对应于时钟源CLOCK_MONOTONIC。
* 这是一个从系统启动后某个时间点开始、单调递增的时钟。它不受系统
* (墙上)时间被手动或通过NTP修改的影响,但在系统休眠(suspend)
* 时会暂停计时。这是内核中最常用、最可靠的用于计算时间间隔和超时的
* 时间基准。其定时器回调通常在硬中断(hardirq)上下文中被快速触发。
*/
HRTIMER_BASE_MONOTONIC,

/*
* HRTIMER_BASE_REALTIME:
* 对应于时钟源CLOCK_REALTIME。
* 这代表系统的“墙上时间”(wall-clock time)。它会受到settimeofday()
* 系统调用或NTP服务的影响,可能会发生向前或向后的跳变。它适用于那些
* 需要与现实世界绝对时间同步的事件。其回调也在硬中断上下文中触发。
*/
HRTIMER_BASE_REALTIME,

/*
* HRTIMER_BASE_BOOTTIME:
* 对应于时钟源CLOCK_BOOTTIME。
* 这是一个单调递增的时钟,与CLOCK_MONOTONIC类似,但它包含了系统
* 处于休眠状态的时间。即,系统休眠多久,这个时钟就会增加多久。
* 它对于需要计算包含系统休眠时间在内的总运行时间的场景非常有用。
* 其回调也在硬中断上下文中触发。
*/
HRTIMER_BASE_BOOTTIME,

/*
* HRTIMER_BASE_TAI:
* 对应于时钟源CLOCK_TAI (International Atomic Time, 国际原子时)。
* 这是一个基于国际原子时的、非常精确的时间标准。与CLOCK_REALTIME不同,
* 它不受闰秒(leap second)调整的影响,因此是严格单调递增的,不会回跳。
* 它适用于需要极高精度和无跳变时间源的科学或金融应用。
* 其回调也在硬中断上下文中触发。
*/
HRTIMER_BASE_TAI,

/*
* HRTIMER_BASE_MONOTONIC_SOFT:
* 对应于时钟源CLOCK_MONOTONIC_SOFT。
* 这是CLOCK_MONOTONIC的“软中断变体”。其定时器到期事件的处理被推迟到
* TIMER_SOFTIRQ软中断上下文中执行。这适用于那些对延迟不那么敏感、
* 但又不希望其回调函数在硬中断上下文中长时间运行的定时器,是一种
* 降低系统中断压力的优化。
*/
HRTIMER_BASE_MONOTONIC_SOFT,

/*
* HRTIMER_BASE_REALTIME_SOFT:
* 对应于时钟源CLOCK_REALTIME_SOFT。
* 这是CLOCK_REALTIME的软中断变体,语义与MONOTONIC_SOFT类似。
*/
HRTIMER_BASE_REALTIME_SOFT,

/*
* HRTIMER_BASE_BOOTTIME_SOFT:
* 对应于时钟源CLOCK_BOOTTIME_SOFT。
* 这是CLOCK_BOOTTIME的软中断变体,语义与MONOTONIC_SOFT类似。
*/
HRTIMER_BASE_BOOTTIME_SOFT,

/*
* HRTIMER_BASE_TAI_SOFT:
* 对应于时钟源CLOCK_TAI_SOFT。
* 这是CLOCK_TAI的软中断变体,语义与MONOTONIC_SOFT类似。
*/
HRTIMER_BASE_TAI_SOFT,

/*
* HRTIMER_MAX_CLOCK_BASES:
* 这个枚举成员不代表一种时钟类型,它的值等于前面所有时钟类型的总数。
* 它作为一个常量,常用于定义以hrtimer_base_type为索引的数组的大小,
* 以及在循环中作为边界条件,以确保代码能处理所有时钟类型。
*/
HRTIMER_MAX_CLOCK_BASES,
};

kernel/time/hrtimer.c

hrtimers_prepare_cpu 与启动时初始化相关的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* 与启动时初始化相关的功能:
*/
int hrtimers_prepare_cpu(unsigned int cpu)
{
struct hrtimer_cpu_base *cpu_base = &per_cpu(hrtimer_bases, cpu);
int i;

for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {
struct hrtimer_clock_base *clock_b = &cpu_base->clock_base[i];

clock_b->cpu_base = cpu_base;
seqcount_raw_spinlock_init(&clock_b->seq, &cpu_base->lock);
timerqueue_init_head(&clock_b->active);
}

cpu_base->cpu = cpu;
hrtimer_cpu_base_init_expiry_lock(cpu_base);
return 0;
}

hrtimers_cpu_starting 清除 CPU 关闭作中任何剩余状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int hrtimers_cpu_starting(unsigned int cpu)
{
struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);

/* 清除 CPU 关闭作中任何剩余状态 */
cpu_base->active_bases = 0;
cpu_base->hres_active = 0;
cpu_base->hang_detected = 0;
cpu_base->next_timer = NULL;
cpu_base->softirq_next_timer = NULL;
cpu_base->expires_next = KTIME_MAX;
cpu_base->softirq_expires_next = KTIME_MAX;
cpu_base->online = 1;
return 0;
}

hrtimer_init 初始化高分辨率定时器

1
2
3
4
5
6
void __init hrtimers_init(void)
{
hrtimers_prepare_cpu(smp_processor_id());
hrtimers_cpu_starting(smp_processor_id());
open_softirq(HRTIMER_SOFTIRQ, hrtimer_run_softirq);
}

hrtimer_update_base 更新一个hrtimer_cpu_base中的所有时钟基座

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
* 这是一个静态内联函数,用于更新一个hrtimer_cpu_base中的所有时钟基座。
* @base: 指向当前CPU的hrtimer_cpu_base。
* 返回值: 基于CLOCK_MONOTONIC的当前精确时间(ktime_t)。
*/
static inline ktime_t hrtimer_update_base(struct hrtimer_cpu_base *base)
{
/* offs_real: 指向REALTIME硬中断时钟基座的偏移量字段。*/
ktime_t *offs_real = &base->clock_base[HRTIMER_BASE_REALTIME].offset;
/* offs_boot: 指向BOOTTIME硬中断时钟基座的偏移量字段。*/
ktime_t *offs_boot = &base->clock_base[HRTIMER_BASE_BOOTTIME].offset;
/* offs_tai: 指向TAI硬中断时钟基座的偏移量字段。*/
ktime_t *offs_tai = &base->clock_base[HRTIMER_BASE_TAI].offset;
/* now: 用于存储获取到的当前精确时间。*/
ktime_t now;

/*
* 调用ktime_get_update_offsets_now,这是一个核心函数。
* 它能原子地获取当前时间戳(now),并同时获取所有可变时钟
* (REALTIME, BOOTTIME, TAI)相对于主时钟的最新偏移量。
* 它使用base->clock_was_set_seq这个序列计数器来检测并发的偏移量更新。
*/
now = ktime_get_update_offsets_now(&base->clock_was_set_seq,
offs_real, offs_boot, offs_tai);

/*
* 将刚刚获取到的、最新的硬中断时钟偏移量,直接复制到对应的
* 软中断时钟基座的偏移量字段中。这确保了两种上下文的时间基准同步。
*/
base->clock_base[HRTIMER_BASE_REALTIME_SOFT].offset = *offs_real;
base->clock_base[HRTIMER_BASE_BOOTTIME_SOFT].offset = *offs_boot;
base->clock_base[HRTIMER_BASE_TAI_SOFT].offset = *offs_tai;

/* 返回获取到的、基于CLOCK_MONOTONIC的当前时间。*/
return now;
}

for_each_active_base 遍历所有活动时钟基座

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/*
* 这是一个静态的内部辅助函数,用于从一个活动基座的位掩码中,
* 获取下一个需要处理的时钟基座。
* @cpu_base: 指向当前CPU的高精度定时器基座。
* @active: 指向一个位掩码的指针。该位掩码代表了所有待处理的活动时钟基座。
* 此函数会修改这个掩码。
*
* 返回值: 指向下一个活动时钟基座的指针,如果没有更多活动基座,则返回NULL。
*/
static struct hrtimer_clock_base *
__next_base(struct hrtimer_cpu_base *cpu_base, unsigned int *active)
{
/* idx: 用于存储找到的下一个活动基座的索引(即位号)。*/
unsigned int idx;

/* 如果传入的*active掩码为0,表示没有更多活动基座需要处理。*/
if (!*active)
/* 直接返回NULL,这将导致for_each_active_base循环终止。*/
return NULL;

/*
* 调用__ffs() (find first set),一个高效的位操作函数,
* 找到*active掩码中,从最低位算起第一个被置为1的位的索引。
*/
idx = __ffs(*active);

/*
* 将*active掩码中刚刚找到的那个位清零。
* 这是为了确保下一次调用__next_base时,不会重复处理同一个基座。
* `(1U << idx)`构造了一个只有第idx位为1的掩码,`~`取反后,
* 通过位与操作,即可将第idx位清零。
*/
*active &= ~(1U << idx);

/*
* 使用计算出的索引idx,从cpu_base->clock_base数组中取出
* 对应的hrtimer_clock_base结构体的地址,并返回。
*/
return &cpu_base->clock_base[idx];
}

/*
* 这是一个宏,提供一个标准的for循环风格的接口,用于遍历一个cpu_base中
* 所有处于active状态的时钟基座。
* @base: 一个hrtimer_clock_base*类型的循环变量,在每次循环中会指向
* 下一个活动基座。
* @cpu_base: 指向要遍历的hrtimer_cpu_base。
* @active: 一个包含了待处理基座的位掩码的整型变量。
*/
#define for_each_active_base(base, cpu_base, active) \
/*
* 构建一个while循环。循环的条件是__next_base的返回值不为NULL。
* 在每次循环开始时,都会调用__next_base来获取下一个活动基座,
* 并将其赋值给循环变量base。当所有活动基座都处理完毕后,
* __next_base会返回NULL,循环终止。
*/
while ((base = __next_base((cpu_base), &(active))))

__run_hrtimer 执行一个定时器

  • 它的核心作用是:以一种高度并发安全的方式,从定时器队列中移除一个指定的定时器,执行其回调函数,并根据回调的返回值决定是否需要将其重新排队。
    它的工作原理是一个被**序列计数器屏障(seqcount_barrier)清晰地划分为三个阶段的、精密的“解锁-调用-重锁”**模型。这个模型是hrtimer能够实现高性能和高并发安全的关键。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/*
* __run_hrtimer中的write_seqcount_barrier()将整个过程分为3个不同部分:
*
* - queued: 定时器已排队
* - callback: 定时器回调正在被执行
* - post: 定时器处于非活动状态,或已被(重新)排队
*
* 在读取端,我们确保我们观察到的timer->state和cpu_base->running来自同
* 一个部分,如果在我们观察期间有任何东西改变了,我们就重试。这包括
* timer->base的改变,因为仅靠序列号不足以应对这种情况。
*
* 序列号是必需的,因为否则如果我们读取操作被涂抹在多个连续的
* __run_hrtimer()调用上,我们仍可能观察到错误的负值(false negative)。
*/
static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base,
struct hrtimer_clock_base *base,
struct hrtimer *timer, ktime_t *now,
unsigned long flags) __must_hold(&cpu_base->lock)
{
/* fn: 指向定时器的回调函数指针。*/
enum hrtimer_restart (*fn)(struct hrtimer *);
/* expires_in_hardirq: 用于lockdep,标记回调是否在硬中断上下文中执行。*/
bool expires_in_hardirq;
/* restart: 存储回调函数的返回值。*/
int restart;

/* 调试断言:确保我们已持有cpu_base->lock。*/
lockdep_assert_held(&cpu_base->lock);

/* 为调试对象系统反激活此定时器。*/
debug_deactivate(timer);
/* 将base->running指向当前定时器,标记它为“正在执行”。*/
base->running = timer;

/*
* 将 ->running 的赋值与 ->state 的赋值分离开来。
*
* 与常规的写屏障一样,这确保了在hrtimer_active()的读取端,
* 不可能观察到 base->running == NULL && timer->state == INACTIVE 的状态。
*/
/* 这是一个序列计数器写屏障,它会将base->seq加1,使其变为奇数,标志着修改开始。*/
raw_write_seqcount_barrier(&base->seq);

/* 调用__remove_hrtimer,将定时器从红黑树中移除,并将其状态设置为INACTIVE。*/
__remove_hrtimer(timer, base, HRTIMER_STATE_INACTIVE, 0);
/* 获取私有的回调函数指针。ACCESS_PRIVATE用于处理一些特殊架构的指针。*/
fn = ACCESS_PRIVATE(timer, function);

/*
* 为低分辨率时间(TIME_LOW_RES)的情况,清除'is_relative'标志。如果定时器
* 以一个周期被重启,它就变成了一个绝对时间的定时器。如果不重启,这无所谓。
*/
if (IS_ENABLED(CONFIG_TIME_LOW_RES))
timer->is_rel = false;

/*
* 定时器在CPU基座中被标记为正在运行,所以即使我们释放了锁,
* 它也能被保护,不会被迁移到另一个CPU。
*/
/* 释放cpu_base->lock,并恢复之前的中断状态,准备执行回调。*/
raw_spin_unlock_irqrestore(&cpu_base->lock, flags);
/* 记录“定时器到期入口”的追踪事件。*/
trace_hrtimer_expire_entry(timer, now);
/* 通知锁依赖检测器,即将进入hrtimer回调。*/
expires_in_hardirq = lockdep_hrtimer_enter(timer);

/* 通过函数指针,调用用户注册的回调函数。*/
restart = fn(timer);

/* 通知锁依赖检测器,已退出hrtimer回调。*/
lockdep_hrtimer_exit(expires_in_hardirq);
/* 记录“定时器到期出口”的追踪事件。*/
trace_hrtimer_expire_exit(timer);
/* 重新获取cpu_base->lock,并禁用中断。*/
raw_spin_lock_irq(&cpu_base->lock);

/*
* 注意:我们在enqueue_hrtimer之后才清除running状态,并且我们不重新
* 编程事件硬件。这会在hrtimer_start_range_ns()或hrtimer_interrupt()
* 中发生。
*
* 注意:因为我们上面释放了cpu_base->lock,hrtimer_start_range_ns()
* 可能已经介入,并为我们重新排队了该定时器。
*/
/* 如果回调返回HRTIMER_RESTART,并且定时器尚未被并发地重新排队。*/
if (restart != HRTIMER_NORESTART &&
!(timer->state & HRTIMER_STATE_ENQUEUED))
/* 则调用enqueue_hrtimer,根据其新的到期时间,将其重新插入红黑树。*/
enqueue_hrtimer(timer, base, HRTIMER_MODE_ABS);

/*
* 将 ->running 的赋值与 ->state 的赋值分离开来。
*
* 与常规的写屏障一样,这确保了在hrtimer_active()的读取端,
* 不可能观察到 base->running.timer == NULL && timer->state == INACTIVE 的状态。
*/
/* 第二个序列计数器写屏障,将base->seq再次加1,使其恢复为偶数,标志着修改结束。*/
raw_write_seqcount_barrier(&base->seq);

/* 健壮性检查:确保running指针仍然是我们正在处理的timer。*/
WARN_ON_ONCE(base->running != timer);
/* 将running指针清零,表示处理完毕。*/
base->running = NULL;
}

__hrtimer_run_queues 执行所有活动的定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
static inline s64 hrtimer_get_softexpires_tv64(const struct hrtimer *timer)
{
return timer->_softexpires;
}

/*
* 这是一个静态函数,负责执行一个hrtimer_cpu_base中所有活动队列的到期定时器。
* @cpu_base: 指向当前CPU的高精度定时器基座。
* @now: 当前的单调时间(CLOCK_MONOTONIC)。
* @flags: 当前的中断状态,用于在执行回调时传递。
* @active_mask: 一个位掩码,用于筛选要处理的基座类型(硬中断或软中断)。
*/
static void __hrtimer_run_queues(struct hrtimer_cpu_base *cpu_base, ktime_t now,
unsigned long flags, unsigned int active_mask)
{
/* base: 在循环中,指向当前正在处理的时钟基座(hrtimer_clock_base)。*/
struct hrtimer_clock_base *base;
/* active: 这是一个位掩码,表示本次调用需要处理的所有活动基座的集合。*/
unsigned int active = cpu_base->active_bases & active_mask;

/*
* 使用for_each_active_base宏,遍历active掩码中所有被置位的时钟基座。
*/
for_each_active_base(base, cpu_base, active) {
/* node: 指向从红黑树中获取的下一个到期定时器的节点。*/
struct timerqueue_node *node;
/* basenow: 当前基座的精确时间,由全局单调时间now和基座偏移量计算得出。*/
ktime_t basenow;

/* 计算当前基座的“现在”时间。*/
basenow = ktime_add(now, base->offset);

/*
* 内层循环:只要基座的定时器红黑树(base->active)中还有节点,就一直循环。
*/
while ((node = timerqueue_getnext(&base->active))) {
/* timer: 指向从节点中获取的hrtimer结构体。*/
struct hrtimer *timer;

/* 使用container_of宏,根据成员node的地址,反向计算出其所属的hrtimer结构体的地址。*/
timer = container_of(node, struct hrtimer, node);

/*
* 使用软到期时间(softexpires)的直接目标是最小化唤醒,
* 而不是在定时器软到期后的最早中断中运行它。
* 这使我们能够避免使用可以回答重叠区间查询的优先级搜索树,
* 而是使用我们已有的简单二叉搜索树(红黑树)。
* 通过延迟那些位于一个尚未到期的定时器右侧的定时器,我们不会增加
* 额外的唤醒,因为那个尚未到期的定时器无论如何都必须触发一次唤醒。
*/
/*
* 这是核心的到期检查。比较当前基座时间和定时器的软到期时间。
* hrtimer_get_softexpires_tv64会获取timer->_softexpires。
*/
if (basenow < hrtimer_get_softexpires_tv64(timer))
/* 如果最早的定时器都尚未到期,由于红黑树的有序性,
* 后面的所有定时器也肯定未到期,可以安全地跳出内层循环。*/
break;

/*
* 调用__run_hrtimer来执行这个已到期的定时器。
* 这个函数会负责将其从红黑树中移除、调用回调函数、并可能重新排队。
*/
__run_hrtimer(cpu_base, base, timer, &basenow, flags);
/* 如果是在软中断上下文中,则调用同步等待函数。*/
if (active_mask == HRTIMER_ACTIVE_SOFT)
// hrtimer_sync_wait_running(cpu_base, flags);
}
}
}

hrtimer_run_softirq 运行HRTIMER_SOFTIRQ软中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
* 这是一个静态函数,负责运行HRTIMER_SOFTIRQ软中断。
* __latent_entropy: GCC属性,表示此函数执行时间的微小变化可以为内核随机数熵池贡献熵。
*/
static __latent_entropy void hrtimer_run_softirq(void)
{
/* cpu_base: 指向当前CPU私有的高精度定时器基座(hrtimer_cpu_base)。*/
struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
/* flags: 用于保存spin_lock_irqsave获取锁前的中断状态。*/
unsigned long flags;
/* now: 用于存储当前的高精度时间。*/
ktime_t now;

/*
* 获取一个用于保护next_expiry字段的特殊锁。
* (在一些内核版本中,此锁可能与主锁合并)
*/
hrtimer_cpu_base_lock_expiry(cpu_base);
/* 获取保护整个cpu_base数据结构的主自spin_lock,并禁用本地中断。*/
raw_spin_lock_irqsave(&cpu_base->lock, flags);

/*
* 调用hrtimer_update_base,它会读取一个高精度时钟源,
* 获取当前精确时间now,并用它更新cpu_base的内部时钟。
*/
now = hrtimer_update_base(cpu_base);
/*
* 调用__hrtimer_run_queues,这是执行所有到期定时器的核心函数。
* - cpu_base: 要处理的基座。
* - now: 当前的精确时间,所有到期时间 <= now 的定时器都将被执行。
* - flags: 当前的中断状态,用于在执行回调时可能需要的临时开中断。
* - HRTIMER_ACTIVE_SOFT: 标记本次执行是在软中断上下文中。
*/
__hrtimer_run_queues(cpu_base, now, flags, HRTIMER_ACTIVE_SOFT);

/* 将“软中断已被激活”的标志清零。*/
cpu_base->softirq_activated = 0;
/*
* 调用hrtimer_update_softirq_timer,它会查看红黑树中下一个最近的定时器,
* 并据此重新编程硬件定时器,以便在下一个精确时刻触发中断。
* 传入true表示这是一个软中断上下文。
*/
hrtimer_update_softirq_timer(cpu_base, true);

/* 释放主自旋锁,并恢复之前的中断状态。*/
raw_spin_unlock_irqrestore(&cpu_base->lock, flags);
/* 释放next_expiry锁。*/
hrtimer_cpu_base_unlock_expiry(cpu_base);
}

hrtimer_clockid_to_base 将 clockid 转换为 hrtimer_base_type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static inline int hrtimer_clockid_to_base(clockid_t clock_id)
{
switch (clock_id) {
case CLOCK_REALTIME:
return HRTIMER_BASE_REALTIME;
case CLOCK_MONOTONIC:
return HRTIMER_BASE_MONOTONIC;
case CLOCK_BOOTTIME:
return HRTIMER_BASE_BOOTTIME;
case CLOCK_TAI:
return HRTIMER_BASE_TAI;
default:
WARN(1, "Invalid clockid %d. Using MONOTONIC\n", clock_id);
return HRTIMER_BASE_MONOTONIC;
}
}

hrtimer_setup 初始化高分辨率定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
static inline enum hrtimer_restart hrtimer_dummy_timeout(struct hrtimer *unused)
{
return HRTIMER_NORESTART;
}

static void __hrtimer_setup(struct hrtimer *timer,
enum hrtimer_restart (*function)(struct hrtimer *),
clockid_t clock_id, enum hrtimer_mode mode)
{
bool softtimer = !!(mode & HRTIMER_MODE_SOFT);
struct hrtimer_cpu_base *cpu_base;
int base;

/*
* 在启用了 PREEMPT_RT 的内核上,由于延迟原因,
* 并且因为回调可以调用可能在 RT 上休眠的函数,例如 spin_lock() ,
* 因此未明确标记为硬中断到期模式的 hrtimer 被移动到软中断上下文中。
*/
if (IS_ENABLED(CONFIG_PREEMPT_RT) && !(mode & HRTIMER_MODE_HARD))
softtimer = true;

memset(timer, 0, sizeof(struct hrtimer));

cpu_base = raw_cpu_ptr(&hrtimer_bases);

/*
* POSIX 魔力:相对CLOCK_REALTIME定时器不受时钟修改的影响,因此它们需要变得CLOCK_MONOTONIC以确保 POSIX 合规性。
*/
if (clock_id == CLOCK_REALTIME && mode & HRTIMER_MODE_REL)
clock_id = CLOCK_MONOTONIC;

base = softtimer ? HRTIMER_MAX_CLOCK_BASES / 2 : 0;
base += hrtimer_clockid_to_base(clock_id);
timer->is_soft = softtimer;
timer->is_hard = !!(mode & HRTIMER_MODE_HARD);
timer->base = &cpu_base->clock_base[base];
timerqueue_init(&timer->node);

if (WARN_ON_ONCE(!function))
ACCESS_PRIVATE(timer, function) = hrtimer_dummy_timeout;
else
ACCESS_PRIVATE(timer, function) = function;
}

/**
* hrtimer_setup - 将定时器初始化为给定的时钟
* @timer:需要初始化的定时器
* @function:回调函数
* @clock_id:要使用的时钟
* @mode:与初始化相关的模式:
* HRTIMER_MODE_ABS、HRTIMER_MODE_REL、HRTIMER_MODE_ABS_SOFT、
* HRTIMER_MODE_REL_SOFT
*
* 上述的 PINNED 变体可以提交,
* 但在发生固定时忽略 PINNED 位
* hrtimer 启动时
*/
void hrtimer_setup(struct hrtimer *timer, enum hrtimer_restart (*function)(struct hrtimer *),
clockid_t clock_id, enum hrtimer_mode mode)
{
debug_setup(timer, clock_id, mode);
__hrtimer_setup(timer, function, clock_id, mode);
}
EXPORT_SYMBOL_GPL(hrtimer_setup);

lock_hrtimer_base unlock_hrtimer_base 定时器底座锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static inline struct hrtimer_clock_base *
lock_hrtimer_base(const struct hrtimer *timer, unsigned long *flags)
__acquires(&timer->base->cpu_base->lock)
{
struct hrtimer_clock_base *base = timer->base;

raw_spin_lock_irqsave(&base->cpu_base->lock, *flags);

return base;
}

/*
* Counterpart to lock_hrtimer_base above:
*/
static inline
void unlock_hrtimer_base(const struct hrtimer *timer, unsigned long *flags)
__releases(&timer->base->cpu_base->lock)
{
raw_spin_unlock_irqrestore(&timer->base->cpu_base->lock, *flags);
}

hrtimer_bases 计时器底座

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*
* 计时器底座:
*
* clockid 比 hrtimer bases 多。
* 因此,我们通过 hrtimer_base_type 枚举索引到计时器基数。
* 当尝试使用 clockid 到达一个基数时,hrtimer_clockid_to_base() 用于将 clockid 转换为正确的 hrtimer_base_type。
*/
DEFINE_PER_CPU(struct hrtimer_cpu_base, hrtimer_bases) =
{
.lock = __RAW_SPIN_LOCK_UNLOCKED(hrtimer_bases.lock),
.clock_base =
{
{
.index = HRTIMER_BASE_MONOTONIC,
.clockid = CLOCK_MONOTONIC,
.get_time = &ktime_get,
},
{
.index = HRTIMER_BASE_REALTIME,
.clockid = CLOCK_REALTIME,
.get_time = &ktime_get_real,
},
{
.index = HRTIMER_BASE_BOOTTIME,
.clockid = CLOCK_BOOTTIME,
.get_time = &ktime_get_boottime,
},
{
.index = HRTIMER_BASE_TAI,
.clockid = CLOCK_TAI,
.get_time = &ktime_get_clocktai,
},
{
.index = HRTIMER_BASE_MONOTONIC_SOFT,
.clockid = CLOCK_MONOTONIC,
.get_time = &ktime_get,
},
{
.index = HRTIMER_BASE_REALTIME_SOFT,
.clockid = CLOCK_REALTIME,
.get_time = &ktime_get_real,
},
{
.index = HRTIMER_BASE_BOOTTIME_SOFT,
.clockid = CLOCK_BOOTTIME,
.get_time = &ktime_get_boottime,
},
{
.index = HRTIMER_BASE_TAI_SOFT,
.clockid = CLOCK_TAI,
.get_time = &ktime_get_clocktai,
},
},
.csd = CSD_INIT(retrigger_next_event, NULL)
};

__remove_hrtimer - 删除计时器的内部功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
* __remove_hrtimer - 删除计时器的内部功能
*
* 调用方必须持有 base lock。
*
* 高分辨率定时器模式在定时器是下一个过期的定时器时重新编程时钟事件设备。
* 调用方可以通过将 reprogram 设置为零来禁用此功能。
* 当上下文无论如何都要进行重新编程时(例如 timer interrupt),这很有用
*/
static void __remove_hrtimer(struct hrtimer *timer,
struct hrtimer_clock_base *base,
u8 newstate, int reprogram)
{
struct hrtimer_cpu_base *cpu_base = base->cpu_base;
u8 state = timer->state;

/* Pairs with the lockless read in hrtimer_is_queued() */
WRITE_ONCE(timer->state, newstate);
if (!(state & HRTIMER_STATE_ENQUEUED))
return;

if (!timerqueue_del(&base->active, &timer->node))
cpu_base->active_bases &= ~(1 << base->index);

/*
* Note: If reprogram is false we do not update
* cpu_base->next_timer. This happens when we remove the first
* timer on a remote cpu. No harm as we never dereference
* cpu_base->next_timer. So the worst thing what can happen is
* an superfluous call to hrtimer_force_reprogram() on the
* remote cpu later on if the same timer gets enqueued again.
*/
if (reprogram && timer == cpu_base->next_timer)
hrtimer_force_reprogram(cpu_base, 1);
}

remove_hrtimer 删除 hrtimer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
*删除 hrtimer,在持有 base lock 的情况下调用
*/
static inline int
remove_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base,
bool restart, bool keep_local)
{
u8 state = timer->state;

/* 判断它是否已被加入队列 */
if (state & HRTIMER_STATE_ENQUEUED) {
bool reprogram;

/*
* 设置 reprogram 标志,用于指示是否需要重新编程硬件时钟:
* 如果定时器位于当前 CPU 的定时器基础上,则可能需要重新编程。
* 如果定时器位于其他 CPU,则跳过重新编程,因为中断处理程序会在目标 CPU 上完成重新编程。
*/
debug_deactivate(timer);
reprogram = base->cpu_base == this_cpu_ptr(&hrtimer_bases);

/*
* 如果定时器未重新启动,则如果定时器是本地定时器,则需要重新编程。
* 如果它是本地的并且即将重新启动,请避免对其进行两次编程(在删除时和稍后重新排队时)。
*/
if (!restart)
state = HRTIMER_STATE_INACTIVE;
else
reprogram &= !keep_local;

__remove_hrtimer(timer, base, state, reprogram);
return 1;
}
return 0;
}

enqueue_hrtimer 用于(重新)启动计时器的内部函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* enqueue_hrtimer - 用于(重新)启动计时器的内部函数
*
* 计时器按到期顺序插入。插入到红黑树中的是 O(log(n))。必须持有基础锁。
*
* 当新计时器是树中最左侧的计时器时,返回 true。
*/
static bool enqueue_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base,
enum hrtimer_mode mode)
{
debug_activate(timer, mode);
WARN_ON_ONCE(!base->cpu_base->online);

base->cpu_base->active_bases |= 1 << base->index;

/* 与 hrtimer_is_queued() 中的无锁读取配对 */
WRITE_ONCE(timer->state, HRTIMER_STATE_ENQUEUED);

return timerqueue_add(&base->active, &timer->node);
}

__hrtimer_reprogram 重新编程硬件时钟事件设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void __hrtimer_reprogram(struct hrtimer_cpu_base *cpu_base,
struct hrtimer *next_timer,
ktime_t expires_next)
{
/* 记录下次定时器触发时间 */
cpu_base->expires_next = expires_next;

/*
* 如果高精度定时器未激活或检测到系统挂起,则不重新编程硬件。
这是为了避免在某些特殊情况下错误地重设定时器,比如:
一个定时器(T1)应该在 50ms 后触发。
另一个定时器(T2)应该在 5s 后触发。
如果 T1 被删除,函数可能会重新编程定时器,使其在 5s 后触发。
但如果系统挂起,之后的 hrtimer_start 可能 不会再更新硬件,导致所有定时器一直等待 T2 触发,阻塞整个系统。
*/
if (!hrtimer_hres_active(cpu_base) || cpu_base->hang_detected)
return;

tick_program_event(expires_next, 1);
}

hrtimer_reprogram 在高分辨率定时器(hrtimer)队列中重新编程时钟事件设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/*
* 当 timer 入队并且比已经排队的 timer 更早过期时,我们必须检查它是否比 clock event device 为其准备的 timer 早过期。
*
* 在禁用中断并按住 base->cpu_base.lock 的情况下调用
*/
static void hrtimer_reprogram(struct hrtimer *timer, bool reprogram)
{
struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
struct hrtimer_clock_base *base = timer->base;
/* 调用 hrtimer_get_expires(timer) 获取定时器的绝对到期时间,并减去时钟基准的偏移量(base->offset),计算出相对到期时间(expires) */
ktime_t expires = ktime_sub(hrtimer_get_expires(timer), base->offset);

WARN_ON_ONCE(hrtimer_get_expires_tv64(timer) < 0);

/*
* 如果到期时间小于 0,将其设置为 0,确保不会出现无效的负值
*/
if (expires < 0)
expires = 0;

/* 处理软定时器 */
if (timer->is_soft) {
/*
* 软 hrtimer 可以在远程 CPU 上启动。
* 在这种情况下,需要在远程 CPU 上更新 softirq_expires_next。
*软 hrtimer 不会在远程 CPU 上的第一个硬 hrtimer 之前过期 - hrtimer_check_target() 可以防止这种情况。
*/
struct hrtimer_cpu_base *timer_cpu_base = base->cpu_base;

/* 检查软定时器是否在远程 CPU 上激活
如果是,则无需重新编程*/
if (timer_cpu_base->softirq_activated)
return;

/* 如果当前到期时间晚于远程 CPU 的 softirq_expires_next,也无需重新编程 */
if (!ktime_before(expires, timer_cpu_base->softirq_expires_next))
return;

/* 更新远程 CPU 的下一个软定时器信息,包括 softirq_next_timer 和 softirq_expires_next */
timer_cpu_base->softirq_next_timer = timer;
timer_cpu_base->softirq_expires_next = expires;

/* 如果到期时间晚于远程 CPU 的 expires_next 或者不需要重新编程(!reprogram),直接返回 */
if (!ktime_before(expires, timer_cpu_base->expires_next) ||
!reprogram)
return;
}

/*
* 如果定时器不属于当前 CPU 的时钟基准, 无法重新编程其他 CPU 的时钟事件设备,直接返回
*/
if (base->cpu_base != cpu_base)
return;

/* 如果到期时间晚于当前 CPU 的 expires_next,无需重新编程 */
if (expires >= cpu_base->expires_next)
return;

/*
* 如果当前 CPU 正在处理高分辨率定时器中断(cpu_base->in_hrtirq),无需重新编程,因为中断处理程序会重新评估时钟基准并重新编程时钟事件设备
*/
if (cpu_base->in_hrtirq)
return;

/* 更新当前 CPU 的下一个定时器 */
cpu_base->next_timer = timer;

__hrtimer_reprogram(cpu_base, timer, expires);
}

hrtimer_start_range_ns - 启动高分辨率定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
static int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
u64 delta_ns, const enum hrtimer_mode mode,
struct hrtimer_clock_base *base)
{
struct hrtimer_cpu_base *this_cpu_base = this_cpu_ptr(&hrtimer_bases);
struct hrtimer_clock_base *new_base;
bool force_local, first;

/*
* 如果计时器位于本地 cpu 基上,并且是第一个过期的计时器,那么这最终可能会对硬件重新编程两次(删除时和排队时)。
* 为了通过防止删除时重新编程来避免这种情况,请将 timer 保持在当前 CPU 的本地,
* 并在它排队后强制重新编程,无论它是否再次成为新的第一个过期 timer。
*/
/* 如果定时器当前位于本地 CPU 的定时器基础(cpu_base)上,并且是下一个即将到期的定时器,则可能需要强制将其保留在本地队列 */
force_local = base->cpu_base == this_cpu_base;
force_local &= base->cpu_base->next_timer == timer;

/*
* 如果当前 CPU 已离线(this_cpu_base->online 为 false),则不会强制本地队列
*/
force_local &= this_cpu_base->online;

/*
* 如果定时器已经在队列中,则将其移除。
* 如果定时器是本地 CPU 上的第一个到期定时器,并且启用了 force_local,则跳过硬件重新编程,推迟到稍后处理
*/
remove_hrtimer(timer, base, true, force_local);

/* 如果定时器的模式是相对时间(HRTIMER_MODE_REL),则将当前时间加到目标时间 tim 上,转换为绝对时间 */
if (mode & HRTIMER_MODE_REL)
tim = ktime_add_safe(tim, base->get_time());

/* 如果定时器处于低分辨率模式,则调整目标时间以适应低分辨率的限制 */
tim = hrtimer_update_lowres(timer, tim, mode);

/* 设置定时器的到期时间 tim 和允许的时间范围 delta_ns */
hrtimer_set_expires_range_ns(timer, tim, delta_ns);

/* 如果不强制本地队列,则可能需要将定时器迁移到其他 CPU 的定时器基础 */
if (!force_local) {
new_base = switch_hrtimer_base(timer, base,
mode & HRTIMER_MODE_PINNED);
} else {
/* 如果强制本地队列,则保持在当前基础上 */
new_base = base;
}

/* 将定时器加入到新的定时器基础队列中,并返回是否是第一个到期的定时器 */
first = enqueue_hrtimer(timer, new_base, mode);
if (!force_local) {
/*
* 如果当前 CPU 基处于联机状态,则如果 timer 是远程 CPU 上的第一个过期计时器,则它永远不会在远程 CPU 上排队。
*/
if (hrtimer_base_is_online(this_cpu_base))
return first;

/*
*计时器已远程排队,因为当前基地已脱机。如果定时器是第一个过期的,则踢出远程 CPU 以重新编程时钟事件.
*/
if (first) {
struct hrtimer_cpu_base *new_cpu_base = new_base->cpu_base;

smp_call_function_single_async(new_cpu_base->cpu, &new_cpu_base->csd);
}
return 0;
}

/*
* 如果定时器被强制保留在本地队列中,则强制重新编程硬件时钟,以确保定时器的正确触发
*/
hrtimer_force_reprogram(new_base->cpu_base, 1);
return 0;
}

/**
* hrtimer_start_range_ns - (重新)启动 HRTimter
* @timer:要添加的计时器
* @tim:有效期
* @delta_ns:定时器的 “slack” 范围
* @mode:定时器模式:绝对 (HRTIMER_MODE_ABS) 或
* 相对 (HRTIMER_MODE_REL) 和固定 (HRTIMER_MODE_PINNED);
* 基于 SoftIRQ 的模式仅用于调试目的!
*/
void hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
u64 delta_ns, const enum hrtimer_mode mode)
{
struct hrtimer_clock_base *base;
unsigned long flags;

/*
* 检查 CONFIG_PREEMPT_RT = n 的 HRTIMER_MODE_SOFT 位和 hrtimer.is_soft 是否匹配。
* 使用 PREEMPT_RT 检查硬过期模式,因为未标记的计时器被移动到软中断过期。
*/
if (!IS_ENABLED(CONFIG_PREEMPT_RT))
WARN_ON_ONCE(!(mode & HRTIMER_MODE_SOFT) ^ !timer->is_soft);
else
WARN_ON_ONCE(!(mode & HRTIMER_MODE_HARD) ^ !timer->is_hard);

base = lock_hrtimer_base(timer, &flags);

if (__hrtimer_start_range_ns(timer, tim, delta_ns, mode, base))
hrtimer_reprogram(timer, true);

unlock_hrtimer_base(timer, &flags);
}
EXPORT_SYMBOL_GPL(hrtimer_start_range_ns);

hrtimer_start 重新启动 HRTimter

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* hrtimer_start - (重新)启动 HRTimter
* @timer:要添加的计时器
* @tim:有效期
* @mode:定时器模式:绝对 (HRTIMER_MODE_ABS) 或
* 相对 (HRTIMER_MODE_REL) 和固定 (HRTIMER_MODE_PINNED);
* 基于 SoftIRQ 的模式仅用于调试目的!
*/
static inline void hrtimer_start(struct hrtimer *timer, ktime_t tim,
const enum hrtimer_mode mode)
{
hrtimer_start_range_ns(timer, tim, 0, mode);
}

hrtimer_run_queues - 运行高精度定时器队列

  • 它的核心作用是:在每个jiffy到来时,检查并执行所有已经到期的、不可延迟的硬中断高精度定时器(hrtimer),并负责触发HRTIMER_SOFTIRQ软中断来处理可延迟的软中断定时器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/*
* Is the high resolution mode active ?
*/
static inline int hrtimer_hres_active(struct hrtimer_cpu_base *cpu_base)
{
return IS_ENABLED(CONFIG_HIGH_RES_TIMERS) ?
cpu_base->hres_active : 0;
}

/*
* 由本地的、per-CPU的定时器中断在硬中断(hardirq)上下文中,每个jiffy调用一次。
*/
void hrtimer_run_queues(void)
{
/* cpu_base: 指向当前CPU私有的高精度定时器基座。*/
struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
/* flags: 用于保存spin_lock_irqsave获取锁前的中断状态。*/
unsigned long flags;
/* now: 用于存储当前的高精度时间。*/
ktime_t now;

/*
* 检查当前CPU的高精度定时器是否已经处于激活状态(即高分辨率模式)。
* 如果是,则tick由one-shot hrtimer驱动,不应再执行这里的周期性逻辑。
*/
if (hrtimer_hres_active(cpu_base))
return;

/*
* 这(段代码)是丑陋的:我们必须周期性地检查,我们是否可以切换到
* 高分辨率(highres)和/或无时钟节拍(nohz)模式。时钟源的切换是在
* 持有xtime_lock时发生的。从那里发出的通知只在tick_oneshot代码中
* 设置一个检查位,否则我们可能会与xtime_lock发生死锁。
*/
/*
* 调用tick_check_oneshot_change,检查是否满足切换到高分辨率模式的条件。
* !hrtimer_is_hres_enabled()确保我们只在尚未开启hres时才检查。
*/
if (tick_check_oneshot_change(!hrtimer_is_hres_enabled())) {
/* 如果可以,则调用hrtimer_switch_to_hres()执行模式切换,然后返回。*/
hrtimer_switch_to_hres();
return;
}

/* 获取保护cpu_base数据结构的主自旋锁,并禁用本地中断。*/
raw_spin_lock_irqsave(&cpu_base->lock, flags);
/* 调用hrtimer_update_base获取当前精确时间now,并同步时钟偏移量。*/
now = hrtimer_update_base(cpu_base);

/* 检查当前时间now是否已经到达或超过了下一个软中断定时器的到期时间。*/
if (!ktime_before(now, cpu_base->softirq_expires_next)) {
/* 如果是,则将下一个软中断到期时间设置为无穷大,防止重复触发。*/
cpu_base->softirq_expires_next = KTIME_MAX;
/* 设置“软中断已激活”标志。*/
cpu_base->softirq_activated = 1;
/* 调用raise_timer_softirq设置HRTIMER_SOFTIRQ软中断标志位。*/
raise_timer_softirq(HRTIMER_SOFTIRQ);
}

/*
* 调用核心执行函数__hrtimer_run_queues。
* HRTIMER_ACTIVE_HARD掩码告诉它,这次只处理硬中断上下文的定时器。
*/
__hrtimer_run_queues(cpu_base, now, flags, HRTIMER_ACTIVE_HARD);
/* 释放主自旋锁,并恢复之前的中断状态。*/
raw_spin_unlock_irqrestore(&cpu_base->lock, flags);
}

hrtimer_active: 无锁高精度定时器状态检查

本代码片段提供了一个高性能、无锁的函数 hrtimer_active,用于检查一个高精度定时器(hrtimer)是否处于“激活”状态。根据内核定义,一个定时器在以下三种情况下被认为是激活的:1) 已被添加到其时钟基座(clock base)的红黑树中等待触发;2) 其回调函数正在执行中;3) 正在被迁移到另一个 CPU。此函数的关键设计目标是在保证绝对不会出现“假阴性”(即定时器是激活的但函数返回 false)的前提下,尽可能快地完成检查。

实现原理分析

此函数是内核中无锁编程的经典范例,其核心实现是一种被称为“顺序锁”(Seqlock)的乐观同步技术。它允许多个读取者(hrtimer_active 的调用者)与一个写入者(修改定时器状态的代码)并发执行,读取者通过一个重试循环来确保其读取数据的一致性。

  1. 乐观读取与重试循环: 函数的主体是一个 do-while 循环。它首先“乐观地”读取定时器的状态,不获取任何锁。然后,在循环的末尾,它会验证在读取期间是否有写入者修改了数据。如果检测到冲突,整个读取操作就会被放弃并从头重试。

  2. 顺序锁(Seqlock)的使用:

    • raw_read_seqcount_begin(&base->seq): 在读取任何受保护的数据之前,此宏会读取当前时钟基座 base 的序列计数器 seq 的值。
    • 数据读取: 随后,代码读取 timer->statebase->running 这两个关键状态。
    • read_seqcount_retry(&base->seq, seq): 在读取完成后,此宏会再次检查序列计数器。如果计数器的值与开始时读取的 seq 不一致,或者如果计数器的值是奇数(表示写入者正在进行写操作),则意味着在读取期间数据可能已被修改,处于不一致的状态。此时,read_seqcount_retry 返回 true,导致循环重试。
  3. 处理定时器迁移: 除了 seqlock,循环的退出条件还有一个 base != READ_ONCE(timer->base) 的检查。这是一个处理定时器跨 CPU 迁移的关键细节。在读取 seq 和检查状态之间,定时器可能被迁移到了一个新的 base。如果发生这种情况,我们之前基于旧 base 的 seqlock 检查就变得毫无意义。因此,必须确认 timer->base 指针在整个过程中没有改变。如果改变了,也必须重试整个循环,以从新的 base 开始检查。

  4. READ_ONCE: READ_ONCE 宏的使用确保了编译器不会对指针的读取进行优化(如缓存到寄存器中),而是每次都从内存中真实地加载 timer->base 的值,这对于正确检测并发修改至关重要。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* hrtimer_active - 检查一个高精度定时器是否处于激活状态。
* @timer: 指向待检查的 hrtimer 结构体的常量指针。
*
* 当一个定时器被加入到红黑树中,或者其回调函数正在运行,
* 或者它正处于被迁移到另一个CPU的状态时,它就被认为是“激活的”。
*
* 此函数必须确保不会返回“假阴性”(即定时器是激活的但返回false)。
*
* 返回值:
* true: 如果定时器是激活的。
* false: 如果定时器是未激活的。
*/
bool hrtimer_active(const struct hrtimer *timer)
{
struct hrtimer_clock_base *base;
unsigned int seq;

// 使用do-while循环和seqlock来实现无锁读取,以处理并发修改。
do {
// 使用READ_ONCE确保从内存中原子地读取timer->base指针,防止编译器优化。
base = READ_ONCE(timer->base);
// 开始一个seqlock的读序列,获取当前的序列号。
seq = raw_read_seqcount_begin(&base->seq);

// 检查定时器的状态。如果状态不是“未激活”,或者定时器是当前基座上
// 正在运行的那个,那么它就是激活的。
if (timer->state != HRTIMER_STATE_INACTIVE ||
base->running == timer)
return true;

// 循环的退出条件,有两个:
// 1. read_seqcount_retry: 检查在读取期间seqlock序列号是否被修改。
// 如果被修改,说明有写入者操作了数据,读取可能不一致,需要重试。
// 2. base != READ_ONCE(timer->base): 检查定时器的基座指针是否被修改。
// 如果定时器被迁移到了另一个CPU,基座会改变,也需要重试。
} while (read_seqcount_retry(&base->seq, seq) ||
base != READ_ONCE(timer->base));

// 如果循环正常退出(没有检测到并发写入),则说明定时器确实是未激活的。
return false;
}
// 将 hrtimer_active 函数导出,使其可以被其他内核模块调用。
EXPORT_SYMBOL_GPL(hrtimer_active);

高精度定时器的取消与同步 (hrtimer_cancel, hrtimer_try_to_cancel)

本代码片段是 Linux 内核高精度定时器(hrtimer)子系统的核心组成部分,提供了两种用于取消(停止)一个已启动定时器的关键函数。hrtimer_try_to_cancel 是一个非阻塞的尝试取消操作,如果定时器的回调函数正在执行,它会立即失败并返回。hrtimer_cancel 则是一个阻塞的、保证成功(除非定时器未激活)的取消操作,它会在回调函数运行时进行等待,直至回调执行完毕且定时器被成功移除。

实现原理分析

这两个函数的核心是处理一个关键的竞态条件:当一个任务试图取消定时器时,该定时器可能恰好到期,其回调函数正在被或即将被另一个上下文(通常是软中断)执行。

  1. 无锁状态检查 (hrtimer_active): hrtimer_try_to_cancel 的第一步是调用 hrtimer_active(timer)。这是一个无锁的优化检查,它通过读取定时器的状态位来判断定时器是否可能处于“激活”(已入队或正在运行回调)状态。如果定时器未激活,函数可以安全地提前返回,避免了获取重量级锁的开销。

  2. 基于锁的原子移除: 如果定时器是激活的,函数必须通过 lock_hrtimer_base 获取保护该定时器所在时钟基座(clock base)的自旋锁。这个锁保护了管理 hrtimer 的红黑树数据结构。在锁的保护下,代码通过 hrtimer_callback_running(timer) 再次精确检查回调函数是否正在运行。如果回调没有运行,remove_hrtimer 函数会被调用,它会将定时器从红黑树中安全地移除,完成取消操作。

  3. 阻塞式重试机制 (hrtimer_cancel): hrtimer_cancel 的实现体现了“循环-尝试-等待”的设计模式。它在一个 do-while 循环中反复调用 hrtimer_try_to_cancel

    • 如果 hrtimer_try_to_cancel 返回 >= 0,表示取消成功或定时器本就未激活,循环结束。
    • 如果返回 -1,则说明遇到了回调函数正在运行的竞态。此时,hrtimer_cancel_wait_running(timer) 会被调用。这个函数会进入一个忙等待循环(通常使用 cpu_relax()),等待定时器的“回调运行中”标志位被清除。重要的是,在等待期间,锁已被释放,中断也是开启的,这使得执行回调的软中断上下文能够正常完成其工作。一旦回调结束,do-while 循环会进行下一次尝试,这次 hrtimer_try_to_cancel 就能够成功了。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
static inline void hrtimer_cancel_wait_running(struct hrtimer *timer)
{
cpu_relax();
}

// hrtimer_cancel: 取消一个高精度定时器,并等待其回调函数(如果正在运行)执行完毕。
// @timer: 指向需要被取消的 hrtimer 结构体的指针。
// 返回值:
// 0: 如果定时器本就未激活。
// 1: 如果定时器被成功取消。
int hrtimer_cancel(struct hrtimer *timer)
{
int ret;

// 使用 do-while 循环,以处理回调函数正在运行的竞态条件。
do {
// 尝试取消定时器。这是一个非阻塞操作。
ret = hrtimer_try_to_cancel(timer);

// 如果 hrtimer_try_to_cancel 返回-1,表示定时器的回调函数正在执行。
if (ret < 0)
// 在这种情况下,调用此函数进行忙等待,直到回调函数执行完毕。
hrtimer_cancel_wait_running(timer);
// 只要 ret < 0,就持续循环。
} while (ret < 0);
return ret;
}
// 将 hrtimer_cancel 函数导出,使其可以被其他内核模块调用。
EXPORT_SYMBOL_GPL(hrtimer_cancel);

/*
* Helper function to check, whether the timer is running the callback
* function
*/
static inline int hrtimer_callback_running(struct hrtimer *timer)
{
return timer->base->running == timer;
}

// hrtimer_try_to_cancel: 尝试停用一个高精度定时器。
// @timer: 指向需要被停止的 hrtimer 结构体的指针。
// 返回值:
// 0: 如果定时器本就未激活。
// 1: 如果定时器被成功取消。
// -1: 如果定时器的回调函数正在执行,因此无法立即停止。
int hrtimer_try_to_cancel(struct hrtimer *timer)
{
struct hrtimer_clock_base *base; // 指向定时器所属时钟基座的指针。
unsigned long flags; // 用于保存中断状态。
int ret = -1; // 默认返回值为-1,表示回调正在运行。

/*
* 首先进行无锁检查。如果定时器未激活(既未入队,也未运行回调),
* 则此处无需任何操作。基座锁不能序列化并发的入队操作,
* 所以我们可以避免获取它。
*/
if (!hrtimer_active(timer))
return 0;

// 获取保护定时器基座的锁,并保存/禁用中断。
base = lock_hrtimer_base(timer, &flags);

// 在锁的保护下,再次检查回调函数是否正在运行。
if (!hrtimer_callback_running(timer))
// 如果回调没有运行,则调用 remove_hrtimer 将其从队列中移除。
// remove_hrtimer 会返回1表示成功移除。
ret = remove_hrtimer(timer, base, false, false);

// 释放定时器基座的锁,并恢复中断状态。
unlock_hrtimer_base(timer, &flags);

return ret;

}
// 将 hrtimer_try_to_cancel 函数导出。
EXPORT_SYMBOL_GPL(hrtimer_try_to_cancel);