title: timer
categories:
- linux
- kernel
- time
tags: - linux
- kernel
- time
abbrlink: 6ad0de1a
date: 2025-10-03 09:01:49

文章目录
- [kernel/time/timer.c 传统内核定时器(Legacy Kernel Timers) 基于jiffies的通用定时机制](#kernel/time/timer.c 传统内核定时器(Legacy Kernel Timers) 基于jiffies的通用定时机制)
- include/linux/timer.h
-
- [timer_setup 初始化计时器](#timer_setup 初始化计时器)
- [timer_pending 检查计时器是否处于挂起状态](#timer_pending 检查计时器是否处于挂起状态)
- [timer_setup_on_stack 堆上的计时器设置](#timer_setup_on_stack 堆上的计时器设置)
- kernel/time/timer.c
-
- [do_init_timer 初始化定时器](#do_init_timer 初始化定时器)
- [init_timer_key 初始化定时器key](#init_timer_key 初始化定时器key)
- [get_timer_base 获取计时器基础](#get_timer_base 获取计时器基础)
- [lock_timer_base 锁定计时器基础](#lock_timer_base 锁定计时器基础)
- [forward_timer_base 更新定时器基础(timer_base)的时钟值(clk)](#forward_timer_base 更新定时器基础(timer_base)的时钟值(clk))
- [timer_get_idx 提取定时器在时间轮数组中的索引](#timer_get_idx 提取定时器在时间轮数组中的索引)
- [timer_set_idx 设置定时器在时间轮数组中的索引](#timer_set_idx 设置定时器在时间轮数组中的索引)
- [calc_wheel_index 计算时间轮索引](#calc_wheel_index 计算时间轮索引)
- [detach_timer 从时间轮中移除定时器](#detach_timer 从时间轮中移除定时器)
- [detach_if_pending 从时间轮(timer wheel)中移除挂起状态的定时器(timer_list)](#detach_if_pending 从时间轮(timer wheel)中移除挂起状态的定时器(timer_list))
- [trigger_dyntick_cpu 在必要时唤醒目标 CPU,以确保定时器(timer_list)能够及时处理](#trigger_dyntick_cpu 在必要时唤醒目标 CPU,以确保定时器(timer_list)能够及时处理)
- [enqueue_timer 将定时器(timer_list)加入时间轮(timer wheel)的哈希桶中,并更新相关的状态信息](#enqueue_timer 将定时器(timer_list)加入时间轮(timer wheel)的哈希桶中,并更新相关的状态信息)
- [internal_add_timer 添加定时器到时间轮](#internal_add_timer 添加定时器到时间轮)
- [__mod_timer 修改计时器的超时时间](#__mod_timer 修改计时器的超时时间)
- [mod_timer 修改计时器的超时时间](#mod_timer 修改计时器的超时时间)
- [add_timer 启动一个定时器](#add_timer 启动一个定时器)
- [add_timer_global 在未设置TIMER_PINNED标志的情况下启动计时器](#add_timer_global 在未设置TIMER_PINNED标志的情况下启动计时器)
- [add_timer_on 在特定 CPU 上启动计时器](#add_timer_on 在特定 CPU 上启动计时器)
- [__try_to_del_timer_sync 内部功能:尝试停用计时器](#__try_to_del_timer_sync 内部功能:尝试停用计时器)
- [__timer_delete_sync 内部功能:停用计时器并等待处理程序完成](#__timer_delete_sync 内部功能:停用计时器并等待处理程序完成)
- [timer_delete_sync 停用计时器并等待处理程序完成](#timer_delete_sync 停用计时器并等待处理程序完成)
- [__timer_delete - 内部函数:停用计时器](#__timer_delete - 内部函数:停用计时器)
- [timer_delete 停用计时器](#timer_delete 停用计时器)
- [init_timers 初始化定时器](#init_timers 初始化定时器)
- [timer_init_key 初始化定时器](#timer_init_key 初始化定时器)
- [collect_expired_timers 收集已到期的定时器](#collect_expired_timers 收集已到期的定时器)
- [timer_recalc_next_expiry 重新计算下一个到期时间](#timer_recalc_next_expiry 重新计算下一个到期时间)
- [call_timer_fn 调用定时器回调函数](#call_timer_fn 调用定时器回调函数)
- [expire_timers 执行已到期的定时器](#expire_timers 执行已到期的定时器)
- [run_timer_base 运行定时器基座](#run_timer_base 运行定时器基座)
- [run_timer_softirq 运行定时器软中断](#run_timer_softirq 运行定时器软中断)
- [run_local_timers 运行本地定时器](#run_local_timers 运行本地定时器)
- [update_process_times 更新进程时间](#update_process_times 更新进程时间)
kernel/time/timer.c 传统内核定时器(Legacy Kernel Timers) 基于jiffies的通用定时机制
历史与背景
这项技术是为了解决什么特定问题而诞生的?
kernel/time/timer.c 实现了Linux内核中最传统、最通用的低分辨率定时器 机制。它的诞生是为了解决内核中一个无处不在的需求:以一种低开销、非阻塞的方式,安排一个函数在未来的某个时间点被执行。
在内核的许多代码路径中,尤其是在与硬件交互时,都需要处理超时。例如:
- 设备驱动:一个驱动向设备发送一个命令后,不能无限期地等待其响应。它需要设置一个超时,如果在指定时间内没有收到响应,就必须进行错误处理。
- 网络协议栈:TCP协议在发送一个数据段后,会启动一个重传定时器。如果在规定时间内没有收到对方的确认(ACK),定时器到期后就会重新发送该数据段。
- 周期性轮询:某些简单的驱动可能需要周期性地检查硬件状态。
timer.c 提供的struct timer_list接口,就是为了满足这些**对精度要求不高(毫秒级)**的通用定时需求而设计的。
它的发展经历了哪些重要的里程碑或版本迭代?
传统定时器是内核最古老的组件之一,其实现为了追求性能和扩展性,经历了重要的架构演进。
- 从排序列表到定时器轮 :最早的实现可能是一个简单的、按到期时间排序的链表。这种方式的缺点是,每次添加一个新的定时器,都需要遍历列表以找到正确的位置,其开销与已存在的定时器数量成正比(O(N)),性能很差。为了解决这个问题,内核引入了定时器轮(Timer Wheels)算法。这是一个极其重要的里程碑,它使得添加、删除和处理到期定时器的平均开销都变为了常数时间 O(1)。
- Per-CPU定时器轮 :在多核(SMP)系统上,如果所有CPU共享一个全局的定时器轮,那么保护这个数据结构的锁就会成为一个巨大的性能瓶颈。因此,内核将定时器轮的实现改为了per-CPU,即每个CPU核心都有自己独立的一套定时器轮。一个定时器总是在启动它的那个CPU上到期,这完全消除了锁争用,极大地提高了扩展性。
- 与
NO_HZ(无滴答内核)的适配 :在Tickless内核中,空闲的CPU会停止周期性的时钟中断。timer.c的实现需要与此适配。当CPU进入空闲时,它会计算出其定时器轮上最早到期的那个定时器的时间,并以此来编程硬件,确保CPU能被准时唤醒来处理它,而不是被周期性地无效唤醒。
目前该技术的社区活跃度和主流应用情况如何?
timer_list定时器是内核中一个极其稳定、成熟的"主力"组件。尽管有了更高精度的hrtimer,但由于其极低的运行时开销 ,它仍然是所有非精度敏感 定时场景的首选。
- 主流应用 :
- 网络协议栈中的绝大多数超时。
- 块设备层的请求超时。
- 绝大多数设备驱动中的硬件命令超时。
核心原理与设计
它的核心工作原理是什么?
timer.c 的核心是基于jiffies的per-CPU定时器轮算法。
-
核心数据结构
struct timer_list:- 这是开发者使用的API结构。它包含:
expires:一个unsigned long,存储了定时器到期的jiffies值。function:一个函数指针,指向定时器到期时要执行的回调函数。data:一个长整型,用于向回调函数传递私有数据。
- 这是开发者使用的API结构。它包含:
-
定时器轮 (Timer Wheel):
- 可以将其想象成一个哈希表,其哈希键是
expires(到期时间)。 - 它是一个由多个链表头组成的数组。当一个定时器被添加时(
add_timer()),内核会根据其expires值计算出一个索引,然后将该定时器挂载到对应索引的链表中。 - 为了处理长时间的定时,这个轮子通常是级联的(cascading),类似于钟表的秒针、分针和时针。有多个不同粒度的轮子,当一个低粒度的轮子转完一整圈时,它会"进位"并处理更高粒度轮子中的一个"桶"。这避免了需要一个极其巨大的、包含所有未来时间点的数组。
- 可以将其想象成一个哈希表,其哈希键是
-
时钟中断驱动:
- 内核的周期性时钟中断(tick)是驱动整个机制的"马达"。
- 在每次时钟中断发生时,
jiffies计数器会加一。 - 中断处理程序会检查当前CPU的定时器轮,看看是否有对应于当前
jiffies值的"桶"。 - 如果有,它会取出该桶中的所有到期定时器,并依次执行它们的回调函数。
-
执行上下文:
- 定时器的回调函数通常在软中断(softirq)上下文 中执行。这意味着它们在执行时不能睡眠(例如,不能调用
kmalloc(GFP_KERNEL)或获取mutex)。
- 定时器的回调函数通常在软中断(softirq)上下文 中执行。这意味着它们在执行时不能睡眠(例如,不能调用
它的主要优势体现在哪些方面?
- 极高的效率 :添加(
add_timer)、删除(del_timer)和处理到期定时器的平均时间复杂度都是O(1),与系统中定时器的总数无关。 - 低开销:数据结构简单,每次中断的处理开销非常小。
- 出色的多核扩展性:per-CPU的设计避免了锁争用。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 低分辨率 :这是其最根本的限制。它的精度完全受限于
jiffies的粒度(即1/HZ秒)。例如,在HZ=250的系统上,其精度只有4毫秒。 - 定时器合并(Coalescing) :所有到期时间落在同一个
jiffies滴答内的定时器,会被认为是同时到期的,并且它们回调函数的执行顺序是不保证的。 - 执行上下文限制:在软中断上下文中运行,限制了回调函数中可以执行的操作。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
timer_list是所有对精度要求不高(容忍几毫秒的误差)、非硬实时的内核定时场景的首选解决方案。
- 网络超时 :TCP重传超时通常是几百毫秒,使用
timer_list完美且高效。 - 设备轮询:一个驱动需要每秒检查几次设备状态。
- 延迟工作:需要延迟几十毫秒再执行某个操作。
是否有不推荐使用该技术的场景?为什么?
- 高精度定时 :任何需要亚毫秒级精度的场景(如
nanosleep的实现、专业音频应用),都必须 使用高精度定时器(hrtimers) 。使用timer_list无法满足需求。 - 需要极短的超时 :尝试设置一个小于
1/HZ秒的超时是无意义的,它至少要在一个jiffies滴答后才能被处理。 - 回调函数需要睡眠 :如果回调函数中必须执行可能导致睡眠的操作,那么不能直接使用
timer_list。一种常见的模式是在定时器回调函数中唤醒一个内核线程(kthread),让该线程在进程上下文中去执行需要睡眠的工作。
对比分析
请将其 与 其他相似技术 进行详细对比。
| 特性 | timer_list (传统定时器) |
hrtimer (高精度定时器) |
schedule_work / tasklet (延迟工作) |
|---|---|---|---|
| 核心用途 | 低精度 的定时执行。 | 高精度 的定时执行。 | 异步 、延迟执行(不保证精确时间)。 |
| 时间基准 | jiffies |
ktime (纳秒) |
N/A (通常是"尽快"或"稍后") |
| 分辨率 | 低 (毫秒级, 由HZ决定)。 |
高 (纳秒级)。 | 不保证时间,只保证执行时机。 |
| 开销 | 低。O(1)的定时器轮。 | 较高。O(log N)的红黑树和硬件编程。 | 中等。 |
| 执行上下文 | 软中断 (softirq)。 | 硬中断或软中断。 | 进程上下文 (workqueue) 或 软中断 (tasklet)。 |
| 是否可睡眠 | 否 | 否 | 是 (仅限workqueue)。 |
| 适用场景 | 通用的、非精度敏感的超时。 | nanosleep、实时应用、精确超时。 |
将中断处理的下半部分延迟,或在进程上下文中执行异步任务。 |
include/linux/timer.h
timer_setup 初始化计时器
c
/**
* init_timer_key - 初始化计时器
* @timer:需要初始化的定时器
* @func:定时器回调函数
* @flags:计时器标志
* @name:计时器的名称
* @key:用于跟踪定时器同步锁依赖关系的假锁的 lockdep 类键
*
* init_timer_key() 必须在调用 *任何* 其他计时器函数之前对计时器执行。
*/
void init_timer_key(struct timer_list *timer,
void (*func)(struct timer_list *), unsigned int flags,
const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, func, flags, name, key);
}
EXPORT_SYMBOL(init_timer_key);
#define __init_timer(_timer, _fn, _flags) \
init_timer_key((_timer), (_fn), (_flags), NULL, NULL)
#define __init_timer_on_stack(_timer, _fn, _flags) \
init_timer_on_stack_key((_timer), (_fn), (_flags), NULL, NULL)
/**
*timer_setup - 准备一个首次使用的计时器
* @timer:有问题的计时器
* @callback:定时器过期时要调用的函数
* @flags:任何 TIMER_* 标志
*
* 常规计时器初始化应使用上面的 DEFINE_TIMER() 或 timer_setup()。
* 对于堆栈上的计时器,必须使用 timer_setup_on_stack(),并且必须通过调用 destroy_timer_on_stack() 来平衡。
*/
#define timer_setup(timer, callback, flags) \
__init_timer((timer), (callback), (flags))\
timer_pending 检查计时器是否处于挂起状态
c
/**
* timer_pending - 计时器是否待处理?
* @timer:有问题的计时器
*
* timer_pending 将判断给定的计时器当前是否处于待处理状态。
* 调用方必须确保序列化 wrt.对此计时器执行的其他作,例如。中断上下文或 SMP 上的其他 CPU。
*
* 返回:如果计时器处于待处理状态,则为 1,否则为 0。
*/
static inline int timer_pending(const struct timer_list * timer)
{
/* 如果链表节点未被哈希(即未挂起),函数返回 0;否则返回 1 */
return !hlist_unhashed_lockless(&timer->entry);
}
timer_setup_on_stack 堆上的计时器设置
c
static inline void timer_init_key_on_stack(struct timer_list *timer,
void (*func)(struct timer_list *),
unsigned int flags,
const char *name,
struct lock_class_key *key)
{
timer_init_key(timer, func, flags, name, key);
}
#define __timer_init_on_stack(_timer, _fn, _flags) \
timer_init_key_on_stack((_timer), (_fn), (_flags), NULL, NULL)
#define timer_setup_on_stack(timer, callback, flags) \
__timer_init_on_stack((timer), (callback), (flags))
kernel/time/timer.c
do_init_timer 初始化定时器
c
static void do_init_timer(struct timer_list *timer,
void (*func)(struct timer_list *),
unsigned int flags,
const char *name, struct lock_class_key *key)
{
timer->entry.pprev = NULL;
timer->function = func;
if (WARN_ON_ONCE(flags & ~TIMER_INIT_FLAGS))
flags &= TIMER_INIT_FLAGS;
timer->flags = flags | raw_smp_processor_id();
lockdep_init_map(&timer->lockdep_map, name, key, 0);
}
init_timer_key 初始化定时器key
c
/**
* init_timer_key - 初始化计时器
* @timer:需要初始化的定时器
* @func:定时器回调函数
* @flags:计时器标志
* @name:计时器的名称
* @key:用于跟踪定时器的假锁的 lockdep 类 key
* 同步锁定依赖项
*
* init_timer_key() 必须在调用 *任何* 其他计时器函数之前对计时器执行。
*/
void init_timer_key(struct timer_list *timer,
void (*func)(struct timer_list *), unsigned int flags,
const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, func, flags, name, key);
}
EXPORT_SYMBOL(init_timer_key);
get_timer_base 获取计时器基础
c
#ifdef CONFIG_NO_HZ_COMMON
/*
* 如果需要锁定多个 base,请使用 base 顺序进行锁定嵌套,即最小数字在前。
*/
# define NR_BASES 3
# define BASE_LOCAL 0
# define BASE_GLOBAL 1
# define BASE_DEF 2
#else
# define NR_BASES 1
# define BASE_LOCAL 0
# define BASE_GLOBAL 0
# define BASE_DEF 0
#endif
static DEFINE_PER_CPU(struct timer_base, timer_bases[NR_BASES]);
static inline struct timer_base *get_timer_cpu_base(u32 tflags, u32 cpu)
{
/* 设置了 TIMER_PINNED 标志,表示定时器被绑定到特定的 CPU,选择本地定时器基础(BASE_LOCAL)。
如果未设置 TIMER_PINNED 标志,选择全局定时器基础(BASE_GLOBAL) */
int index = tflags & TIMER_PINNED ? BASE_LOCAL : BASE_GLOBAL;
/*
* 如果计时器是可延迟的并且设置了 NO_HZ_COMMON,那么我们需要使用可延迟的基数。
*/
if (IS_ENABLED(CONFIG_NO_HZ_COMMON) && (tflags & TIMER_DEFERRABLE))
index = BASE_DEF;
return per_cpu_ptr(&timer_bases[index], cpu);
}
static inline struct timer_base *get_timer_base(u32 tflags)
{
return get_timer_cpu_base(tflags, tflags & TIMER_CPUMASK);
}
lock_timer_base 锁定计时器基础
c
/*
* 我们正在使用哈希锁定:按住 per_cpu(timer_bases[x]).lock 意味着所有绑定到这个 base 的计时器都被锁定,base 本身也被锁定。
*
* 所以 __run_timers/migrate_timers 可以安全地修改所有可以在 base->vectors 数组中找到的计时器。
*
* 当计时器正在迁移时,会设置TIMER_MIGRATING标志,我们需要等到迁移完成。
*/
static struct timer_base *lock_timer_base(struct timer_list *timer,
unsigned long *flags)
__acquires(timer->base->lock)
{
for (;;) {
struct timer_base *base;
u32 tf;
/*
* We need to use READ_ONCE() here, otherwise the compiler
* might re-read @tf between the check for TIMER_MIGRATING
* and spin_lock().
*/
tf = READ_ONCE(timer->flags);
/* 未设置 TIMER_MIGRATING 标志,表示定时器未处于迁移状态,可以安全地获取其基础 */
if (!(tf & TIMER_MIGRATING)) {
base = get_timer_base(tf);
raw_spin_lock_irqsave(&base->lock, *flags);
/* 再次检查定时器的标志位是否未发生变化。如果标志位一致,返回定时器基础;否则释放锁并重新尝试 */
if (timer->flags == tf)
return base;
raw_spin_unlock_irqrestore(&base->lock, *flags);
}
/* 调用 cpu_relax() 让出 CPU,避免忙等待。 */
cpu_relax();
}
}
forward_timer_base 更新定时器基础(timer_base)的时钟值(clk)
c
static inline void __forward_timer_base(struct timer_base *base,
unsigned long basej)
{
/*
* 检查我们是否可以转发基地。
* 我们只能在 @basej 超过 base->clk 时执行此作,否则我们可能会倒带 base->clk.
*/
/* 传入的时间值(basej)小于或等于当前时钟值(base->clk),直接返回 */
if (time_before_eq(basej, base->clk))
return;
/*
* 定时器的下一个到期时间(next_expiry)大于传入的时间值(basej),将时钟值更新为 basej
*/
if (time_after(base->next_expiry, basej)) {
base->clk = basej;
} else {
if (WARN_ON_ONCE(time_before(base->next_expiry, base->clk)))
return;
base->clk = base->next_expiry;
}
}
static inline void forward_timer_base(struct timer_base *base)
{
__forward_timer_base(base, READ_ONCE(jiffies));
}
timer_get_idx 提取定时器在时间轮数组中的索引
c
/*
TIMER_DEFERRABLE:
表示可延迟的定时器。
当系统繁忙时,该定时器会正常工作,但不会唤醒处于空闲状态的 CPU 来处理它。
定时器会在 CPU 因其他非延迟定时器唤醒时被处理。
这种设计优化了系统的功耗,适用于非紧急任务。
TIMER_IRQSAFE:
表示中断安全的定时器。
定时器的回调函数在中断被禁用的情况下执行,确保可以安全地从中断处理程序中等待定时器完成,例如通过调用 timer_delete_sync()。
这种中断禁用的回调执行仅用于解决工作队列锁定问题,而不是用于执行任意任务。滥用行为会被监控。
TIMER_PINNED:
表示绑定到特定 CPU 的定时器。
定时器会始终在其入队的 CPU 上到期。
如果需要将定时器绑定到特定 CPU,必须使用 add_timer_on()。
使用 mod_timer() 和 add_timer() 时,定时器总是入队到本地 CPU。
*/
#define TIMER_CPUMASK 0x0003FFFF
#define TIMER_MIGRATING 0x00040000
#define TIMER_BASEMASK (TIMER_CPUMASK | TIMER_MIGRATING)
#define TIMER_DEFERRABLE 0x00080000
#define TIMER_PINNED 0x00100000
#define TIMER_IRQSAFE 0x00200000
#define TIMER_INIT_FLAGS (TIMER_DEFERRABLE | TIMER_PINNED | TIMER_IRQSAFE)
#define TIMER_ARRAYSHIFT 22
#define TIMER_ARRAYMASK 0xFFC00000
static inline unsigned int timer_get_idx(struct timer_list *timer)
{
return (timer->flags & TIMER_ARRAYMASK) >> TIMER_ARRAYSHIFT;
}
timer_set_idx 设置定时器在时间轮数组中的索引
c
static inline void timer_set_idx(struct timer_list *timer, unsigned int idx)
{
timer->flags = (timer->flags & ~TIMER_ARRAYMASK) |
idx << TIMER_ARRAYSHIFT;
}
calc_wheel_index 计算时间轮索引
- 时间轮是一种高效的定时器管理结构,通过分层的时间粒度组织定时器,减少调度开销并优化性能。代码定义了时间轮的层级、粒度、范围以及相关的宏,用于计算定时器的存储桶(bucket)位置。
- 层级和粒度:
- 时间轮由多个层级(LVL_DEPTH)组成,每个层级包含固定数量的存储桶(LVL_SIZE)。
- 每个层级的时间粒度不同,粒度随着层级的增加而变大。
- 粒度公式:LVL_CLK_DIV ^ level,其中 LVL_CLK_DIV 是层级间的时钟除数
- 层级的作用:
- 定时器的到期时间决定其所属的层级。到期时间越远,定时器被分配到粒度更大的层级。
- 这种设计优化了大多数定时器的使用场景(如网络超时和磁盘 I/O),因为大多数定时器在到期前会被取消。
- 优化设计
- 去除级联操作:
- 与传统时间轮不同,该实现去除了定时器的级联操作(recascading)。
- 定时器不会从高层级重新分配到低层级,而是直接在其所属层级中管理。
- 批量处理:
- 时间轮的粒度提供了隐式的批量处理能力,减少了定时器的调度开销。
- 对于超时定时器,轻微的触发延迟通常不会影响系统的正常运行。
- 特殊情况处理:
- 对于小到期时间的定时器(如网络定时器),它们被分配到粒度最小的层级(第 0 层),以确保精确性。
- 超过最后层级容量的定时器会被强制设置为最大超时时间。
- 时间轮的层级和范围
代码提供了不同系统时钟频率(HZ)下时间轮的层级、粒度和范围示例:
- HZ = 1000:
- 第 0 层粒度为 1 毫秒,范围为 0 毫秒到 63 毫秒。
- 第 8 层粒度为 16777216 毫秒(约 4 小时),范围为 134217728 毫秒(约 1 天)到 1073741822 毫秒(约 12 天)。
- HZ = 300、HZ = 250 和 HZ = 100:
- 粒度和范围随着时钟频率的降低而变大。
- 例如,HZ = 100 时,第 0 层粒度为 10 毫秒,范围为 0 毫秒到 630 毫秒。
c
/*
* 计时器轮具有 LVL_DEPTH 个数组级别。每个级别都提供一组 LVL_SIZE 存储桶。每个级别都由自己的 clock 驱动,因此每个级别都有不同的粒度。
*
* The level granularity is: LVL_CLK_DIV ^ level
* The level clock frequency is: HZ / (LVL_CLK_DIV ^ level)
*
* 新武装计时器的数组级别取决于相对到期时间。过期时间越远,数组级别越高,因此粒度就越大。
*
* 与旨在 timers"精确"过期的原始 timer wheel 实现相反,此实现消除了将 timers 重新级联到较低数组级别的需要。内核的先前 'classic' 计时器轮实现已经违反了 'exact' 过期,因为它在过期时间中添加 slack 以提供批量过期。粒度级别提供隐式批处理。
*
* 这是对大多数计时器轮用例的原始计时器轮实现的优化:超时。绝大多数超时计时器(网络、磁盘 I/O 等)在到期前被取消。如果超时到期,则表示正常运行受到干扰,因此超时是否带有轻微延迟并不重要。
*
* 唯一的例外是过期时间较短的网络计时器。他们依赖于粒度。这些值适合第一个 wheel 级别,该级别具有 HZ 粒度。
*
* 我们不再有级联。过期时间高于 last wheel 级别容量的计时器将在 last wheel 级别的最大超时值处强制过期。从数据采样中,我们知道观察到的最大值为 5 天(网络连接跟踪),因此这应该不是问题。
*
* 当前选择的数组常量值是数组大小和粒度之间的良好折衷。
*
* 这将产生以下粒度和范围级别:
*
* HZ 1000 steps
* Level Offset 粒度 Range
* 0 0 1 ms 0 ms - 63 ms
* 1 64 8 ms 64 ms - 511 ms
* 2 128 64 ms 512 ms - 4095 ms (512ms - ~4s)
* 3 192 512 ms 4096 ms - 32767 ms (~4s - ~32s)
* 4 256 4096 ms (~4s) 32768 ms - 262143 ms (~32s - ~4m)
* 5 320 32768 ms (~32s) 262144 ms - 2097151 ms (~4m - ~34m)
* 6 384 262144 ms (~4m) 2097152 ms - 16777215 ms (~34m - ~4h)
* 7 448 2097152 ms (~34m) 16777216 ms - 134217727 ms (~4h - ~1d)
* 8 512 16777216 ms (~4h) 134217728 ms - 1073741822 ms (~1d - ~12d)
*
* HZ 300
* Level Offset Granularity Range
* 0 0 3 ms 0 ms - 210 ms
* 1 64 26 ms 213 ms - 1703 ms (213ms - ~1s)
* 2 128 213 ms 1706 ms - 13650 ms (~1s - ~13s)
* 3 192 1706 ms (~1s) 13653 ms - 109223 ms (~13s - ~1m)
* 4 256 13653 ms (~13s) 109226 ms - 873810 ms (~1m - ~14m)
* 5 320 109226 ms (~1m) 873813 ms - 6990503 ms (~14m - ~1h)
* 6 384 873813 ms (~14m) 6990506 ms - 55924050 ms (~1h - ~15h)
* 7 448 6990506 ms (~1h) 55924053 ms - 447392423 ms (~15h - ~5d)
* 8 512 55924053 ms (~15h) 447392426 ms - 3579139406 ms (~5d - ~41d)
*
* HZ 250
* Level Offset Granularity Range
* 0 0 4 ms 0 ms - 255 ms
* 1 64 32 ms 256 ms - 2047 ms (256ms - ~2s)
* 2 128 256 ms 2048 ms - 16383 ms (~2s - ~16s)
* 3 192 2048 ms (~2s) 16384 ms - 131071 ms (~16s - ~2m)
* 4 256 16384 ms (~16s) 131072 ms - 1048575 ms (~2m - ~17m)
* 5 320 131072 ms (~2m) 1048576 ms - 8388607 ms (~17m - ~2h)
* 6 384 1048576 ms (~17m) 8388608 ms - 67108863 ms (~2h - ~18h)
* 7 448 8388608 ms (~2h) 67108864 ms - 536870911 ms (~18h - ~6d)
* 8 512 67108864 ms (~18h) 536870912 ms - 4294967288 ms (~6d - ~49d)
*
* HZ 100
* Level Offset Granularity Range
* 0 0 10 ms 0 ms - 630 ms
* 1 64 80 ms 640 ms - 5110 ms (640ms - ~5s)
* 2 128 640 ms 5120 ms - 40950 ms (~5s - ~40s)
* 3 192 5120 ms (~5s) 40960 ms - 327670 ms (~40s - ~5m)
* 4 256 40960 ms (~40s) 327680 ms - 2621430 ms (~5m - ~43m)
* 5 320 327680 ms (~5m) 2621440 ms - 20971510 ms (~43m - ~5h)
* 6 384 2621440 ms (~43m) 20971520 ms - 167772150 ms (~5h - ~1d)
* 7 448 20971520 ms (~5h) 167772160 ms - 1342177270 ms (~1d - ~15d)
*/
/* 下一级别的时钟除数 */
#define LVL_CLK_SHIFT 3
#define LVL_CLK_DIV (1UL << LVL_CLK_SHIFT)
#define LVL_CLK_MASK (LVL_CLK_DIV - 1)
#define LVL_SHIFT(n) ((n) * LVL_CLK_SHIFT)
#define LVL_GRAN(n) (1UL << LVL_SHIFT(n))
/*
* 每个级别的开始时间值,用于在入队时选择存储桶。
* 我们从上一层的最后一个可能的 delta 开始,以便稍后可以向 n 添加额外的 LVL_GRAN(n)
*(参见 calc_index())。
*/
#define LVL_START(n) ((LVL_SIZE - 1) << (((n) - 1) * LVL_CLK_SHIFT))
/* 每个时钟级别的大小 */
#define LVL_BITS 6
#define LVL_SIZE (1UL << LVL_BITS)
#define LVL_MASK (LVL_SIZE - 1)
#define LVL_OFFS(n) ((n) * LVL_SIZE)
/* Level depth */
#if HZ > 100
# define LVL_DEPTH 9
# else
# define LVL_DEPTH 8
#endif
/* The cutoff (max. capacity of the wheel) */
#define WHEEL_TIMEOUT_CUTOFF (LVL_START(LVL_DEPTH))
#define WHEEL_TIMEOUT_MAX (WHEEL_TIMEOUT_CUTOFF - LVL_GRAN(LVL_DEPTH - 1))
/*
* The resulting wheel size. If NOHZ is configured we allocate two
* wheels so we have a separate storage for the deferrable timers.
*/
#define WHEEL_SIZE (LVL_SIZE * LVL_DEPTH)
/*
* 用于计算给定到期时间的数组索引的 Helper 函数。
*/
static inline unsigned calc_index(unsigned long expires, unsigned lvl,
unsigned long *bucket_expiry)
{
/*
* 定时器可能因为时间粒度的截断或在时间滴答边缘被设置而提前触发。
* 为了防止这种情况,函数通过向上取整(加 1)来确保定时器不会提前触发
*/
expires = (expires >> LVL_SHIFT(lvl)) + 1;
/* 根据层级的时间粒度(LVL_SHIFT(lvl)),计算定时器所在桶的到期时间 */
*bucket_expiry = expires << LVL_SHIFT(lvl);
/* 返回数组索引 */
return LVL_OFFS(lvl) + (expires & LVL_MASK);
}
static int calc_wheel_index(unsigned long expires, unsigned long clk,
unsigned long *bucket_expiry)
{
/* 时间差用于判断定时器所属的时间轮层级 */
unsigned long delta = expires - clk;
unsigned int idx;
/* 每个层级的时间粒度由 LVL_START(n) 定义,时间差越大,定时器被分配到粒度更大的层级 */
if (delta < LVL_START(1)) {
idx = calc_index(expires, 0, bucket_expiry);
} else if (delta < LVL_START(2)) {
idx = calc_index(expires, 1, bucket_expiry);
} else if (delta < LVL_START(3)) {
idx = calc_index(expires, 2, bucket_expiry);
} else if (delta < LVL_START(4)) {
idx = calc_index(expires, 3, bucket_expiry);
} else if (delta < LVL_START(5)) {
idx = calc_index(expires, 4, bucket_expiry);
} else if (delta < LVL_START(6)) {
idx = calc_index(expires, 5, bucket_expiry);
} else if (delta < LVL_START(7)) {
idx = calc_index(expires, 6, bucket_expiry);
} else if (LVL_DEPTH > 8 && delta < LVL_START(8)) {
idx = calc_index(expires, 7, bucket_expiry);
} else if ((long) delta < 0) {
/* 如果时间差为负值,表示定时器已经过期。
定时器被分配到当前时间对应的存储桶 */
idx = clk & LVL_MASK;
*bucket_expiry = clk;
} else {
/* 处理超大时间差 */
/*
* 果时间差超过时间轮的最大容量(WHEEL_TIMEOUT_CUTOFF),定时器被强制设置为最大超时时间。
*/
if (delta >= WHEEL_TIMEOUT_CUTOFF)
expires = clk + WHEEL_TIMEOUT_MAX;
idx = calc_index(expires, LVL_DEPTH - 1, bucket_expiry);
}
return idx;
}
detach_timer 从时间轮中移除定时器
c
static inline void detach_timer(struct timer_list *timer, bool clear_pending)
{
struct hlist_node *entry = &timer->entry;
debug_deactivate(timer);
/* 从链表中删除定时器的节点 */
__hlist_del(entry);
if (clear_pending)
entry->pprev = NULL;
/* 标记节点为无效 */
entry->next = LIST_POISON2;
}
detach_if_pending 从时间轮(timer wheel)中移除挂起状态的定时器(timer_list)
c
static int detach_if_pending(struct timer_list *timer, struct timer_base *base,
bool clear_pending)
{
unsigned idx = timer_get_idx(timer);
if (!timer_pending(timer))
return 0;
/* 检查定时器是否是其所在存储桶中的唯一节点 */
if (hlist_is_singular_node(&timer->entry, base->vectors + idx)) {
/* 如果是唯一节点,调用 __clear_bit 从时间轮的挂起映射(pending_map)中清除该定时器的索引 */
__clear_bit(idx, base->pending_map);
/* 表示需要重新计算时间轮的下一个到期时间 */
base->next_expiry_recalc = true;
}
/* 移除定时器 clear_pending 参数决定是否清除定时器的挂起状态*/
detach_timer(timer, clear_pending);
return 1;
}
trigger_dyntick_cpu 在必要时唤醒目标 CPU,以确保定时器(timer_list)能够及时处理
c
static void
trigger_dyntick_cpu(struct timer_base *base, struct timer_list *timer)
{
/*
* 可延迟计时器不会阻止 CPU 进入 dynticks,也不会在 idle/nohz_full 路径上考虑。当新的可延迟计时器排队时,IPI 将唤醒远程 CPU,但不会对可延迟计时器库执行任何作。因此,请完全跳过可延迟计时器的远程 IPI。
*/
/* 果系统未启用 nohz 模式(即不支持动态时钟)或定时器标记为可延迟(TIMER_DEFERRABLE)
可延迟定时器不会阻止 CPU 进入空闲状态,因此无需发送远程中断(IPI)唤醒目标 CPU */
if (!is_timers_nohz_active() || timer->flags & TIMER_DEFERRABLE)
return;
/*
* 如果底座空闲且计时器已固定,我们可能必须对远程 CPU 进行 IPI。如果它是非固定计时器,则当计时器在排队期间运行时,它仅在远程 CPU 上排队。然后无论如何,一切都由远程 CPU 处理。如果另一个 CPU 正在空闲,那么它无法设置 base->is_idle因为我们持有 base 锁:
*/
/* 如果目标 CPU处于空闲状态(base->is_idle),函数检查定时器是否被绑定到特定 CPU(TIMER_PINNED)或目标 CPU是否处于 nohz_full 模式 */
if (base->is_idle) {
WARN_ON_ONCE(!(timer->flags & TIMER_PINNED ||
tick_nohz_full_cpu(base->cpu)));
wake_up_nohz_cpu(base->cpu);
}
}
enqueue_timer 将定时器(timer_list)加入时间轮(timer wheel)的哈希桶中,并更新相关的状态信息
c
/*
* 将计时器排队到哈希桶中,在位图中将其标记为 pending,
* 将索引存储在计时器标志中,然后根据需要唤醒目标 CPU。
*/
static void enqueue_timer(struct timer_base *base, struct timer_list *timer,
unsigned int idx, unsigned long bucket_expiry)
{
/* 将定时器的链表节点(entry)添加到时间轮的哈希桶中 */
hlist_add_head(&timer->entry, base->vectors + idx);
__set_bit(idx, base->pending_map);
/* 将索引存储到定时器的标志位中 */
timer_set_idx(timer, idx);
trace_timer_start(timer, bucket_expiry);
/*
* 检查当前定时器的到期时间(bucket_expiry)是否早于时间轮的下一个到期时间(base->next_expiry)。如果是,则需要更新时间轮的状态
*/
if (time_before(bucket_expiry, base->next_expiry)) {
/*
* 更新时间轮的下一个到期时间
*/
WRITE_ONCE(base->next_expiry, bucket_expiry);
/* 表示时间轮中有挂起的定时器 */
base->timers_pending = true;
/* 表示无需重新计算下一个到期时间 */
base->next_expiry_recalc = false;
/* 唤醒目标 CPU,以便重新评估时间轮并处理定时器 */
trigger_dyntick_cpu(base, timer);
}
}
internal_add_timer 添加定时器到时间轮
c
static void internal_add_timer(struct timer_base *base, struct timer_list *timer)
{
unsigned long bucket_expiry;
unsigned int idx;
idx = calc_wheel_index(timer->expires, base->clk, &bucket_expiry);
enqueue_timer(base, timer, idx, bucket_expiry);
}
__mod_timer 修改计时器的超时时间
c
#define MOD_TIMER_PENDING_ONLY 0x01
#define MOD_TIMER_REDUCE 0x02
#define MOD_TIMER_NOTPENDING 0x04
static inline int
__mod_timer(struct timer_list *timer, unsigned long expires, unsigned int options)
{
unsigned long clk = 0, flags, bucket_expiry;
struct timer_base *base, *new_base;
unsigned int idx = UINT_MAX;
int ret = 0;
debug_assert_init(timer);
/*
* 这是由网络代码触发的常见优化
- 如果 timer 被重新修改为具有相同的超时或最终在同一个数组存储桶中,则只需返回:
- 如果新的到期时间与当前到期时间相同,或者新的到期时间更短且设置了 MOD_TIMER_REDUCE 标志
*/
if (!(options & MOD_TIMER_NOTPENDING) && timer_pending(timer)) {
/*
* 此优化的缺点是,它可能导致比添加具有此过期时间的新计时器获得的粒度更大的粒度。
*/
long diff = timer->expires - expires;
if (!diff)
return 1;
/* 新的到期时间更短且设置了 MOD_TIMER_REDUCE 标志 */
if (options & MOD_TIMER_REDUCE && diff <= 0)
return 1;
/*
* 我们在此处锁定计时器基数并计算存储桶索引。
* 如果 timer 最终位于同一个存储桶中,那么我们只更新过期时间并避免整个 dequeue/enqueue 舞蹈。
*/
base = lock_timer_base(timer, &flags);
/*
* @timer 关闭了吗?这需要在持有 base lock 时进行评估,以防止与 shutdown 代码发生争用。
*/
if (!timer->function)
goto out_unlock;
/* 更新定时器基础(timer_base)的时钟值(clk) */
forward_timer_base(base);
/* 检查定时器是否处于挂起状态
MOD_TIMER_REDUCE该标志表示允许优化定时器的到期时间,尤其是在新的到期时间更短的情况下
比较定时器的当前到期时间(timer->expires)与新的到期时间(expires) */
if (timer_pending(timer) && (options & MOD_TIMER_REDUCE) &&
time_before_eq(timer->expires, expires)) {
ret = 1;
goto out_unlock;
}
clk = base->clk;
/* 计算定时器在时间轮中的索引,用于优化定时器的重新调度 */
idx = calc_wheel_index(expires, clk, &bucket_expiry);
/*
* 检索并比较 pendingtimer 的数组索引。
* 如果匹配,请将 expiry 设置为新值,以便后续调用将在上面的 expires 检查中退出。
*/
if (idx == timer_get_idx(timer)) {
if (!(options & MOD_TIMER_REDUCE))
timer->expires = expires;
else if (time_after(timer->expires, expires))
timer->expires = expires;
ret = 1;
goto out_unlock;
}
} else {
base = lock_timer_base(timer, &flags);
/*
* @timer 关闭了吗?这需要在持有 base lock 时进行评估,以防止与 shutdown 代码发生争用。
*/
if (!timer->function)
goto out_unlock;
forward_timer_base(base);
}
ret = detach_if_pending(timer, base, false);
if (!ret && (options & MOD_TIMER_PENDING_ONLY))
goto out_unlock;
new_base = get_timer_this_cpu_base(timer->flags);
/* 定时器当前绑定的 CPU 与目标 CPU 不同,函数会迁移定时器到目标 CPU 的定时器基础 */
if (base != new_base) {
/* 我们正在尝试在新基上安排计时器。
* 但是,我们不能在 timer 运行时更改 timer 的 base,
* 否则 timer_delete_sync() 无法检测到 timer 的处理程序尚未完成。
* 这也保证了 timer 本身是序列化的。
*/
if (likely(base->running_timer != timer)) {
/* See the comment in lock_timer_base() */
timer->flags |= TIMER_MIGRATING;
raw_spin_unlock(&base->lock);
base = new_base;
raw_spin_lock(&base->lock);
WRITE_ONCE(timer->flags,
(timer->flags & ~TIMER_BASEMASK) | base->cpu);
forward_timer_base(base);
}
}
debug_timer_activate(timer);
/* 更新定时器的到期时间 */
timer->expires = expires;
/*
* 如果上面计算了 'idx',并且基数在计算 'idx' 和可能切换基数之间没有提前,
* 则只需要 enqueue_timer()。
* 否则,我们需要通过 internal_add_timer() (重新)计算 wheel index。
*/
if (idx != UINT_MAX && clk == base->clk)
enqueue_timer(base, timer, idx, bucket_expiry);
else
internal_add_timer(base, timer);
out_unlock:
raw_spin_unlock_irqrestore(&base->lock, flags);
return ret;
}
mod_timer 修改计时器的超时时间
c
/**
* mod_timer - 修改计时器的超时时间
* @timer: 需要修改的计时器
* @expires: 新的绝对超时时间(以 jiffies 为单位)
*
* mod_timer(timer, expires) 等价于:
*
* 删除定时器(timer); timer->到期时间 = expires; 添加定时器(timer);
*
* mod_timer()比上述开放编码序列更有效。如果计时器不活动,timer_delete()部分是一个NOP。在任何情况下,计时器都将使用新的到期时间@expires激活。
*
* 请注意,如果有多个未序列化的并发用户使用同一个定时器,那么 mod_timer() 是唯一安全的修改超时的方法,因为 add_timer() 不能修改已经在运行的定时器。
*
* 如果 @timer->function == NULL,则启动操作将被静默丢弃。在这种情况下,返回值为 0,并且没有意义。
*
* Return:
* * %0 - 定时器处于非活动状态,已启动或处于关闭状态,操作已被放弃
* * %1 - 定时器处于活动状态,并重新排队在 @expires 过期,或者定时器处于活动状态且未被修改,因为 @expires 并没有改变有效的到期时间
*/
int mod_timer(struct timer_list *timer, unsigned long expires)
{
return __mod_timer(timer, expires, 0);
}
EXPORT_SYMBOL(mod_timer);
add_timer 启动一个定时器
c
/**
* add_timer - 启动一个定时器
* @timer: 需要被启动的定时器
*
* 启动@timer,使其在未来的@timer->expires时刻到期。@timer->expires是
* 以'jiffies'为单位计量的绝对到期时间。当定时器到期时,
* timer->function(timer)将会从软中断上下文中被调用。
*
* 在调用本函数之前,@timer->expires和@timer->function字段必须被设置好。
*
* 如果@timer->function == NULL,那么启动操作会被静默地丢弃。
*
* 如果@timer->expires已经是一个过去的时间,@timer将会被排队到下一个
* 时钟节拍到期。
*
* 本函数只能操作一个未激活的定时器。尝试对一个已激活的定时器调用本函数
* 将会被警告并拒绝。
*/
void add_timer(struct timer_list *timer)
{
/*
* 使用WARN_ON_ONCE进行安全检查。timer_pending(timer)检查定时器是否
* 已经处于挂起(激活)状态。如果是,则打印一次警告信息,并直接返回,
* 拒绝重复添加。
*/
if (WARN_ON_ONCE(timer_pending(timer)))
return;
/*
* 调用内部核心函数__mod_timer来完成实际的添加工作。
* - timer: 要操作的定时器。
* - timer->expires: 将定时器的到期时间设置为其自身的expires字段。
* - MOD_TIMER_NOTPENDING: 作为一个标志传入,告知__mod_timer该定时器
* 当前是未激活的,允许其进行内部优化。
*/
__mod_timer(timer, timer->expires, MOD_TIMER_NOTPENDING);
}
/* 将该符号导出,使得内核模块可以使用。*/
EXPORT_SYMBOL(add_timer);
add_timer_global 在未设置TIMER_PINNED标志的情况下启动计时器
c
/**
* add_timer_global() - 在未设置TIMER_PINNED标志的情况下启动计时器
* @timer:要启动的计时器
*
* 与 add_timer() 相同,但 timer 标志 TIMER_PINNED 未设置。
*
* 有关详细信息,请参阅 add_timer()。
*/
void add_timer_global(struct timer_list *timer)
{
/* 检查定时器是否已经处于挂起状态(即是否已经被启动) */
if (WARN_ON_ONCE(timer_pending(timer)))
return;
/* 清除定时器的 TIMER_PINNED 标志,使其成为一个未绑定的全局定时器 */
timer->flags &= ~TIMER_PINNED;
__mod_timer(timer, timer->expires, MOD_TIMER_NOTPENDING);
}
add_timer_on 在特定 CPU 上启动计时器
c
/**
* add_timer_on - 在特定 CPU 上启动计时器
* @timer:要启动的计时器
* @cpu:启动它的 CPU
*
* 与 add_timer() 相同,只是它在给定的 CPU 上启动计时器并设置了 TIMER_PINNED 标志。当 timer 在下一轮中不应该是固定计时器时,应该使用 add_timer_global() 代替,因为它会取消设置 TIMER_PINNED 标志。
*
* 有关详细信息,请参阅 add_timer()。
*/
void add_timer_on(struct timer_list *timer, int cpu)
{
struct timer_base *new_base, *base;
unsigned long flags;
debug_assert_init(timer);
if (WARN_ON_ONCE(timer_pending(timer)))
return;
/* 确保计时器标志已设置TIMER_PINNED标志
表示定时器被绑定到特定的 CPU。 */
timer->flags |= TIMER_PINNED;
/* 获取目标 CPU 的定时器基础 */
new_base = get_timer_cpu_base(timer->flags, cpu);
/*
* 调用 lock_timer_base 锁定当前定时器基础,确保在迁移过程中不会发生竞争条件
*/
base = lock_timer_base(timer, &flags);
/*
*如果定时器的回调函数(function)为空,表示定时器已关闭,直接解锁并返回
*/
if (!timer->function)
goto out_unlock;
/* 如果当前定时器基础与目标 CPU 的定时器基础不同,迁移定时器到目标 CPU */
if (base != new_base) {
/* 设置 TIMER_MIGRATING 标志,表示定时器正在迁移 */
timer->flags |= TIMER_MIGRATING;
raw_spin_unlock(&base->lock);
base = new_base;
raw_spin_lock(&base->lock);
/* 更新定时器的 flags 字段以反映新的 CPU 绑定。 */
WRITE_ONCE(timer->flags,
(timer->flags & ~TIMER_BASEMASK) | cpu);
}
/* 调用 forward_timer_base 更新定时器基础的时间 */
forward_timer_base(base);
debug_timer_activate(timer);
/* 调用 internal_add_timer 将定时器添加到目标 CPU 的定时器基础中 */
internal_add_timer(base, timer);
out_unlock:
raw_spin_unlock_irqrestore(&base->lock, flags);
}
EXPORT_SYMBOL_GPL(add_timer_on);
__try_to_del_timer_sync 内部功能:尝试停用计时器
c
/**
* __try_to_del_timer_sync - 内部功能:尝试停用计时器
* @timer:停用计时器
* @shutdown:如果为 true,则表示定时器即将永久关闭。
*
* 如果 @shutdown 为 true,则 @timer->function 在定时器基本锁定下设置为 NULL,以防止进一步重新设置定时器。在此函数返回后重新武装 @timer 的任何尝试都将被静默忽略。
*
* 如果 @shutdown 为 false,此功能无法保证在放下基础锁后无法立即重新设置计时器。如有必要,需要通过调用代码来防止这种情况。
*
*返回:
* * %0 - 计时器未挂起
* * %1 - 计时器处于待处理状态并已停用
* * %-1 - 计时器回调函数正在不同的 CPU 上运行
*/
static int __try_to_del_timer_sync(struct timer_list *timer, bool shutdown)
{
struct timer_base *base;
unsigned long flags;
int ret = -1;
debug_assert_init(timer);
base = lock_timer_base(timer, &flags);
/* 如果定时器的回调函数未在当前 CPU 上运行,调用 detach_if_pending 移除定时器并清除其挂起状态 */
if (base->running_timer != timer)
ret = detach_if_pending(timer, base, true);
if (shutdown)
timer->function = NULL;
raw_spin_unlock_irqrestore(&base->lock, flags);
return ret;
}
__timer_delete_sync 内部功能:停用计时器并等待处理程序完成
c
/**
* __timer_delete_sync - 内部功能:停用计时器并等待处理程序完成。
* @timer:要停用的计时器
* @shutdown:如果为 true,则 @timer->function 将在定时器基础锁定下设置为 NULL,以防止重新@timer
*
* 如果未设置 @shutdown,则可以稍后重新设置计时器。如果 timer 可以并发重置,即在删除 base lock 之后,则 return 值没有意义。
*
* 如果设置了 @shutdown,则 @timer->function 在定时器基本锁定下设置为 NULL,以防止定时器重新启动。任何重置关闭计时器的尝试都会被静默忽略。
*
* 如果定时器在关机后需要重复使用,则必须再次初始化。
*
*返回:
* * %0 - 计时器未挂起
* * %1 - 计时器处于待处理状态并已停用
*/
static int __timer_delete_sync(struct timer_list *timer, bool shutdown)
{
int ret;
/*
* 不要在 hardirq 上下文中使用它,因为它可能会导致死锁。
*/
WARN_ON(in_hardirq() && !(timer->flags & TIMER_IRQSAFE));
/*
* 由于 del_timer_wait_running() 中的慢路径,必须能够在 PREEMPT_RT 上休眠。
*/
if (IS_ENABLED(CONFIG_PREEMPT_RT) && !(timer->flags & TIMER_IRQSAFE))
lockdep_assert_preemption_enabled();
/* 循环尝试停用 */
do {
/* 确保定时器的回调函数完成后再停用定时器 */
ret = __try_to_del_timer_sync(timer, shutdown);
if (unlikely(ret < 0)) {
/* 等待回调完成,并通过 cpu_relax 减少 CPU 的忙等待开销 */
del_timer_wait_running(timer);
cpu_relax();
}
} while (ret < 0);
return ret;
}
timer_delete_sync 停用计时器并等待处理程序完成
- 防止定时器重新启动:
- 调用者必须确保定时器不会被重新启动(重新挂起),否则函数的行为可能没有意义。
- 中断上下文限制:
- 函数不能在中断上下文中调用,除非定时器是中断安全的(irqsafe)。
- 非中断安全的定时器可能导致死锁或其他竞争条件。
- 锁依赖性:
- 调用者不能持有可能阻止定时器回调函数完成的锁。
- 例如,如果定时器的回调函数需要获取某个锁,而调用者已经持有该锁,则可能导致死锁。
c
/**
* timer_delete_sync - 停用计时器并等待处理程序完成。
* @timer:要停用的计时器
*
* 同步规则:调用者必须阻止定时器的重启,否则此功能毫无意义。除非计时器是 irqsafe 的,否则它不能从 interrupt 上下文中调用。调用方不得持有锁,否则会阻止计时器的 callbackfunction 完成。计时器的处理程序不得调用 add_timer_on()。退出时,计时器未排队,并且处理程序未在任何 CPU 上运行。
*
* 对于 irqsafe 计时器,调用者不得持有中断上下文中持有的锁。即使锁与有问题的计时器无关。 原因如下:
*
* CPU0 CPU1
* ---- ----
* <SOFTIRQ>
* call_timer_fn();
* base->running_timer = mytimer;
* spin_lock_irq(somelock);
* <IRQ>
* spin_lock(somelock);
* timer_delete_sync(mytimer);
* while (base->running_timer == mytimer);
*
* 现在 timer_delete_sync() 永远不会返回,也永远不会释放 somelock。另一个 CPU 上的中断正在等待获取某个锁,但它中断了 CPU0 正在等待完成的软中断。
*
* 此函数不能保证计时器在删除 baselock 后立即不会被某些并发或抢占代码再次重置。如果存在并发 rearm 的可能性,则函数的 returnvalue 毫无意义。
*
* 如果需要这样的保证,例如对于拆解情况,请使用 timer_shutdown_sync() 代替。
*
*返回:
* * %0 - 计时器未挂起
* * %1 - 计时器处于待处理状态并已停用
*/
int timer_delete_sync(struct timer_list *timer)
{
return __timer_delete_sync(timer, false);
}
EXPORT_SYMBOL(timer_delete_sync);
__timer_delete - 内部函数:停用计时器
c
/**
* __timer_delete - 内部功能:停用计时器
* @timer:要停用的计时器
* @shutdown:如果为 true,则表示计时器即将
* 永久关闭。
*
* 如果 @shutdown 为 true,则 @timer->function 在定时器基本锁定下设置为 NULL,
* 以防止进一步重新排列时间。在这种情况下,在此函数返回后重新武装 @timer 的任何尝试都将被静默忽略。
*
*返回:
* * %0 - 计时器未挂起
* * %1 - 计时器处于待处理状态并已停用
*/
static int __timer_delete(struct timer_list *timer, bool shutdown)
{
struct timer_base *base;
unsigned long flags;
int ret = 0;
debug_assert_init(timer);
/*
* 如果设置了 @shutdown,则无论计时器是否处于待处理状态,都必须采用锁,
* 以防止可能在无锁挂起检查和锁获取之间发生的并发重置。
* 通过获取锁,可以确保这样一个新排队的计时器被取消排队,
* 并且不会在过期代码中以 timer->function == NULL 结束。
*
* 如果当前正在执行 timer->function,则这将确保回调无法将计时器重新排队。
*/
/* 是否处于挂起状态(timer_pending)或是否需要永久关闭(shutdown) */
if (timer_pending(timer) || shutdown) {
base = lock_timer_base(timer, &flags);
/* 从时间轮中移除定时器,并清除其挂起状态 */
ret = detach_if_pending(timer, base, true);
if (shutdown)
/* 这确保定时器无法被重新启动,任何重新启动操作都会被忽略。 */
timer->function = NULL;
raw_spin_unlock_irqrestore(&base->lock, flags);
}
return ret;
}
timer_delete 停用计时器
c
/**
* timer_delete - 停用计时器
* @timer:要停用的计时器
*
* 该函数仅停用待处理的计时器,但与 timer_delete_sync() 相反,
* 它不考虑计时器的回调函数是否在不同的 CPU 上并发执行。
* 它也不会阻止计时器的重新开始。 如果 @timer 可以并发重新武装,则此函数的返回值将毫无意义。
*
*返回:
* * %0 - 计时器未挂起
* * %1 - 计时器处于待处理状态并已停用
*/
int timer_delete(struct timer_list *timer)
{
/* false 表示不需要同步处理定时器的回调函数 */
return __timer_delete(timer, false);
}
EXPORT_SYMBOL(timer_delete);
init_timers 初始化定时器
c
static void __init init_timer_cpu(int cpu)
{
struct timer_base *base;
int i;
for (i = 0; i < NR_BASES; i++) {
base = per_cpu_ptr(&timer_bases[i], cpu);
base->cpu = cpu;
raw_spin_lock_init(&base->lock);
base->clk = jiffies;
base->next_expiry = base->clk + NEXT_TIMER_MAX_DELTA;
timer_base_init_expiry_lock(base);
}
}
static void __init init_timer_cpus(void)
{
int cpu;
for_each_possible_cpu(cpu)
init_timer_cpu(cpu);
}
void __init init_timers(void)
{
init_timer_cpus();
posix_cputimers_init_work();
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}
timer_init_key 初始化定时器
c
/**
* timer_init_key - 初始化一个定时器
* @timer: 要初始化的定时器
* @func: 定时器回调函数
* @flags: 定时器标志
* @name: 定时器的名称
* @key: 用于跟踪定时器同步锁依赖的虚拟锁的锁依赖类键
*
* 在调用任何其他定时器函数之前,必须对定时器执行 timer_init_key()。
*/
void timer_init_key(struct timer_list *timer,
void (*func)(struct timer_list *), unsigned int flags,
const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, func, flags, name, key);
}
EXPORT_SYMBOL(timer_init_key);
collect_expired_timers 收集已到期的定时器
collect_expired_timers是__run_timers函数在处理循环中调用的第一个核心辅助函数。
它的核心作用是:根据当前到期的时间点(base->clk),从timer_base的层级时间轮(hierarchical timer wheel)数据结构中,高效地"收集"所有已经到期的定时器,并将它们从时间轮中移除,放入一个临时的本地链表数组heads中,以供后续处理。
它是Linux利用时间轮算法来高效管理大量定时器的关键实现。
其工作原理是基于一个**多级、级联的时间轮(Timer Wheel)**数据结构:
-
时间轮数据结构:
timer_base内部包含一个名为vectors的大数组,这个数组被逻辑地划分为LVL_DEPTH(通常是8)个层级。- 每个层级代表了不同的时间粒度。最低层级(level 0)的每个"槽(slot)"代表一个
jiffy(一个时钟tick),而更高层级的每个槽代表的时间跨度则逐级指数增长。 - 一个定时器根据其到期时间,会被放入其对应层级、对应槽位的哈希链表(
hlist)中。
-
"级联"或"滴答"原理:
- 当时间不断流逝,最低层级(level 0)的指针会随着每个
jiffy向前移动。 - 当level 0的指针完成一整圈的转动时,就会产生一个"进位",导致level 1的指针向前移动一格。
- 当level 1的指针要移动时,它会把它槽位中的所有定时器,根据它们更精确的到期时间,"重新分散(cascade)"到下一层(level 0)的各个槽位中。
- 这个过程逐级向上。因此,内核无需在每个tick都去遍历所有定时器,它只需要处理当前tick对应的level 0的槽位,以及在发生"进位"时,处理更高层级的级联即可。
- 当时间不断流逝,最低层级(level 0)的指针会随着每个
-
collect_expired_timers的执行流程:- 确定当前时间点 : 函数首先将基座的内部时钟
clk推进到下一个有定时器到期的时刻base->next_expiry。 - 遍历层级: 它从level 0开始,向上遍历时间轮的各个层级。
- 检查并收集 : 在每一层,它计算出当前
clk对应的槽位索引idx。然后,它使用__test_and_clear_bit原子地检查base->pending_map这个位图中,对应的槽位是否有定时器挂起。- 如果有,它就通过
hlist_move_list,将该槽位(base->vectors[idx])的整个定时器链表 ,一次性地、高效地移动到作为输出参数的heads数组中。
- 如果有,它就通过
- 判断是否级联 :
if (clk & LVL_CLK_MASK)这个检查,是在判断当前clk是否走到了一个需要发生"进位"的时间点(即level 0是否转完了一圈)。如果不是,说明更高层级在此刻没有定时器需要被级联下来,遍历就可以提前终止。 - 准备下一层级 : 如果需要继续检查更高层级,它会将
clk右移LVL_CLK_SHIFT位,相当于将时间"粗粒度化",以匹配更高层级的时间粒度。
- 确定当前时间点 : 函数首先将基座的内部时钟
c
/*
* 这是一个静态函数,负责从一个给定的timer_base中,收集所有已到期的定时器。
* @base: 指向需要被处理的timer_base结构体。
* @heads: 指向一个临时的、位于调用者栈上的哈希链表头数组。
* 所有收集到的到期定时器都会被移动到这个数组的链表中。
*
* 返回值: 包含到期定时器的层级数。
*/
static int collect_expired_timers(struct timer_base *base,
struct hlist_head *heads)
{
/*
* clk: 一个无符号长整型,作为当前正在处理的时间点。
* 首先,将基座的内部时钟base->clk推进到下一个已知的到期时间点。
*/
unsigned long clk = base->clk = base->next_expiry;
/* vec: 用于临时指向时间轮中某个槽位的哈希链表头。*/
struct hlist_head *vec;
/* i: 用于循环遍历时间轮的层级。*/
/* levels: 用于计收集到了多少个非空链表(即多少个层级有到期定时器)。*/
int i, levels = 0;
/* idx: 用于存储计算出的、在vectors数组中的索引。*/
unsigned int idx;
/* 从level 0开始,向上遍历所有时间轮层级(LVL_DEPTH是总层级数)。*/
for (i = 0; i < LVL_DEPTH; i++) {
/*
* 计算当前clk在当前层级i下,对应的槽位索引。
* (clk & LVL_MASK) 取出当前层级相关的比特位。
*/
idx = (clk & LVL_MASK) + i * LVL_SIZE;
/*
* 使用__test_and_clear_bit原子地检查并清除pending_map中的对应位。
* pending_map是一个位图,用于快速判断哪个槽位有定时器,避免遍历空槽位。
*/
if (__test_and_clear_bit(idx, base->pending_map)) {
/* 如果该槽位确实有待处理的定时器。*/
/* 获取该槽位的哈希链表头指针。*/
vec = base->vectors + idx;
/*
* 调用hlist_move_list,将该槽位(vec)的整个定时器链表,
* 高效地移动到输出数组heads的当前位置。heads指针会自动递增。
*/
hlist_move_list(vec, heads++);
/* 增加已收集的层级计数。*/
levels++;
}
/*
* Is it time to look at the next level?
* 检查当前clk是否到达了一个需要向上层"进位"的边界。
* LVL_CLK_MASK用于判断当前层级的时间轮是否已转完一圈。
*/
if (clk & LVL_CLK_MASK)
/* 如果没有转完一圈,说明更高层级不可能有需要级联的定时器,
* 可以安全地提前中止遍历。*/
break;
/*
* Shift clock for the next level granularity
* 将clk右移,使其时间粒度与下一更高层级匹配,为下一轮循环做准备。
*/
clk >>= LVL_CLK_SHIFT;
}
/* 返回收集到的、包含到期定时器的链表的数量。*/
return levels;
}
timer_recalc_next_expiry 重新计算下一个到期时间
- 它的核心作用是:在当前时间点(base->clk)之后,向前搜索整个层级时间轮(hierarchical timer wheel),找出下一个最近的、有定时器挂起的"槽位(bucket)",并计算出其对应的到期时间,然后将这个时间更新到base->next_expiry中。
这个函数是Linux定时器系统效率和NOHZ(动态时钟)功能的基础。通过预先计算出下一个事件的发生时间,CPU在空闲时就可以精确地知道自己可以安全地睡眠多久,而不会错过任何定时器事件
c
/*
* 在不同的时钟层级中,搜索第一个到期的定时器。调用者必须持有base->lock。
*
* 将下一个到期时间存储在base->next_expiry中。
*/
static void timer_recalc_next_expiry(struct timer_base *base)
{
/* clk: 在循环中代表不同层级的当前时间点。*/
unsigned long clk;
/* next: 用于存储在搜索过程中,当前已找到的最近的未来到期时间。*/
unsigned long next;
/* adj: "adjustment",用于根据是否发生进位,调整上一层级的时钟。*/
unsigned long adj;
/* lvl: 当前正在搜索的时间轮层级。*/
/* offset: 当前层级在全局vectors数组中的起始偏移。*/
unsigned lvl, offset = 0;
/* 初始化next为一个遥远的未来时间,作为搜索的上限。*/
next = base->clk + TIMER_NEXT_MAX_DELTA;
/* 将clk初始化为基座的当前时钟。*/
clk = base->clk;
/* 从level 0开始,向上遍历所有时间轮层级。*/
for (lvl = 0; lvl < LVL_DEPTH; lvl++, offset += LVL_SIZE) {
/* 调用next_pending_bucket,在当前层级的位图中,从(clk & LVL_MASK)位置开始,查找下一个有定时器的槽位。*/
int pos = next_pending_bucket(base, offset, clk & LVL_MASK);
/* 获取当前层级时钟的低位,用于判断是否在进位边界。*/
unsigned long lvl_clk = clk & LVL_CLK_MASK;
/* 如果pos大于等于0,说明在当前层级找到了一个挂起的槽位。*/
if (pos >= 0) {
/* 计算出该槽位对应的绝对时间tmp。*/
unsigned long tmp = clk + (unsigned long) pos;
/* 将tmp左移,以从当前层级的时间单位转换回jiffies单位。*/
tmp <<= LVL_SHIFT(lvl);
/* 如果这个新找到的时间tmp比我们已知的next更早。*/
if (time_before(tmp, next))
/* 则更新next为这个更早的时间。*/
next = tmp;
/*
* 如果下一个到期事件在我们到达下一层级之前发生,就无需再检查更高层级了。
*/
/* 这是一个优化,如果找到的到期点在本层级的一圈之内,就可以提前终止遍历。*/
if (pos <= ((LVL_CLK_DIV - lvl_clk) & LVL_CLK_MASK))
break;
}
/*
* 下一层级的时钟。如果当前层级时钟的低位是零,我们就直接看下一层级。
* 如果不是,我们需要将其加一,因为那将是该层级中下一个到期的桶。
* base->clk是下一个到期的jiffy。所以在如下情况:
*
* LVL5 LVL4 LVL3 LVL2 LVL1 LVL0
* 0 0 0 0 0 0
*
* 我们必须查看所有层级的@index 0。而对于:
*
* LVL5 LVL4 LVL3 LVL2 LVL1 LVL0
* 0 0 0 0 0 2
*
* LVL0的下一个到期桶在@index 2。更高层级的下一个到期桶在@index 1。
*
* 如果传播导致上层级回绕,同样的规则适用:
*
* LVL5 LVL4 LVL3 LVL2 LVL1 LVL0
* 0 0 0 0 F 2
*
* 所以在查看LVL0后,我们得到:
*
* LVL5 LVL4 LVL3 LVL2 LVL1
* 0 0 0 1 0
*
* 所以从LVL1到LVL2没有传播,因为加法已经处理了,但之后我们需要从LVL2传播到LVL3。
*
* 因此,简单地检查当前层级的低位是否为0就足以应对所有情况。
*/
/* 根据当前层级时钟的低位(lvl_clk)是否为零,决定调整值adj是1还是0。*/
adj = lvl_clk ? 1 : 0;
/* 将clk右移,使其时间粒度与下一更高层级匹配。*/
clk >>= LVL_CLK_SHIFT;
/* 应用调整值,处理进位效应。*/
clk += adj;
}
/* 使用WRITE_ONCE原子地将最终计算出的next_expiry写入到基座中。*/
WRITE_ONCE(base->next_expiry, next);
/* 将"需要重新计算"的标志位置为false。*/
base->next_expiry_recalc = false;
/* 根据next是否被更新,设置timers_pending标志。*/
base->timers_pending = !(next == base->clk + TIMER_NEXT_MAX_DELTA);
}
call_timer_fn 调用定时器回调函数
c
/*
* 这是一个静态函数,负责安全地调用一个定时器的回调函数。
* @timer: 指向已到期的timer_list结构体。
* @fn: 指向需要被执行的回调函数。
* @baseclk: 定时器到期的jiffy时间点,主要用于追踪。
*/
static void call_timer_fn(struct timer_list *timer,
void (*fn)(struct timer_list *),
unsigned long baseclk)
{
/* count: 整型变量,用于保存执行回调函数之前的抢占计数值,以便事后检查。*/
int count = preempt_count();
/* 如果内核配置了锁依赖检测器(lockdep)。*/
#ifdef CONFIG_LOCKDEP
/*
* 从函数内部释放定时器是允许的,我们也需要为lockdep考虑到这一点。
* 为了避免虚假的"持有锁被释放"警告,以及在查看timer->lockdep_map时
* 出现问题,我们创建一个副本并在这里使用它。
*/
/* lockdep_map: 在当前函数栈上创建一个临时的lockdep_map结构体。*/
struct lockdep_map lockdep_map;
/* 调用lockdep_copy_map,将timer->lockdep_map的内容安全地复制到栈上的副本中。*/
lockdep_copy_map(&lockdep_map, &timer->lockdep_map);
#endif
/*
* 通过在这里和timer_delete_sync()中围绕fn()调用获取lock_map,
* 将锁链与timer_delete_sync()中的锁链耦合起来。
*/
/* 在调用回调前,为lockdep获取这个(可能是虚拟的)锁。
* 这建立了一个临界区,timer_delete_sync()会等待这个临界区的结束。*/
lock_map_acquire(&lockdep_map);
/* 记录一个"定时器到期入口"的追踪事件。*/
trace_timer_expire_entry(timer, baseclk);
/* 通过函数指针,调用用户注册的定时器回调函数,将timer自身作为参数传入。*/
fn(timer);
/* 记录一个"定时器到期出口"的追踪事件。*/
trace_timer_expire_exit(timer);
/* 在回调结束后,为lockdep释放这个锁。*/
lock_map_release(&lockdep_map);
/* 比较执行回调函数之后和之前的抢占计数值。*/
if (count != preempt_count()) {
/* 如果不相等,说明回调函数存在抢占计数泄漏的bug,触发一次性内核警告。*/
WARN_ONCE(1, "timer: %pS preempt leak: %08x -> %08x\n",
fn, count, preempt_count());
/*
* 恢复抢占计数。这给了我们一个不错的机会去幸存下来并提取信息。
* 如果回调函数还保持着一个锁,那就算倒霉了,但也比我们之前
* 的BUG()要好。
*/
/* 强制将抢占计数恢复到进入函数前的正确状态,尝试让内核继续运行。*/
preempt_count_set(count);
}
}
expire_timers 执行已到期的定时器
c
/*
* 这是一个静态函数,负责执行一个链表中所有已到期的定时器。
* @base: 指向当前正在处理的timer_base。
* @head: 指向一个哈希链表头,该链表包含了所有已收集到的、需要被执行的定时器。
*/
static void expire_timers(struct timer_base *base, struct hlist_head *head)
{
/*
* 这个值仅用于追踪。base->clk在expire_timers被调用前已被直接增加。
* 但到期事件是与旧的base->clk值相关的。
*
* @baseclk: 保存定时器真正的到期时间点(jiffy),即当前基座时钟减1。
*/
unsigned long baseclk = base->clk - 1;
/* 只要head链表不为空,就一直循环。*/
while (!hlist_empty(head)) {
/* @timer: 指向当前要处理的timer_list结构体。*/
struct timer_list *timer;
/* @fn: 指向定时器的回调函数指针。*/
void (*fn)(struct timer_list *);
/* 获取链表中的第一个定时器。hlist_entry是用于从链表节点获取整个结构体指针的宏。*/
timer = hlist_entry(head->first, struct timer_list, entry);
/*
* 将base->running_timer指向当前正在处理的定时器。这是一个状态标志,
* 用于防止其他代码并发地修改这个正在被执行的定时器。
*/
base->running_timer = timer;
/*
* 调用detach_timer,将定时器从其所有链表中(包括head链表)安全地移除,
* 并清除其"挂起"状态。true表示该定时器正在被分离。
*/
detach_timer(timer, true);
/* 获取定时器注册的回调函数指针。*/
fn = timer->function;
/*
* 这是一个健壮性检查。一个被排队的定时器,其回调函数指针不应该是NULL。
* 如果是,这是一个严重的内核bug,触发一次性警告。
*/
if (WARN_ON_ONCE(!fn)) {
/* 理论上永远不应发生。强调是"理论上"!*/
/* 清理running_timer状态并继续处理下一个。*/
base->running_timer = NULL;
continue;
}
/* 检查定时器是否被标记为"中断安全"。*/
if (timer->flags & TIMER_IRQSAFE) {
/*
* 如果是中断安全的,则释放锁时无需开启中断。
* 调用者保证了在硬中断上下文中,这个回调也是安全的。
*/
raw_spin_unlock(&base->lock);
/* 调用包装函数,最终执行fn(timer)。*/
call_timer_fn(timer, fn, baseclk);
/* 重新获取锁。*/
raw_spin_lock(&base->lock);
/* 清理running_timer状态。*/
base->running_timer = NULL;
} else {
/*
* 如果不是中断安全的(这是常规情况),则释放锁时必须开启中断,
* 以保证系统在执行可能耗时的回调时,仍能响应硬件中断。
*/
raw_spin_unlock_irq(&base->lock);
/* 调用包装函数,最终执行fn(timer)。*/
call_timer_fn(timer, fn, baseclk);
/* 重新获取锁,并禁用中断。*/
raw_spin_lock_irq(&base->lock);
/* 清理running_timer状态。*/
base->running_timer = NULL;
/* 等待其他CPU上可能正在运行此回调的同步操作完成。*/
// timer_sync_wait_running(base);
}
}
}
run_timer_base 运行定时器基座
- 它的核心作用是:在一个循环中,持续地从给定的timer_base中,收集所有已经到期的定时器,并执行它们的回调函数。
其工作原理是基于Linux传统的、使用层级时间轮(timer wheel)优化的"收集-执行"模型
c
static DEFINE_PER_CPU(struct timer_base, timer_bases[NR_BASES]);
/**
* __run_timers - 运行这个CPU上所有(如果有的话)已到期的定时器。
* @base: 需要被处理的定时器向量(基座)。
*/
static inline void __run_timers(struct timer_base *base)
{
/* heads: 一个临时的、位于栈上的哈希链表头数组,用于存放收集到的到期定时器。*/
struct hlist_head heads[LVL_DEPTH];
/* levels: 存储collect_expired_timers返回的、包含到期定时器的层级数。*/
int levels;
/* 这是一个调试断言,确保我们确实持有了base->lock。*/
lockdep_assert_held(&base->lock);
/*
* 如果base->running_timer非空,表示当前CPU上已经有另一个
* __run_timers的实例正在运行(这可能发生在嵌套的中断或软中断中)。
* 为避免重入问题,直接返回。
*/
if (base->running_timer)
return;
/*
* 只要当前时间jiffies大于等于基座的内部时钟clk,就一直循环,
* 处理从clk到jiffies之间所有tick的到期定时器。
*/
while (time_after_eq(jiffies, base->clk) &&
time_after_eq(jiffies, base->next_expiry)) {
/* 调用collect_expired_timers,从base中收集所有在当前clk时刻
* 到期的定时器,放入heads数组,并返回包含定时器的层级数。*/
levels = collect_expired_timers(base, heads);
/*
* 在此clk未找到任何到期定时器的两个可能原因是:所有匹配的
* 定时器都已被出队,或者自从base::next_expiry被设置为
* base::clk + TIMER_NEXT_MAX_DELTA以来,没有新的定时器被排入。
*/
/* 这是一个健壮性检查,如果没找到定时器但又不符合上述预期,则发出警告。*/
WARN_ON_ONCE(!levels && !base->next_expiry_recalc
&& base->timers_pending);
/*
* 在执行定时器期间,base->clk被设置为领先jiffies一个tick,
* 以避免定时器回调函数无休止地将自己重新排队到当前的jiffies。
*/
/* 将基座的内部时钟向前推进一个tick。*/
base->clk++;
/* 重新计算该基座上下一个可能到期的定时器时间。*/
timer_recalc_next_expiry(base);
/* 从最高层级开始,循环处理所有收集到的定时器。*/
while (levels--)
/* 调用expire_timers,它会遍历heads[levels]链表,并执行其中每个定时器的回调函数。*/
expire_timers(base, heads + levels);
}
}
/*
* 这是一个静态函数,负责在加锁后,处理单个定时器基座。
* @base: 指向需要被处理的具体timer_base结构体。
*/
static void __run_timer_base(struct timer_base *base)
{
/*
* 可以在没有锁的情况下,与一个远程CPU更新next_expiry的操作产生竞争。
* 这是一个无锁的快速路径检查。如果当前时间jiffies还没有到达
* 该基座上最早的到期时间,就直接返回。
* READ_ONCE用于确保原子地读取next_expiry的值。
*/
if (time_before(jiffies, READ_ONCE(base->next_expiry)))
return;
/*
* 获取一个特殊的、用于保护next_expiry字段的锁。
*/
// timer_base_lock_expiry(base);
/* 获取保护整个timer_base数据结构的主自旋锁,并禁用中断。*/
raw_spin_lock_irq(&base->lock);
/* 调用核心执行函数。*/
__run_timers(base);
/* 释放主自旋锁,并恢复中断。*/
raw_spin_unlock_irq(&base->lock);
/* 释放next_expiry锁。*/
// timer_base_unlock_expiry(base);
}
/*
* 这是一个静态函数,是处理单个定时器基座的顶层入口。
* @index: 要处理的定时器基座的索引号
* (BASE_LOCAL, BASE_GLOBAL, 或 BASE_DEF)。
*/
static void run_timer_base(int index)
{
/*
* 使用this_cpu_ptr宏,安全地获取指向当前CPU的、timer_bases数组中
* 对应索引的timer_base结构体的指针。
* timer_bases是一个per-cpu的数组。
*/
struct timer_base *base = this_cpu_ptr(&timer_bases[index]);
/* 调用下一层函数,对获取到的具体基座进行处理。*/
__run_timer_base(base);
}
run_timer_softirq 运行定时器软中断
- run_timer_softirq是Linux内核中注册用于处理TIMER_SOFTIRQ类型软中断的核心处理函数(handler) 。每当硬件时钟中断(tick)发生,并在其硬中断上下文中发现有到期的定时器时,它就会触发TIMER_SOFTIRQ。随后,在软中断处理阶段(由handle_softirqs驱动),run_timer_softirq就会被调用。
它的核心作用是:检查并执行所有已经到期的定时器,并处理与动态时钟(tickless/NOHZ)相关的定时器迁移。
c
/*
* 这是一个静态函数,负责在下半部(softirq)上下文中,运行定时器和定时器任务队列。
* __latent_entropy: GCC属性,表示此函数执行时间的微小变化可以为内核随机数熵池贡献熵。
*/
static __latent_entropy void run_timer_softirq(void)
{
/*
* 调用run_timer_base,处理当前CPU的本地定时器基座(BASE_LOCAL)。
* 这个基座包含了所有绑定到本CPU的、不可延迟的常规和高精度定时器。
* 这是最优先需要处理的。
*/
run_timer_base(BASE_LOCAL);
/*
* 如果内核配置了通用的动态时钟(tickless/NO_HZ)支持。
*/
if (IS_ENABLED(CONFIG_NO_HZ_COMMON)) {
/*
* 调用run_timer_base,处理全局的可延迟定时器基座(BASE_GLOBAL)。
* 这个基座管理着从已经停止tick的idle CPU迁移过来的可延迟定时器。
*/
run_timer_base(BASE_GLOBAL);
/*
* 调用run_timer_base,处理当前CPU的本地可延迟定时器基座(BASE_DEF)。
*/
run_timer_base(BASE_DEF);
/*
* 如果当前系统处于动态时钟(NOHZ)激活状态。
*/
if (is_timers_nohz_active())
/*
* 调用tmigr_handle_remote(),处理远程定时器的迁移请求。
* 即检查是否有从其他idle CPU发送过来的、需要本CPU接管的定时器。
*/
tmigr_handle_remote();
}
}
run_local_timers 运行本地定时器
- 它的核心作用是:检查当前CPU上的各种传统定时器基座(timer base),判断是否有已经到期或即将到期的定时器需要处理。如果有,就触发TIMER_SOFTIRQ软中断。
它本身不执行任何定时器回调,它只负责发出处理请求。它是连接**硬中断(tick)和软中断(定时器处理)**的桥梁
c
/*
* 由本地的、per-CPU的定时器中断在SMP系统上调用。
*/
static void run_local_timers(void)
{
/* base: 指向当前CPU的本地定时器基座(BASE_LOCAL)。*/
struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_LOCAL]);
/*
* 首先,运行高精度定时器队列。这会直接在硬中断上下文中处理
* 已经到期的、不可延迟的hrtimer。
*/
hrtimer_run_queues();
/* 循环遍历当前CPU上的所有传统定时器基座 (BASE_LOCAL, BASE_DEF等)。*/
for (int i = 0; i < NR_BASES; i++, base++) {
/*
* 仅在需要时才触发软中断。
*
* timer_base::next_expiry可能在持有锁的情况下被一个远程CPU写入。如果
* 这个写操作与无锁的本地读操作同时发生,健全性检查器可能会抱怨
* 数据损坏。
*
* timer_base::next_expiry被远程CPU写入有两种可能情况:
*
* 1. 远程CPU使本CPU的全局定时器到期,并随后在next_timer_interrupt()
* 或timer_recalc_next_expiry()中更新了BASE_GLOBAL的next_expiry。
* 最坏的结果是,当我们读到尚未更新的值时,多触发了一次无害的定时器软中断。
*
* 2. 一个新的、被固定的(pinned)首个定时器被一个远程CPU排入队列,
* 因此BASE_LOCAL的next_expiry被更新。如果错过了这个更新,
* 也不是问题,因为如果CPU之前是空闲的,无论如何都会有一个IPI被执行。
* 如果CPU不空闲但错过了更新,那么定时器会延迟一个jiffy到期------运气不好。
*
* 那些最坏结果只是一个jiffy的延迟或一次多余的软中断触发的、不大可能
* 发生的边界情况,其代价远没有在每次检查时都持有锁那么昂贵。
*
* 可能的远程写者使用WRITE_ONCE()。因此本地读者使用READ_ONCE()。
*/
/*
* 检查是否有任何一个基座需要处理。
* 条件1: 当前时间jiffies是否已经到达或超过了该基座的下一个到期时间。
* READ_ONCE用于并发安全的无锁读取。
* 条件2: (对于可延迟基座) 是否有从其他CPU迁移过来的定时器需要处理。
*/
if (time_after_eq(jiffies, READ_ONCE(base->next_expiry)) ||
(i == BASE_DEF && tmigr_requires_handle_remote())) {
/*
* 如果满足任一条件,则调用raise_timer_softirq触发TIMER_SOFTIRQ软中断。
* 这只是提交一个"需要处理传统定时器"的请求。
*/
raise_timer_softirq(TIMER_SOFTIRQ);
/*
* 一旦触发了软中断请求,就可以立即返回,因为一次软中断会处理
* 所有到期的定时器,无需再检查其他基座。
*/
return;
}
}
}
update_process_times 更新进程时间
- 它的核心作用是:在每个时钟节拍(tick)到来时,为当前正在CPU上运行的任务p进行时间记账,并触发所有与tick相关的、per-cpu的周期性事件。
c
/*
* 从定时器中断处理程序中调用,用于将一个tick的时间记在当前进程上。
* 如果tick是用户时间,则user_tick为1,否则为0(系统时间)。
*/
void update_process_times(int user_tick)
{
/* p: 指向当前正在运行的任务(即被时钟中断所打断的那个任务)。*/
struct task_struct *p = current;
/*
* 注意:这个定时器中断上下文自身的时间也必须被记账。
* 调用account_process_tick,根据user_tick的值,将一个tick的时间
* 累加到任务p的用户态(utime)或内核态(stime)时间统计中。
*/
account_process_tick(p, user_tick);
/*
* 调用run_local_timers,这个函数会检查本地CPU的jiffies计数,
* 如果有到期的传统定时器,它会触发TIMER_SOFTIRQ软中断。
*/
run_local_timers();
/*
* 调用rcu_sched_clock_irq,通知RCU子系统一个时钟tick已经发生。
* RCU会利用这个周期性的事件来驱动其状态机,例如检测停滞(stall)。
*/
rcu_sched_clock_irq(user_tick);
/* 如果内核配置了IRQ Work支持。*/
#ifdef CONFIG_IRQ_WORK
/* 如果当前在硬中断上下文中。*/
if (in_irq())
/* 则调用irq_work_tick,检查并处理挂起的IRQ work。*/
irq_work_tick();
#endif
/*
* 调用调度器的tick处理函数。它会调用当前任务所属调度类
* (如CFS)的task_tick方法,以更新时间片并检查是否需要抢占。
*/
sched_tick();
/* 如果内核配置了POSIX CPU定时器支持。*/
if (IS_ENABLED(CONFIG_POSIX_TIMERS))
/* 调用run_posix_cpu_timers,处理所有与进程CPU时间相关的POSIX定时器。*/
run_posix_cpu_timers();
}