文章目录
- [1. 前言](#1. 前言)
- [2. 硬件 和 框架](#2. 硬件 和 框架)
-
- [2.1 硬件](#2.1 硬件)
- [2.2 整体框架](#2.2 整体框架)
- [3. 实现](#3. 实现)
-
- [3.1 初始化](#3.1 初始化)
-
- [3.1.1 tick 初始化](#3.1.1 tick 初始化)
- [3.1.2 timekeeping 初始化](#3.1.2 timekeeping 初始化)
- [3.1.3 tick 设备 (clock_event_device) 注册](#3.1.3 tick 设备 (clock_event_device) 注册)
-
- [3.1.3.1 注册每 CPU 的 tick 设备(clock_event_device)](#3.1.3.1 注册每 CPU 的 tick 设备(clock_event_device))
- [3.1.3.2 注册广播 tick 设备](#3.1.3.2 注册广播 tick 设备)
- [3.1.4 clocksource 的 注册 和 选择](#3.1.4 clocksource 的 注册 和 选择)
- [3.2 每 CPU tick 中断的工作](#3.2 每 CPU tick 中断的工作)
-
- [3.2.1 oneshot 模式切换](#3.2.1 oneshot 模式切换)
- [3.2.2 每 tick 的工作](#3.2.2 每 tick 的工作)
- [3.3 广播 tick 唤醒](#3.3 广播 tick 唤醒)
- [3.4 用户空间观察](#3.4 用户空间观察)
- [4. 参考资料](#4. 参考资料)
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 硬件 和 框架
2.1 硬件
Linux 时间子系统需要底层硬件定时器的支持。以 ARM 架构为例,定时器硬件框架图如下:

上图中:
-
CPU Local Timer每 CPU 的 Local Timer,连接到
GIC中断控制器,生成PPI中断到对应的 CPU。 -
System Counter频率固定的全局的计数器,为每 CPU 本地的 Timer 提供计数器输入,和每 CPU 本地的 Timer 硬件计数比较单元一起,实现 Timer 功能。在
Armv8.6-A之前,System Counter的频率取决于实现,典型的频率区间为1MHz 到 50MHz,如常见的 24MHz;而从Armv8.6-A 到 Armv9.1-A,System Counter的频率固定为1GHz。System Counter的频率可以从CNTFRQ和CNTPCT等寄存器读取。 -
External Timer为了节省功耗,
CPU Local Timer可能进入低功耗的省电状态,这时候需要一个永不打烊的External Timer广播 tick 事件,并通过IPI_TIMER中断唤醒CPU Local Timer。
来看一个硬件实现例子:

2.2 整体框架
结合硬件、软件形成的 Linux 时间子系统整体框架如下:

上图中:
-
Clock Event device
timer对应前面硬件框图中的CPU Local Timer,抽象为clock_event_device,按CONFIG_HZ配置的频率,产生时钟中断。在时钟中断中,更新jiffies计数,更新wall time,调度软件 timer,进行tick 调度,进程数据统计等工作。 -
Clock Source device
counter对应前面硬件框图中的System Counter,抽象为clocksource,为时间管理模块timekeeping提供时钟源,同时也可以作为调度时钟(sched timer)。 -
其它
图中的其它部分在本文不做展开。
3. 实现
Linux 时间子系统有两大核心功能:
- 定时器(高、低精度 timer)
- 时间维护(timekeeping)
Linux 时间子系统的有两个核心部件:clock_event_device 和 clocksource。clock_event_device 对应每 CPU 的硬件 Timer,按 CONFIG_HZ 配置的频率,产生每 CPU 的 tick 中断,在 tick 中断中,做如下工作:
- 更新
jiffies计数 - 时间维护(timekeeping):从
clocksource读取 cycle 计数更新wall time - 调度软件高、低精度 timer
- 进行每 tick 调度:
scheduler_tick() - 进程时间更新和数据统计等工作
- 其它
接下来以 AllWinner H3(ARMv7,4 个 CPU 核) + Linux 4.14.x 为例,来分析下 Linux 时间子系统的实现细节。注意,本文只分析 Linux 时间子系统的核心流程,不涉及所有各种 nohz 模式的各种细节。
3.1 初始化
3.1.1 tick 初始化
c
start_kernel()
tick_init()
/*
* 创建 tick 设备 periodic 和 oneshot 模式下,接收 广播 tick CPU 集合掩码数据。
* 这些掩码数据初始为 空集合。
*/
tick_broadcast_init()
// 本文不讨论 CONFIG_NO_HZ_FULL=y 的情形。
// CONFIG_NO_HZ_FULL=n 的情形下,tick_nohz_init() 实现为空。
tick_nohz_init()
/* kernel/time/tick-broadcast.c */
void __init tick_broadcast_init(void)
{
/* 创建 并初始化 tick 设备 periodic 模式 广播 tick CPU 集合掩码数据 */
zalloc_cpumask_var(&tick_broadcast_mask, GFP_NOWAIT);
zalloc_cpumask_var(&tick_broadcast_on, GFP_NOWAIT);
zalloc_cpumask_var(&tmpmask, GFP_NOWAIT);
#ifdef CONFIG_TICK_ONESHOT
/* 创建并初始化 tick 设备 oneshot 模式 广播 tick CPU 集合掩码数据 */
zalloc_cpumask_var(&tick_broadcast_oneshot_mask, GFP_NOWAIT);
zalloc_cpumask_var(&tick_broadcast_pending_mask, GFP_NOWAIT);
zalloc_cpumask_var(&tick_broadcast_force_mask, GFP_NOWAIT);
#endif
}
3.1.2 timekeeping 初始化
timekeeping 负责时间维护,如果存在 RTC,则系统 BOOT 期间从 RTC 读取时间进行初始化,后续则从系统注册选中的 clocksource 读取 cycle 更新时间。这里先看 timekeeping 的初始化细节:
c
/* include/linux/timekeeper_internal.h */
struct timekeeper {
struct tk_read_base tkr_mono; /* CLOCK_MONOTONIC 时钟的 时间管理 数据 */
struct tk_read_base tkr_raw; /* CLOCK_MONOTONIC_RAW 时钟的 时间管理 数据 */
u64 xtime_sec; /* CLOCK_REALTIME 时钟的当前秒数 */
unsigned long ktime_sec; /* CLOCK_MONOTONIC 时钟的当前秒数 */
struct timespec64 wall_to_monotonic; /* CLOCK_REALTIME 时钟相对于 CLOCK_MONOTONIC 时钟的偏差 */
ktime_t offs_real;
ktime_t offs_boot;
ktime_t offs_tai;
s32 tai_offset;
unsigned int clock_was_set_seq;
u8 cs_was_changed_seq; /* 时间维护 时钟源(clocksource) 变更的次数 */
ktime_t next_leap_ktime;
u64 raw_sec; /* CLOCK_MONOTONIC_RAW 时钟秒数 */
/* The following members are for timekeeping internal use */
/*
* timkeeper 时间更新间隔 (cycle 数):
* 每隔 @cycle_interval 个 cycle timkeeper 更新一次时间。
*/
u64 cycle_interval;
u64 xtime_interval;
s64 xtime_remainder;
u64 raw_interval;
......
};
/* kernel/time/timekeeping.c */
void __init timekeeping_init(void)
{
struct timekeeper *tk = &tk_core.timekeeper;
struct clocksource *clock;
unsigned long flags;
struct timespec64 now, boot, tmp;
/*
* 从永久存储时钟(RTC)读取当前时间.
*
* read_persistent_clock64() 是一个 weak 符号,
* 默认定义在当前文件中, 且返回 0 值.
* arch 可以定义同名函数覆盖当前文件定义的默认 weak 定义,
* 如 arch/arm/kernel/time.c:
* static clock_access_fn __read_persistent_clock = dummy_clock_access;
*
* void read_persistent_clock64(struct timespec64 *ts)
* {
* __read_persistent_clock(ts);
* }
* ARM 定义的 read_persistent_clock64() 调用函数指针 __read_persistent_clock,
* __read_persistent_clock 默认指向 dummy_clock_access(), dummy_clock_access()
* 和默认的 weak 定义一样, 返回 0 值.
* __read_persistent_clock 函数指针可以通过 arch/armkernel/time.c 中定义的
* 接口 register_persistent_clock() 覆盖, 如:
* tegra20_init_rtc()
* -> register_persistent_clock(NULL, tegra_read_persistent_clock64)
*
* 在绝大多数硬件下, read_persistent_clock64() 总是返回 0, 这也是 H3 的情形.
*/
read_persistent_clock64(&now);
if (!timespec64_valid_strict(&now)) {
...
} else if (now.tv_sec || now.tv_nsec) /* 存在永久存储时钟(RTC) */
persistent_clock_exists = true;
/*
* 读取 boot 硬件时钟时间.
*
* read_boot_clock64() 是一个 weak 符号,
* 默认定义在当前文件中, 且返回 0 值.
* arch 可以定义同名函数覆盖当前文件定义的默认 weak 定义,
* 如 arch/arm/kernel/time.c:
* static clock_access_fn __read_boot_clock = dummy_clock_access;;
*
* void read_boot_clock64(struct timespec64 *ts)
* {
* __read_boot_clock(ts);
* }
* ARM 定义的 read_boot_clock64() 调用函数指针 __read_boot_clock,
* __read_boot_clock 默认指向 dummy_clock_access(), dummy_clock_access()
* 和默认的 weak 定义一样, 返回 0 值.
* __read_boot_clock 函数指针可以通过 arch/armkernel/time.c 中定义的
* 接口 register_persistent_clock() 覆盖.
* 当前内核代码树, 可以认为 read_boot_clock64() 总是返回 0.
*/
read_boot_clock64(&boot);
...
raw_spin_lock_irqsave(&timekeeper_lock, flags);
write_seqcount_begin(&tk_core.seq);
ntp_init();
/*
* 当前还没有任何 时钟源 注册,使用 默认时钟源 clocksource_jiffies 。
* 通过接口 clocksource_default_clock() 获取 默认时钟源.
* 默认时钟源 通常为 定义在 kernel/time/jiffies.c 中的
* clocksource_jiffies , 提供 jiffies 计数值。
*
* 因 clocksource_default_clock() 定义为 weak 符号,所以架构
* 也可以自定义 clocksource_default_clock() 接口来提供架构
* 特定的 默认时钟源,如:
* arch/s390/kernel/time.c 自定义的 clocksource_default_clock() .
*/
clock = clocksource_default_clock();
if (clock->enable)
clock->enable(clock);
tk_setup_internals(tk, clock); /* 设置 timekeeper 的 时钟源 为 缺省时钟源 @clock */
/*
* CLOCK_REALTIME (xtime) 时钟时间初始化:
* 用读到的永久存储时钟(RTC) @now 时间, 设为 CLOCK_REALTIME 时钟 的 初始时间.
*/
tk_set_xtime(tk, &now);
tk->raw_sec = 0;
/* 如果读到的 boot time 为 0, 则用 CLOCK_REALTIME 的初始值作为 CLOCK_BOOTTIME 的初始值 */
if (boot.tv_sec == 0 && boot.tv_nsec == 0)
boot = tk_xtime(tk);
/* 将 {@-boot.tv_sec, @-boot.tv_nsec} 设置到 @tmp */
set_normalized_timespec64(&tmp, -boot.tv_sec, -boot.tv_nsec);
/*
* 初始化 CLOCK_MONOTONIC, CLOCK_MONOTONIC_RAW 时钟:
* CLOCK_MONOTONIC 时间是单调递增的, 同时 CLOCK_MONOTONIC
* 时间为 CLOCK_REALTIME (xtime) 加上 wall_to_monotonic, 由
* 于系统 CLOCK_MONOTONIC 时间初始为 0, 所以将 wall_to_monotonic
* 初始为负数 xtime, 这样 xtime + wall_to_monotonic 则为 0 值,
* 即 CLOCK_MONOTONIC 时间初始为 0 .
*/
tk_set_wall_to_mono(tk, tmp);
/* 基于 CLOCK_REALTIME 时间 初始化 CLOCK_MONOTONIC ktime 等 */
timekeeping_update(tk, TK_MIRROR | TK_CLOCK_WAS_SET);
write_seqcount_end(&tk_core.seq);
raw_spin_unlock_irqrestore(&timekeeper_lock, flags);
}
static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)
{
u64 interval;
u64 tmp, ntpinterval;
struct clocksource *old_clock; /* CLOCK_MONOTONIC 时钟 旧的 时钟源 */
++tk->cs_was_changed_seq;
old_clock = tk->tkr_mono.clock; /* 保存 CLOCK_MONOTONIC 时钟 旧的 时钟源 */
tk->tkr_mono.clock = clock; /* 更新 CLOCK_MONOTONIC 时钟 的 时钟源 为 @clock */
tk->tkr_mono.mask = clock->mask;
/* 更新 CLOCK_MONOTONIC 时钟 的 cycle 为 时钟源 @clock 获取上一次读取的 cycle */
tk->tkr_mono.cycle_last = tk_clock_read(&tk->tkr_mono);
tk->tkr_raw.clock = clock; /* 同时更新 CLOCK_MONOTONIC_RAW 时钟 的 时钟源 为 @clock */
tk->tkr_raw.mask = clock->mask;
tk->tkr_raw.cycle_last = tk->tkr_mono.cycle_last; /* 更新 CLOCK_MONOTONIC_RAW 时钟 的 cycle */
/* Do the ns -> cycle conversion first, using original mult */
tmp = NTP_INTERVAL_LENGTH; /* 每个 时钟滴答 的 纳秒数 */
tmp <<= clock->shift; /* 将每个 时钟滴答 的 纳秒数 转换为 clocksource @clock 的 cycle 数 */
ntpinterval = tmp;
tmp += clock->mult/2; /* 四舍五入处理 */
/*
* cycle => ns: ns = (cycle * mult) >> shift
* ns => cycle: cycle = (ns << shift) / mult
*/
do_div(tmp, clock->mult); /* tmp /= clock->mult */
if (tmp == 0)
tmp = 1;
/* 记录 【时钟滴答间隔 cycle 数】 到 timekeeper */
interval = (u64) tmp;
tk->cycle_interval = interval;
/* Go back from cycles -> shifted ns */
/* 记录 时钟滴答间隔 cycle 数对应的 ns 时间到 timekeeper */
tk->xtime_interval = interval * clock->mult;
tk->xtime_remainder = ntpinterval - tk->xtime_interval;
tk->raw_interval = interval * clock->mult;
/* if changing clocks, convert xtime_nsec shift units */
/* 如果发生了 时钟源 切换, 按 新的时钟源 修正旧的时间 */
if (old_clock) {
int shift_change = clock->shift - old_clock->shift;
if (shift_change < 0) {
tk->tkr_mono.xtime_nsec >>= -shift_change;
tk->tkr_raw.xtime_nsec >>= -shift_change;
} else {
tk->tkr_mono.xtime_nsec <<= shift_change;
tk->tkr_raw.xtime_nsec <<= shift_change;
}
}
/* 切换为新时钟源的 shift */
tk->tkr_mono.shift = clock->shift;
tk->tkr_raw.shift = clock->shift;
tk->ntp_error = 0;
tk->ntp_error_shift = NTP_SCALE_SHIFT - clock->shift;
tk->ntp_tick = ntpinterval << tk->ntp_error_shift;
/*
* The timekeeper keeps its own mult values for the currently
* active clocksource. These value will be adjusted via NTP
* to counteract clock drifting.
*/
/* 切换为新时钟源的 mult */
tk->tkr_mono.mult = clock->mult;
tk->tkr_raw.mult = clock->mult;
tk->ntp_err_mult = 0;
}
static void tk_set_wall_to_mono(struct timekeeper *tk, struct timespec64 wtm)
{
struct timespec64 tmp;
/*
* Verify consistency of: offset_real = -wall_to_monotonic
* before modifying anything
*/
set_normalized_timespec64(&tmp, -tk->wall_to_monotonic.tv_sec,
-tk->wall_to_monotonic.tv_nsec);
WARN_ON_ONCE(tk->offs_real != timespec64_to_ktime(tmp));
tk->wall_to_monotonic = wtm;
set_normalized_timespec64(&tmp, -wtm.tv_sec, -wtm.tv_nsec);
tk->offs_real = timespec64_to_ktime(tmp);
tk->offs_tai = ktime_add(tk->offs_real, ktime_set(tk->tai_offset, 0));
}
/* 更新 timekeeping 相关的时间: CLOCK_MONOTONIC ktime 等 */
static void timekeeping_update(struct timekeeper *tk, unsigned int action)
{
...
tk_update_leap_state(tk);
tk_update_ktime_data(tk); /* 更新 ktime 时间 */
/*
* 同步 timekeeper 时间数据到 vDSO.
* vDSO 的 clock_gettime() 等系列接口从 vdso_data 获取系统时间.
*/
update_vsyscall(tk);
/* XEN 虚拟化时间更新相关处理 */
update_pvclock_gtod(tk, action & TK_CLOCK_WAS_SET);
update_fast_timekeeper(&tk->tkr_mono, &tk_fast_mono);
update_fast_timekeeper(&tk->tkr_raw, &tk_fast_raw);
if (action & TK_CLOCK_WAS_SET)
tk->clock_was_set_seq++;
/*
* The mirroring of the data to the shadow-timekeeper needs
* to happen last here to ensure we don't over-write the
* timekeeper structure on the next update with stale data
*/
if (action & TK_MIRROR)
memcpy(&shadow_timekeeper, &tk_core.timekeeper,
sizeof(tk_core.timekeeper));
}
到此,timekeeping 初始选择使用 clocksource clocksource_jiffies,初始化了各个时间轴的初始时间。
3.1.3 tick 设备 (clock_event_device) 注册
通过接口 clockevents_config_and_register() 注册 tick 设备(clock_event_device)。
3.1.3.1 注册每 CPU 的 tick 设备(clock_event_device)
timer_probe() 扫描注册所有的 tick 设备,这里先分析每 CPU 的 tick 设备注册,而广播 tick 设备的注册,则放到下一小节 3.1.3.2 分析。
c
start_kernel()
time_init()
machine_desc->init_time()
sun6i_timer_init()
timer_probe()
注册每 CPU 的 tick 设备:
c
/* drivers/clocksource/timer-probe.c */
void __init timer_probe(void)
{
struct device_node *np;
const struct of_device_id *match;
of_init_fn_1_ret init_func_ret;
unsigned timers = 0;
int ret;
/*
* 1) arch/arm/boot/dts/sun8i-h3.dtsi
* 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)>;
* };
*/
for_each_matching_node_and_match(np, __timer_of_table, &match) {
if (!of_device_is_available(np))
continue;
init_func_ret = match->data;
/*
* include/linux/clocksource.h: TIMER_OF_DECLARE() / CLOCKSOURCE_OF_DECLARE()
* drivers/clocksource/arm_arch_timer.c: arch_timer_of_init()
* ...
*/
ret = init_func_ret(np);
if (ret) {
pr_err("Failed to initialize '%pOF': %d\n", np, ret);
continue;
}
timers++;
}
......
}
c
/* drivers/clocksource/arm_arch_timer.c */
static int __init arch_timer_of_init(struct device_node *np)
{
...
arch_timers_present |= ARCH_TIMER_TYPE_CP15;
/*
* 解析 {Secure, NON-Secure, Virt, HYP} 模式 timer PPI 中断的
* DTS 配置,并将分配给它们的 Linux 虚拟中断号 记录到 arch_timer_ppi[].
* AllWinner H3:
* Linux IRQ | HW IRQ | Note
* 18 | 29 | arch_timer
* 19 | 30 | arch_timer
* 20 | 27 | kvm guest timer
* 21 | 26 | ???
*/
for (i = ARCH_TIMER_PHYS_SECURE_PPI; i < ARCH_TIMER_MAX_TIMER_PPI; i++)
arch_timer_ppi[i] = irq_of_parse_and_map(np, i);
...
/*
* 记录 时钟频率 到 arch_timer_rate:
* 如果设备 @np 设置了 "clock-frequency", 使用 "clock-frequency" 指定的频率;
* 否则使用 @rate 参数的频率。
*/
rate = arch_timer_get_cntfrq(); /* 从 CP15 读取 系统计数器(system counter) 的 时钟频率 */
arch_timer_of_configure_rate(rate, np);
...
/* 如果没有配置 "always-on", 则 c3 状态停止 timer */
arch_timer_c3stop = !of_property_read_bool(np, "always-on");
...
/* 选择使用 arch timer 的哪个 PPI 中断作为 clockevent device 的中断源 */
if (IS_ENABLED(CONFIG_ARM) &&
of_property_read_bool(np, "arm,cpu-registers-not-fw-configured"))
arch_timer_uses_ppi = ARCH_TIMER_PHYS_SECURE_PPI;
else
arch_timer_uses_ppi = arch_timer_select_ppi();
...
ret = arch_timer_register();
...
}
static int __init arch_timer_register(void)
{
int err;
int ppi;
/*
* 为 每个 ARM CPU 的私有 timer 分配 定时器硬件对象 clock_event_device.
*
* 如果实现了 ARM timer 扩展,架构要求每个 CPU 都实现一个自己的 timer,
* 因此,每个 CPU 都对应了一个 定时器硬件对象 clock_event_device.
*/
arch_timer_evt = alloc_percpu(struct clock_event_device);
...
/* 为 每个 CPU 的 timer 注册中断处理接口 */
ppi = arch_timer_ppi[arch_timer_uses_ppi];
switch (arch_timer_uses_ppi) {
...
case ARCH_TIMER_PHYS_SECURE_PPI:
case ARCH_TIMER_PHYS_NONSECURE_PPI:
/*
* 注册时 通过 @dev_id 参数 将 定时器设备对象
* arch_timer_evt (clock_event_device) 关联到 时钟源
*/
err = request_percpu_irq(ppi, arch_timer_handler_phys,
"arch_timer", arch_timer_evt);
if (!err && arch_timer_has_nonsecure_ppi()) {
ppi = arch_timer_ppi[ARCH_TIMER_PHYS_NONSECURE_PPI];
err = request_percpu_irq(ppi, arch_timer_handler_phys,
"arch_timer", arch_timer_evt);
...
}
break;
...
}
...
err = cpuhp_setup_state(CPUHP_AP_ARM_ARCH_TIMER_STARTING,
"clockevents/arm/arch_timer:starting",
arch_timer_starting_cpu, arch_timer_dying_cpu);
...
}
static int arch_timer_starting_cpu(unsigned int cpu)
{
struct clock_event_device *clk = this_cpu_ptr(arch_timer_evt); /* 每 CPU 的 tick 设备 */
u32 flags;
...
__arch_timer_setup(ARCH_TIMER_TYPE_CP15, clk);
...
}
static void __arch_timer_setup(unsigned type,
struct clock_event_device *clk)
{
clk->features = CLOCK_EVT_FEAT_ONESHOT; /* 标记设备支持 oneshot 模式 */
if (type == ARCH_TIMER_TYPE_CP15) {
if (arch_timer_c3stop)
clk->features |= CLOCK_EVT_FEAT_C3STOP; /* CPU C3 状态下, 定时器 系统计数器 停止计数 */
} else {
...
}
clk->set_state_shutdown(clk); /* arch_timer_shutdown_phys() */
/* 注册每 CPU tick 定时器设备 */
clockevents_config_and_register(clk, arch_timer_rate, 0xf, 0x7fffffff);
}
c
/* include/linux/clockchips.h */
struct clock_event_device {
/* 设备每 tick 中断事件的处理接口 */
void (*event_handler)(struct clock_event_device *);
...
/* 编程 定时器设备 下一触发 的 时间 */
int (*set_next_event)(unsigned long evt, struct clock_event_device *);
...
enum clock_event_state state_use_accessors; /* 状态: CLOCK_EVT_STATE_PERIODIC, CLOCK_EVT_STATE_ONESHOT, ... */
unsigned int features; // CLOCK_EVT_FEAT_PERIODIC, CLOCK_EVT_FEAT_ONESHOT, ...
...
/* tick 广播事件 处理接口 */
void (*broadcast)(const struct cpumask *mask);
...
int rating;
...
const struct cpumask *cpumask; /* 指示此设备用于哪些 CPU */
...
};
/* kernel/time/clockevents.c */
void clockevents_config_and_register(struct clock_event_device *dev,
u32 freq, unsigned long min_delta,
unsigned long max_delta)
{
dev->min_delta_ticks = min_delta;
dev->max_delta_ticks = max_delta;
clockevents_config(dev, freq);
clockevents_register_device(dev);
}
void clockevents_register_device(struct clock_event_device *dev)
{
...
list_add(&dev->list, &clockevent_devices); /* 添加到全局 clock_event_device 列表 @clockevent_devices */
tick_check_new_device(dev);
...
}
c
/* kernel/time/tick-common.c */
void tick_check_new_device(struct clock_event_device *newdev)
{
struct clock_event_device *curdev;
struct tick_device *td;
int cpu;
cpu = smp_processor_id();
td = &per_cpu(tick_cpu_device, cpu);
curdev = td->evtdev;
...
clockevents_exchange_device(curdev, newdev);
tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
/*
* 发送有支持 oneshot 的 tick 设备注册的异步通知.
* 在 cpu 切换到 hrtimer 场景会检查这些异步通知:
* hrtimer_run_queues()
* tick_check_oneshot_change()
*/
if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
tick_oneshot_notify();
return;
...
}
static void tick_setup_device(struct tick_device *td,
struct clock_event_device *newdev, int cpu,
const struct cpumask *cpumask)
{
...
/*
* First device setup ?
*/
if (!td->evtdev) { /* 首次配置 @cpu 滴答设备 tick_device (clock_event_device) */
/* 当前处于 BOOT 阶段,还没有 CPU 负责时间维护更新(timekeeping) */
if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) {
/* nohz_full CPU 不能用来负责时间维护更新: 因为它们会在必要时禁用调度时钟中断滴答 */
if (!tick_nohz_full_cpu(cpu))
/*
* BOOT 阶段,还没有负责时间维护更新的 CPU,
* 同时 @cpu 也不是指定为 nohz_full 模式,
* 则将其作为负责时间维护的 CPU:
* 调度时钟中断滴答中调用 do_timer() 维护更新系统时间.
*
* @cpu 通常是 BOOT CPU.
*/
tick_do_timer_cpu = cpu;
else
...
/*
* 读取 CLOCK_MONOTONIC 时钟 的 当前时间 (纳秒数) 。
*
* CLOCK_MONOTONIC 时钟 是 单调递增 的 时间体系。
* 此时间体系的时间原点并不重要,在 Linux 中是以系统启动的时间点
* 作为时间原点,在计算机休眠时会暂停走时,受 adjtime() 和 NTP 的
* 影响可能会向前跳跃。
*/
tick_next_period = ktime_get();
tick_period = NSEC_PER_SEC / HZ; /* tick 设备以 HZ 为周期产生时钟中断 */
}
/*
* Startup in periodic mode first.
*/
td->mode = TICKDEV_MODE_PERIODIC; /* tick_device 启动默认为 TICKDEV_MODE_PERIODIC 模式 */
} else {
...
}
td->evtdev = newdev; /* 设定 @cpu 新 tick 设备为 @newdev */
...
if (td->mode == TICKDEV_MODE_PERIODIC)
tick_setup_periodic(newdev, 0);
else
...
}
void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
{
/* 设置 TICKDEV_MODE_PERIODIC 模式 定时器事件 处理接口 */
tick_set_periodic_handler(dev, broadcast);
...
if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) &&
!tick_broadcast_oneshot_active()) {
clockevents_switch_state(dev, CLOCK_EVT_STATE_PERIODIC); /* tick 设备切换到 CLOCK_EVT_STATE_PERIODIC 态 */
} else {
...
}
}
c
/* kernel/time/tick-broadcast.c */
int tick_device_uses_broadcast(struct clock_event_device *dev, int cpu)
{
struct clock_event_device *bc = tick_broadcast_device.evtdev;
...
if (!tick_device_is_functional(dev)) {
...
} else {
if (!(dev->features & CLOCK_EVT_FEAT_C3STOP))
...
else /* @cpu C3 态 定时器设备 会进入 低功耗、低电 状态, */
/* 为其设置广播事件处理接口,在 tick broadcast 设备事件时的 IPI_TIMER 被唤醒 */
tick_device_setup_broadcast_func(dev);
/*
* Clear the broadcast bit if the CPU is not in
* periodic broadcast on state.
*/
if (!cpumask_test_cpu(cpu, tick_broadcast_on))
cpumask_clear_cpu(cpu, tick_broadcast_mask);
switch (tick_broadcast_device.mode) {
...
case TICKDEV_MODE_PERIODIC:
/* tick_broadcast_mask 为空, 表示没有 CPU 需要广播事件, 则可以关闭 广播定时器设备 */
if (cpumask_empty(tick_broadcast_mask) && bc)
clockevents_shutdown(bc);
...
break;
...
}
}
}
static void tick_device_setup_broadcast_func(struct clock_event_device *dev)
{
if (!dev->broadcast)
dev->broadcast = tick_broadcast;
...
}
void tick_set_periodic_handler(struct clock_event_device *dev, int broadcast)
{
if (!broadcast)
dev->event_handler = tick_handle_periodic;
else
...
}
到此,每 CPU 的 tick 设备已经注册完成,完成的主要工作可以概括为 2 点:
- 每 CPU 的 tick 设备设置为
TICKDEV_MODE_PERIODIC模式,CPU 本地 tick 中断处理接口设置为tick_handle_periodic() - 由于每 CPU 的 tick 设备标记为
CLOCK_EVT_FEAT_C3STOP,需要广播 tick 唤醒,所以设置广播 tick 中断事件处理接口设置为tick_broadcast()。
需要说明的是,因为每 CPU 的 tick 设备(对应硬件 Timer)是 CPU 私有的,所以注册的数目和 CPU 数目一样,如 AllWinner H3 有 4 个 CPU 核,每个 CPU 都有一个私有的硬件 Timer,所以就注册了 4 个 CPU 私有的 tick 设备(对应硬件 Timer)。
3.1.3.2 注册广播 tick 设备
本小节分析广播 tick 设备的注册过程。那为什么需要广播 tick 设备?因为每 CPU 的 tick timer 设备有可能进入低功耗状态,这时候其 Timer 会暂停工作,所以在后续 CPU 有工作需要做的时候,就需要有人来唤醒它,这个唤醒就是靠广播 tick 设备。
在分析广播 tick 设备的注册前,先来个小插曲。事实上,在 BOOT CPU 0 上,AllWinner H3 的实现的 External Timer 比 CPU 0 的 Local Timer 先注册,但后注册的 CPU 0 的 Local Timer 因为 rating 比 AllWinner H3 的实现的 External Timer 更高,也即精度更好,所以 CPU 0 的 Local Timer 就替代 AllWinner H3 的实现的 External Timer 成为了 CPU 0 的 tick 设备。本小节的分析,就从 AllWinner H3 的 External Timer 已经注册为 CPU 0 的 tick 设备之后,开始注册 CPU 0 的 Local Timer 为 CPU 0 的 tick 设备时开始,这里就有一部分流程和上一小节相同,我们简略重复一下,从 clockevents_config_and_register() 开始:
c
clockevents_config_and_register()
clockevents_register_device()
/*
* 用 CPU Local Timer 替代 AllWinner H3 的 External Timer,
* 注册为 CPU 0 的 tick 设备,而 AllWinner H3 的 External Timer
* 则放入到列表 clockevents_released 中。
*/
tick_check_new_device(dev);
clockevents_exchange_device(curdev, newdev)
void clockevents_exchange_device(struct clock_event_device *old,
struct clock_event_device *new)
{
/*
* Caller releases a clock event device. We queue it into the
* released list and do a notify add later.
*/
if (old) {
module_put(old->owner);
clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED); /* 关停旧设备 @old */
list_del(&old->list); /* 将设备 @old 从列表 @clockevent_devices 移除 */
list_add(&old->list, &clockevents_released); /* 将设备 @old 添加到列表 @clockevents_released */
}
if (new) {
BUG_ON(!clockevent_state_detached(new));
clockevents_shutdown(new); /* 配置新设备时, 首先关停新设备 @new */
}
}
此时,AllWinner H3 的 External Timer 已经被 CPU 0 的 Local Timer 代替,自身被放到了 clockevents_released 列表中。继续分析:
c
clockevents_config_and_register()
clockevents_register_device()
/*
* 用 CPU Local Timer 替代 AllWinner H3 的 External Timer,
* 注册为 CPU 0 的 tick 设备,而 AllWinner H3 的 External Timer
* 则放入到列表 clockevents_released 中。
*/
tick_check_new_device(dev);
clockevents_exchange_device(curdev, newdev)
clockevents_notify_released();
static void clockevents_notify_released(void)
{
struct clock_event_device *dev;
while (!list_empty(&clockevents_released)) {
dev = list_entry(clockevents_released.next,
struct clock_event_device, list);
list_del(&dev->list);
list_add(&dev->list, &clockevent_devices);
/* External Timer 在被 CPU 0 Local Timer 替换后,成为 广播 tick 设备 */
tick_check_new_device(dev);
}
}
/* 流程再次进入 tick_check_new_device(),将 External Timer 设置为 广播 tick 设备 */
void tick_check_new_device(struct clock_event_device *newdev)
{
struct clock_event_device *curdev;
struct tick_device *td;
int cpu;
cpu = smp_processor_id();
td = &per_cpu(tick_cpu_device, cpu);
curdev = td->evtdev;
if (!tick_check_percpu(curdev, newdev, cpu)) /* 如果 @newdev 不能作为 CPU @cpu 的本地设备 */
goto out_bc;
...
out_bc:
/*
* Can the new device be used as a broadcast device ?
*/
// AllWinner H3, drivers/clocksource/sun4i_timer.c
tick_install_broadcast_device(newdev);
}
void tick_install_broadcast_device(struct clock_event_device *dev)
{
struct clock_event_device *cur = tick_broadcast_device.evtdev;
// AllWinner H3, drivers/clocksource/sun4i_timer.c
tick_broadcast_device.evtdev = dev; // 设定 tick broadcast 设备
if (!cpumask_empty(tick_broadcast_mask))
tick_broadcast_start_periodic(dev);
/*
* 发送有支持 oneshot 的 tick 设备注册的异步通知.
* 在 cpu 切换到 hrtimer 场景会检查这些异步通知:
* hrtimer_run_queues()
* tick_check_oneshot_change()
*/
if (dev->features & CLOCK_EVT_FEAT_ONESHOT)
tick_clock_notify();
}
static void tick_broadcast_start_periodic(struct clock_event_device *bc)
{
if (bc)
tick_setup_periodic(bc, 1);
}
void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
{
/* 设置 TICKDEV_MODE_PERIODIC 模式 定时器事件 处理接口 */
tick_set_periodic_handler(dev, broadcast);
if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) &&
!tick_broadcast_oneshot_active()) {
clockevents_switch_state(dev, CLOCK_EVT_STATE_PERIODIC);
} else {
...
}
}
void tick_set_periodic_handler(struct clock_event_device *dev, int broadcast)
{
if (!broadcast)
...
else
dev->event_handler = tick_handle_periodic_broadcast;
}
好了,广播 tick 设备的注册和配置也已经完成了。
3.1.4 clocksource 的 注册 和 选择
理论上有了 tick 设备,已经建立计时体系,为什么还要注册 clocksource?理由很简单,tick 设备的计时粒度太粗。如配置 CONFIG_HZ=250,则一个 tick 的时长为 1000/250=4ms,这种时间粒度,在现在的系统上,显然是不够的。可能有读者会问,那配置更高的 CONFIG_HZ 值呢?如果这样,会因太频繁的 Timer 中断,会给系统造成极高负载,也是无法可行,所以最后,我们需要可以直接读取其 cycle 的 clocksource。下面来看 AllWinner H3 将 System Counter 硬件注册为系统最佳 clocksource 的细节:
c
start_kernel()
time_init()
machine_desc->init_time()
sun6i_timer_init()
timer_probe()
arch_timer_of_init()
arch_timer_common_init()
arch_counter_register()
c
/* drivers/clocksource/arm_arch_timer.c */
/*
* arch_counter_register() 设定的 系统计数器(system counter) 的值
* 读取接口. 不同 CPU 模式下,各不相同.
*/
u64 (*arch_timer_read_counter)(void) = arch_counter_get_cntvct; /* arch_counter_get_cntpct() */
/* 读取 系统计数器 */
static u64 arch_counter_read(struct clocksource *cs)
{
return arch_timer_read_counter();
}
static struct clocksource clocksource_counter = {
.name = "arch_sys_counter",
.rating = 400,
.read = arch_counter_read,
.mask = CLOCKSOURCE_MASK(56),
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
static void __init arch_counter_register(unsigned type)
{
...
/* Register the CP15 based counter if we have one */
if (type & ARCH_TIMER_TYPE_CP15) {
if (IS_ENABLED(CONFIG_ARM64) ||
arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI)
...
else
arch_timer_read_counter = arch_counter_get_cntpct; /* System Counter 读取接口 */
clocksource_counter.archdata.vdso_direct = vdso_default;
} else {
...
}
...
clocksource_register_hz(&clocksource_counter, arch_timer_rate); /* 注册 ARM System Counter 为 系统可选 clocksource 之一 */
...
/* 56 bits minimum, so we assume worst case rollover */
sched_clock_register(arch_timer_read_counter, 56, arch_timer_rate); /* 注册 为 调度时钟 */
}
c
/* include/linux/clocksource.h */
struct clocksource {
/*
* kernel/time/jiffies.c, clocksource_jiffies, jiffies_read() (通常启动初期作为 缺省时钟源)
* drivers/clocksource/arm_arch_timer.c: arch_counter_read()
* drivers/clocksource/sun4i_timer.c: clocksource_mmio_readl_down()
* ...
*/
u64 (*read)(struct clocksource *cs); /* 读取 时钟源 当前的 cycle */
/*
* 从 @read 接口读取的是 cycle , 通过乘数
* @mult 和 除数 1<<@shift 将 cycle 转换为 ns:
* t = (cycle * mult) >> shift
*/
u64 mask;
u32 mult;
u32 shift;
u64 max_idle_ns;
u32 maxadj;
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
struct arch_clocksource_data archdata;
#endif
u64 max_cycles;
const char *name;
struct list_head list; /* 用来挂接到全局列表 @clocksource_list */
int rating;
int (*enable)(struct clocksource *cs);
void (*disable)(struct clocksource *cs);
/*
* 如:
* drivers/clocksource/arm_arch_timer.c 定时器驱动设定
* CLOCK_SOURCE_SUSPEND_NONSTOP, 表示 CPU C3 挂起低电
* 状态下,也不会停止 timer 系统计数器(system counter)
* 的计数.
*
* CLOCK_SOURCE_IS_CONTINUOUS: drivers/clocksource/sun4i_timer.c
*
* ...
*/
unsigned long flags;
void (*suspend)(struct clocksource *cs);
void (*resume)(struct clocksource *cs);
void (*mark_unstable)(struct clocksource *cs);
void (*tick_stable)(struct clocksource *cs);
...
};
static inline int clocksource_register_hz(struct clocksource *cs, u32 hz)
{
return __clocksource_register_scale(cs, 1, hz);
}
c
/* kernel/time/clocksource.c */
int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)
{
/* Initialize mult/shift and max_idle_ns */
__clocksource_update_freq_scale(cs, scale, freq); /* 时钟源 参数 计算设置,并打印 计算设置 的 参数 */
/* Add clocksource to the clocksource list */
mutex_lock(&clocksource_mutex);
clocksource_enqueue(cs); /* 添加到系统 clocksource 列表 @clocksource_list */
/*
* 在 CONFIG_CLOCKSOURCE_WATCHDOG=y 的情形下, 将带
* CLOCK_SOURCE_MUST_VERIFY 标志的 clocksource 添加
* 到 被观察的 clocksource 列表 watchdog_list, 然后
* 用其它作为 watchdog 的 clocksource 来观察这些
* clocksource 稳定性(误差), 最终在 BOOT 结尾时, 通
* 过 clocksource_select() 做一次 clocksource 选择,
* 选择最好的 clocksource 作为 系统时钟源.
*
* 在 CONFIG_CLOCKSOURCE_WATCHDOG=y 的情形下, 不做
* clocksource 的观察验证.
*/
...
clocksource_select(); /* 挑选最合适的 clocksource 的作为 系统时钟源 */
...
mutex_unlock(&clocksource_mutex);
return 0;
}
static void clocksource_select(void)
{
__clocksource_select(false);
}
static void __clocksource_select(bool skipcur)
{
bool oneshot = tick_oneshot_mode_active();
struct clocksource *best, *cs;
/* Find the best suitable clocksource */
best = clocksource_find_best(oneshot, skipcur); /* 选择最好的时钟源 */
if (!best)
return;
/* Check for the override clocksource. */
list_for_each_entry(cs, &clocksource_list, list) {
if (skipcur && cs == curr_clocksource)
continue;
if (strcmp(cs->name, override_name) != 0)
continue;
/*
* Check to make sure we don't switch to a non-highres
* capable clocksource if the tick code is in oneshot
* mode (highres or nohz)
*/
if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) && oneshot) {
/* Override clocksource cannot be used. */
if (cs->flags & CLOCK_SOURCE_UNSTABLE) {
pr_warn("Override clocksource %s is unstable and not HRT compatible - cannot switch while in HRT/NOHZ mode\n",
cs->name);
override_name[0] = 0;
} else {
/*
* The override cannot be currently verified.
* Deferring to let the watchdog check.
*/
pr_info("Override clocksource %s is not currently HRT compatible - deferring\n",
cs->name);
}
} else
/* Override clocksource can be used. */
best = cs;
break;
}
/* 新选择的时钟源 不同于 当前时钟源, 通知 timekeeping 做 clocksource 变更处理 */
if (curr_clocksource != best && !timekeeping_notify(best)) {
/*
* 切换到 新的 时钟源:
* [ 0.272644] clocksource: Switched to clocksource arch_sys_counter
*/
pr_info("Switched to clocksource %s\n", best->name);
curr_clocksource = best; /* 选择最好的 clocksource,记录到 @curr_clocksource */
}
}
curr_clocksource 记录了选择到最好的 clocksource,作为 timekeeping 的时间来源。另外,System Counter 也被注册为调度时钟,给调度子系统使用:
c
arch_counter_register()
sched_clock_register(arch_timer_read_counter, 56, arch_timer_rate); /* 注册 为 调度时钟 */
/* kernel/time/sched_clock.c */
static struct clock_data cd ____cacheline_aligned = {
.read_data[0] = { .mult = NSEC_PER_SEC / HZ,
.read_sched_clock = jiffy_sched_clock_read, },
.actual_read_sched_clock = jiffy_sched_clock_read,
};
void __init
sched_clock_register(u64 (*read)(void), int bits, unsigned long rate)
{
u64 res, wrap, new_mask, new_epoch, cyc, ns;
u32 new_mult, new_shift;
unsigned long r;
char r_unit;
struct clock_read_data rd;
if (cd.rate > rate) /* 如果 现有 调度时钟 的 频率更高 */
return; /* 直接返回: 更高的时钟频率意味着有更细的调度粒度 */
/* Calculate the mult/shift to convert counter ticks to ns. */
clocks_calc_mult_shift(&new_mult, &new_shift, rate, NSEC_PER_SEC, 3600);
new_mask = CLOCKSOURCE_MASK(bits);
cd.rate = rate;
/* Calculate how many nanosecs until we risk wrapping */
wrap = clocks_calc_max_nsecs(new_mult, new_shift, 0, new_mask, NULL);
cd.wrap_kt = ns_to_ktime(wrap);
rd = cd.read_data[0];
/* Update epoch for new counter and update 'epoch_ns' from old counter*/
new_epoch = read();
cyc = cd.actual_read_sched_clock();
ns = rd.epoch_ns + cyc_to_ns((cyc - rd.epoch_cyc) & rd.sched_clock_mask, rd.mult, rd.shift);
cd.actual_read_sched_clock = read;
rd.read_sched_clock = read; /* 重置读取接口为 System Counter 读取接口 arch_timer_read_counter() */
rd.sched_clock_mask = new_mask;
rd.mult = new_mult;
rd.shift = new_shift;
rd.epoch_cyc = new_epoch;
rd.epoch_ns = ns;
update_clock_read_data(&rd);
if (sched_clock_timer.function != NULL) {
/* update timeout for clock wrap */
hrtimer_start(&sched_clock_timer, cd.wrap_kt, HRTIMER_MODE_REL);
}
r = rate;
if (r >= 4000000) {
r /= 1000000;
r_unit = 'M';
} else {
if (r >= 1000) {
r /= 1000;
r_unit = 'k';
} else {
r_unit = ' ';
}
}
/* Calculate the ns resolution of this counter */
res = cyc_to_ns(1ULL, new_mult, new_shift);
pr_info("sched_clock: %u bits at %lu%cHz, resolution %lluns, wraps every %lluns\n",
bits, r, r_unit, res, wrap);
...
}
BOOT 期间,可能多次注册调度时钟,但最终会选择 rate 最高的那个。
3.2 每 CPU tick 中断的工作
3.2.1 oneshot 模式切换
从前面的分析了解到,注册的每 CPU tick 设备工作在 TICKDEV_MODE_PERIODIC 模式,即每次定时器到期,不需要重新编程,就会在下次到期时间点,自动触发 tick 中断。
在启用了 hrtimer 的系统下(CONFIG_HIGH_RES_TIMERS=y),系统会将每 CPU tick 设备切换为 TICKDEV_MODE_ONESHOT 模式。切换场景是在一次 tick 中断中,从每 CPU tick 中断处理入口 arch_timer_handler_phys() 开始:
c
arch_timer_handler_phys()
timer_handler()
evt->event_handler(evt)
tick_handle_periodic()
void tick_handle_periodic(struct clock_event_device *dev)
{
int cpu = smp_processor_id();
...
tick_periodic(cpu);
...
}
static void tick_periodic(int cpu)
{
...
update_process_times(user_mode(get_irq_regs()));
...
}
void update_process_times(int user_tick)
{
...
run_local_timers();
...
}
void run_local_timers(void)
{
struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);
hrtimer_run_queues();
...
}
c
/* kernel/time/hrtime.c */
void hrtimer_run_queues(void)
{
struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
ktime_t now;
/* 如果 CPU 上已经 激活/切换到 hrtimer, 则直接返回 */
if (__hrtimer_hres_active(cpu_base))
return;
if (tick_check_oneshot_change(!hrtimer_is_hres_enabled())) {
hrtimer_switch_to_hres(); /* 当前 CPU 所有 timer 基准(base)切换为高精度 hrtimer */
return;
}
...
}
int tick_check_oneshot_change(int allow_nohz)
{
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
/*
* 检查是否有异步的 tick device 设备变更通知?
* tick_oneshot_notify() / tick_clock_notify()
* 1) tick 设备注册 / replace
* 1.1) tick 设备注册 [为 cpu local 设备]
* clockevents_register_device()
* tick_check_new_device()
* if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
* tick_oneshot_notify();
* 1.2) tick broadcast 注册 [为 broadcast 设备]
* clockevents_register_device()
* tick_check_new_device()
* clockevents_notify_released()
* tick_check_new_device()
* tick_install_broadcast_device()
* if (dev->features & CLOCK_EVT_FEAT_ONESHOT)
* tick_clock_notify()
* 2) clocksource 更换
* clocksource_select()
* __clocksource_select()
* timekeeping_notify()
* tick_clock_notify()
*/
if (!test_and_clear_bit(0, &ts->check_clocks))
return 0;
if (ts->nohz_mode != NOHZ_MODE_INACTIVE)
return 0;
if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
return 0;
if (!allow_nohz)
return 1;
tick_nohz_switch_to_nohz();
return 0;
}
/*
* Switch to high resolution mode
*/
static void hrtimer_switch_to_hres(void)
{
struct hrtimer_cpu_base *base = this_cpu_ptr(&hrtimer_bases);
/* hrtimer 模式下, 使用 oneshot 模式, 不然中断频率过高, 会把系统搞崩 */
if (tick_init_highres()) {
printk(KERN_WARNING "Could not switch to high resolution "
"mode on CPU %d\n", base->cpu);
return;
}
base->hres_active = 1; /* 标记当前 CPU 上 hrtimer base 已经激活 */
hrtimer_resolution = HIGH_RES_NSEC;
/*
* 在切换到 hrtime 之前, 使用硬件 timer 来进行每 tick 调度;
* 在切换到 hrtime 之后, 使用一个 hrtimer 来模拟硬件 timer
* 来进行每 tick 调度。模拟 hrtimer 回调接口为 tick_sched_timer() 。
*/
tick_setup_sched_timer();
/* "Retrigger" the interrupt to get things going */
retrigger_next_event(NULL);
}
c
/* kernel/time/tick-oneshot.c */
#ifdef CONFIG_HIGH_RES_TIMERS
/**
* tick_init_highres - switch to high resolution mode
*
* Called with interrupts disabled.
*/
int tick_init_highres(void)
{
return tick_switch_to_oneshot(hrtimer_interrupt);
}
#endif
int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *))
{
struct tick_device *td = this_cpu_ptr(&tick_cpu_device);
struct clock_event_device *dev = td->evtdev;
...
td->mode = TICKDEV_MODE_ONESHOT; /* CPU 的 tick device 切换为 oneshot 模式 */
dev->event_handler = handler; /* hrtimer_interrupt() */
clockevents_switch_state(dev, CLOCK_EVT_STATE_ONESHOT); /* CPU 的 tick device 切换为 oneshot 状态 */
tick_broadcast_switch_to_oneshot(); /* broadcast tick device 也要跟着切换为 oneshot 模式 */
return 0;
}
c
/* kernel/time/tick-sched.c */
void tick_setup_sched_timer(void)
{
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
ktime_t now = ktime_get();
/*
* Emulate tick processing via per-CPU hrtimers:
*/
hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
ts->sched_timer.function = tick_sched_timer;
/* Get the next period (per-CPU) */
hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());
...
hrtimer_forward(&ts->sched_timer, now, tick_period);
hrtimer_start_expires(&ts->sched_timer, HRTIMER_MODE_ABS_PINNED);
tick_nohz_activate(ts, NOHZ_MODE_HIGHRES);
}
以上流程完成的主要工作有:
- 每 CPU tick 设备由 periodic 切换为 oneshot 模式,每 tick 中断的回调接口由
tick_handle_periodic()变更为hrtimer_interrupt()。 - 广播 tick 设备由 periodic 切换为 oneshot 模式(如果已激活的话),回调接口从
tick_handle_periodic_broadcast()变更为tick_handle_oneshot_broadcast()。 - 每 CPU tick 中断的调度工作,由在原来的 tick 中断中进行,变更为在一个 hrtimer 中处理,该 hrtimer 的回调入口为
tick_sched_timer()。
3.2.2 每 tick 的工作
如果每 CPU tick 工作在 periodic 模式下,则在 tick_handle_periodic() 中处理每 tick 中断的工作,本文对此不做展开,一是因为这不是现在设备的典型情形,二是该函数中展开工作和 oneshot 模式 + hrtimer 的工作也基本相同。
接下来看在 oneshot 模式下,每 CPU tick 中断中的工作,从 hrtimer_interrupt() 开始(之分析主干部分):
c
/* kernel/time/hrtimer.c */
hrtimer_interrupt()
...
/*
* 检查 hrtimer 超时时间是否到期,如果到期则触发 hrtimer 回调,
* 并自动重启返回值 != HRTIMER_NORESTART 的 hrtimer。
* 注意,现在调度工作在一个 hrtimer 中执行,即这里会周期性的触
* 发 tick_sched_timer() 回调。
*/
__hrtimer_run_queues(cpu_base, now);
...
tick_program_event(expires_next, 1); /* 编程下一次 tick 中断 */
...
c
/* kernel/time/tick-sched.c */
#ifdef CONFIG_HIGH_RES_TIMERS
/*
* We rearm the timer until we get disabled by the idle code.
* Called with interrupts disabled.
*/
static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer)
{
struct tick_sched *ts =
container_of(timer, struct tick_sched, sched_timer);
struct pt_regs *regs = get_irq_regs();
ktime_t now = ktime_get();
tick_sched_do_timer(now); /* jiffies 计数更新 + 系统 load 计算 */
/*
* Do not call, when we are not in irq context and have
* no valid regs pointer
*/
if (regs)
tick_sched_handle(ts, regs); /* 每 tick schedule + 进程状态统计 + rcu 工作 + 高、低精度 timer 触发 + ... */
else
ts->next_tick = 0;
...
hrtimer_forward(timer, now, tick_period);
/* 周期性重启 hrtimer 模拟的 schedule tick */
return HRTIMER_RESTART;
}
static void tick_sched_handle(struct tick_sched *ts, struct pt_regs *regs)
{
...
update_process_times(user_mode(regs));
...
}
c
/* kernel/time/timer.c */
void update_process_times(int user_tick)
{
struct task_struct *p = current;
/* Note: this timer irq context must be accounted for as well. */
account_process_tick(p, user_tick);
run_local_timers(); /* 运行当前 CPU 上的 软件 timer */
rcu_check_callbacks(user_tick);
#ifdef CONFIG_IRQ_WORK
if (in_irq())
irq_work_tick();
#endif
scheduler_tick(); /* 周期性的调度 */
if (IS_ENABLED(CONFIG_POSIX_TIMERS))
run_posix_cpu_timers(p);
}
void run_local_timers(void)
{
struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);
hrtimer_run_queues(); /* 高精度 hrtimer 处理 */
/* Raise the softirq only if required. */
if (time_before(jiffies, base->clk)) {
if (!IS_ENABLED(CONFIG_NO_HZ_COMMON))
return;
/* CPU is awake, so check the deferrable base. */
base++;
if (time_before(jiffies, base->clk))
return;
}
raise_softirq(TIMER_SOFTIRQ); /* 低精度 soft timer 在 SOFTIRQ 中处理 */
}
3.3 广播 tick 唤醒
CPU 在某些情形下会进入低功耗状态,同时 CPU 的 Local Timer 也会停止工作,这时如果有工作进来需要 CPU 处理,则需要用不打烊的广播 tick 设备来唤醒它们。
因为只有在 BOOT 期间,有很短的时间 CPU tick 设备工作在 periodic 模式,所以这里对这种情形不做讨论,只讨论 oneshot 模式下的情形。
首先看 CPU 进入低功耗状态时,预订广播 tick 事件的流程:
c
void cpu_startup_entry(enum cpuhp_state state)
{
...
while (1)
do_idle();
}
static void do_idle(void)
{
...
while (!need_resched()) {
...
local_irq_disable();
...
if (cpu_idle_force_poll || tick_check_broadcast_expired())
cpu_idle_poll();
else
cpuidle_idle_call();
...
}
...
}
cpuidle_idle_call()
call_cpuidle(drv, dev, next_state); /* 进入 cpu idle 态 */
cpuidle_enter()
cpuidle_enter_state()
/* CPU 预定广播事件 */
tick_broadcast_enter()
tick_broadcast_oneshot_control(TICK_BROADCAST_ENTER)
__tick_broadcast_oneshot_control(state) // 见后面分析
/*
* CPU 停止 Local Timer 进入 idle 态,停止后续执行流程,
* 直到被唤醒(如本文分析的广播 tick 事件),再继续执行后
* 续代码。
*/
entered_state = target_state->enter(dev, drv, index);
arm_enter_idle_state()
int __tick_broadcast_oneshot_control(enum tick_broadcast_state state)
{
struct clock_event_device *bc, *dev;
int cpu, ret = 0;
ktime_t now;
/*
* If there is no broadcast device, tell the caller not to go
* into deep idle.
*/
if (!tick_broadcast_device.evtdev) /* 没有注册广播 tick 设备 */
return -EBUSY;
dev = this_cpu_ptr(&tick_cpu_device)->evtdev;
raw_spin_lock(&tick_broadcast_lock);
bc = tick_broadcast_device.evtdev;
cpu = smp_processor_id();
if (state == TICK_BROADCAST_ENTER) {
...
/* @cpu 预定 oneshot 模式下的广播 tick 事件 (如 cpu 进入 idle 时: cpuidle_idle_call()) */
if (!cpumask_test_and_set_cpu(cpu, tick_broadcast_oneshot_mask)) {
...
/* Conditionally shut down the local timer. */
broadcast_shutdown_local(bc, dev); /* 关闭 CPU @cpu 的 Local Timer */
...
}
} else {
...
}
}
上面的逻辑,我们主要关注 CPU 在进入休眠时:
- 关闭了
CPU Local Timer(有些例外情形,这里不细表) - 加入了
tick_broadcast_oneshot_mask掩码,也即预定了广播 tick 事件
然后,在广播 tick 设备的中断中,如 AllWinner H3 的 External Timer 的中断处理接口 sun4i_timer_interrupt() 中, 唤醒预定了广播 tick 的那些 CPU(我们仍然只讨论 oneshot 模式)处理每 tick 事务(如果 CPU 的 tick 事件到期的话)。
要注意的是,不管是被广播 tick 中断唤醒的 CPU,还是其它通过 IPI_TIMER 唤醒的 CPU(不包括被广播 tick 中断唤醒的 CPU),当前都处于 CPU 本地中断禁用状态,因此接收的中断会先被挂起,直到中断启用后在进入中断流程。所有被唤醒的 CPU (不管是被广播 tick 中断唤醒的 CPU,还是后续被 IPI_TIMER 中断唤醒的 CPU),都会继续在进入 idle 状态的函数 cpuidle_enter_state() 继续往后执行下面的流程:
c
/* drivers/cpuidle/cpuidle.c */
int cpuidle_enter_state(struct cpuidle_device *dev, struct cpuidle_driver *drv,
int index)
{
int entered_state;
struct cpuidle_state *target_state = &drv->states[index];
bool broadcast = !!(target_state->flags & CPUIDLE_FLAG_TIMER_STOP);
ktime_t time_start, time_end;
s64 diff;
/*
* Tell the time framework to switch to a broadcast timer because our
* local timer will be shut down. If a local timer is used from another
* CPU as a broadcast timer, this call may fail if it is not available.
*/
if (broadcast && tick_broadcast_enter()) {
...
}
...
/*
* CPU 在这里进入 idle 状态,并将执行流程停在此处,直到
* 后面被唤醒(如广播 tick),再继续执行后面的流程。
* drivers/cpuidle/cpuidle-arm.c: arm_enter_idle_state()
* ...
*/
entered_state = target_state->enter(dev, drv, index);
/*
* 1) 被广播 tick 中断唤醒的 CPU,继续执行后续流程。
* 2) 除被广播 tick 中断唤醒的 CPU外,被 IPI_TIMER
* 中断唤醒的其它,继续执行后续流程。
*/
...
if (broadcast) {
if (WARN_ON_ONCE(!irqs_disabled()))
local_irq_disable();
/*
* 取消当前 CPU 对广播 tick 事件的预定:
* 从掩码 tick_broadcast_oneshot_mask 中移除 CPU
*/
tick_broadcast_exit();
}
/* 进入 idle 状态是在 CPU 本地中断本禁用的上下文,现在重启 CPU 的本地中断 */
if (!cpuidle_state_is_coupled(drv, index))
local_irq_enable();
...
return entered_state;
}
好了,现在某个 CPU 被广播 tick 中断唤醒后,取消当前 CPU 对广播 tick 事件的预定,然后重启了该 CPU 的本地中断,开始进入中断处理 sun4i_timer_interrupt(),然后通过 IPI_TIMER 唤醒其它(预定了广播 tick 事件的)CPU:
c
sun4i_timer_interrupt() /* drivers/clocksource/sun4i_timer.c */
evt->event_handler(evt)
tick_handle_oneshot_broadcast()
/* kernel/time/tick-broadcast.c */
static void tick_handle_oneshot_broadcast(struct clock_event_device *dev)
{
struct tick_device *td;
ktime_t now, next_event;
int cpu, next_cpu = 0;
bool bc_local;
raw_spin_lock(&tick_broadcast_lock);
dev->next_event = KTIME_MAX;
next_event = KTIME_MAX;
cpumask_clear(tmpmask);
now = ktime_get();
/* Find all expired events */
for_each_cpu(cpu, tick_broadcast_oneshot_mask) { /* 找到 tick 过期的 idle cpu */
...
td = &per_cpu(tick_cpu_device, cpu);
if (td->evtdev->next_event <= now) { /* idle @cpu 的 tick 时间已经到期 */
cpumask_set_cpu(cpu, tmpmask);
/*
* Mark the remote cpu in the pending mask, so
* it can avoid reprogramming the cpu local
* timer in tick_broadcast_oneshot_control().
*/
cpumask_set_cpu(cpu, tick_broadcast_pending_mask); /* 标记 idle @cpu 有挂起的 广播 tick 事件 */
} else if (td->evtdev->next_event < next_event) { /* 还没到 idle @cpu 的 tick 时间 */
next_event = td->evtdev->next_event;
next_cpu = cpu;
}
}
/*
* Remove the current cpu from the pending mask. The event is
* delivered immediately in tick_do_broadcast() !
*/
/* 当前被 广播 tick 中断 唤醒的 CPU, 不需要向自身发送 tick 广播 */
cpumask_clear_cpu(smp_processor_id(), tick_broadcast_pending_mask);
/* Take care of enforced broadcast requests */
cpumask_or(tmpmask, tmpmask, tick_broadcast_force_mask);
cpumask_clear(tick_broadcast_force_mask);
...
/*
* Wakeup the cpus which have an expired event.
*/
bc_local = tick_do_broadcast(tmpmask); /* 向预定了广播 tick 事件的 CPU 集合发送 IPI_TIMER 唤醒 */
raw_spin_unlock(&tick_broadcast_lock);
if (bc_local) {
td = this_cpu_ptr(&tick_cpu_device);
/*
* 触发当前被 广播 tick 中断唤醒 CPU 上的每 tick 处理事务:
* oneshot 模式触发回调 hrtimer_interrupt() 。
*/
td->evtdev->event_handler(td->evtdev);
}
}
static bool tick_do_broadcast(struct cpumask *mask)
{
int cpu = smp_processor_id();
struct tick_device *td;
bool local = false;
/* 将当前被 广播 tick 中断唤醒的 CPU 排除在外: 自己不用向自己发送 IPI_TIMER */
if (cpumask_test_cpu(cpu, mask)) {
struct clock_event_device *bc = tick_broadcast_device.evtdev;
cpumask_clear_cpu(cpu, mask);
local = !(bc->features & CLOCK_EVT_FEAT_HRTIMER);
}
if (!cpumask_empty(mask)) {
td = &per_cpu(tick_cpu_device, cpumask_first(mask));
td->evtdev->broadcast(mask); /* tick_broadcast() */
}
return local;
}
c
/* arch/arm/kernel/smp.c */
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
void tick_broadcast(const struct cpumask *mask)
{
smp_cross_call(mask, IPI_TIMER);
}
#endif
void handle_IPI(int ipinr, struct pt_regs *regs)
{
unsigned int cpu = smp_processor_id();
struct pt_regs *old_regs = set_irq_regs(regs);
switch (ipinr) {
...
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
case IPI_TIMER:
irq_enter();
tick_receive_broadcast();
irq_exit();
break;
#endif
...
}
}
其它 CPU 被 IPI_TIMER 唤醒,处理每 tick 的事务:
c
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
int tick_receive_broadcast(void)
{
struct tick_device *td = this_cpu_ptr(&tick_cpu_device);
struct clock_event_device *evt = td->evtdev;
if (!evt)
return -ENODEV;
if (!evt->event_handler)
return -EINVAL;
/*
* (1) TICKDEV_MODE_PERIODIC 模式
* tick_handle_periodic() [arm_arch_timer.c]
* (2) TICKDEV_MODE_ONESHOT 模式
* hrtimer_interrupt()
*/
evt->event_handler(evt); /* hrtimer_interrupt() */
return 0;
}
#endif
3.4 用户空间观察
可通过 /proc/timer_list 观察每 CPU 上的 timer、tick 设备信息,广播 tick 设备信息。
4. 参考资料
1\] [What is the Generic Timer?](https://developer.arm.com/documentation/102379/0104/What-is-the-Generic-Timer-?lang=en) \[2\] [External timers](https://developer.arm.com/documentation/102379/0104/External-timers?lang=en "External timers") \[3\] DDI0487A_j_armv8_arm.pdf \[4\] DDI0406C_d_armv7ar_arm.pdf \[5\] DDI0471B_gic400_r0p1_trm.pdf \[6\] [从硬件到软件,Linux 时间子系统全栈解析](https://kernel.meizu.com/2023/12/13/Full-stack-resolution-of-the-Linux-time-subsystem/ "从硬件到软件,Linux 时间子系统全栈解析") \[7\] [Linux Time](https://kernel.meizu.com/2018/07/12//linux-time.html/ "Linux Time")