Linux 时间子系统 (1):基础框架概述

文章目录

  • [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-ASystem Counter 的频率固定为 1GHzSystem Counter 的频率可以从 CNTFRQCNTPCT 等寄存器读取。

  • 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_deviceclocksourceclock_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 因为 ratingAllWinner H3 的实现的 External Timer 更高,也即精度更好,所以 CPU 0 的 Local Timer 就替代 AllWinner H3 的实现的 External Timer 成为了 CPU 0 的 tick 设备。本小节的分析,就从 AllWinner H3External 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 H3External 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 H3System 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 H3External 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")

相关推荐
天选之女wow1 小时前
【Hard——Day8】65.有效数字、68.文本左右对齐、76.最小覆盖子串
linux·运维·redis·算法·leetcode
精英的英1 小时前
【嵌入式Linux开发】如何在Windows上开发Linux ARM版本QT程序
linux·arm开发·windows
咯哦哦哦哦1 小时前
linux patchelf工具 用法
linux·vscode·编辑器·gcc
努力的小帅1 小时前
Linux_进程控制(Linux入门到精通)
linux·网络·shell·进程创建·linux入门
睡觉然后上课1 小时前
如何让虚拟机运行速度翻倍
linux·arm开发·windows
喜欢你,还有大家2 小时前
DaemonSet && service && ingress的
linux·架构·kubernetes
咸鱼の猫2 小时前
用samba服务器将虚拟机的Ubuntu(磁盘)映射到本地电脑实现文件互传
linux·服务器·ubuntu
小年糕是糕手3 小时前
【C++】C++入门 -- inline、nullptr
linux·开发语言·jvm·数据结构·c++·算法·排序算法
工具人55553 小时前
Linux远程登录
linux·运维·服务器