title: tick
categories:
- linux
 - kernel
 - time
tags: - linux
 - kernel
 - time
abbrlink: 16af98cc
date: 2025-10-03 09:01:49 
文章目录
- [kernel/time/tick-*.c 内核时钟滴答(The Kernel Tick) 驱动时间流逝与进程抢占](#kernel/time/tick-*.c 内核时钟滴答(The Kernel Tick) 驱动时间流逝与进程抢占)
 - kernel/time/tick-internal.h
 - 
- [tick_set_periodic_handler 设置周期性处理程序](#tick_set_periodic_handler 设置周期性处理程序)
 
 - kernel/time/tick-common.c
 - 
- [tick_do_timer_cpu 存储负责调用 `do_timer()` 的 CPU 编号](#tick_do_timer_cpu 存储负责调用 
do_timer()的 CPU 编号) - [clockevents_program_min_delta 将时钟事件设备设置为最小延迟](#clockevents_program_min_delta 将时钟事件设备设置为最小延迟)
 - [tick_setup_periodic 设置为周期时钟](#tick_setup_periodic 设置为周期时钟)
 - [检查新设备是否比 curdev 更合适](#检查新设备是否比 curdev 更合适)
 - [tick_setup_device 设置时钟事件设备](#tick_setup_device 设置时钟事件设备)
 - [tick_check_new_device 检查是否应使用新注册的设备](#tick_check_new_device 检查是否应使用新注册的设备)
 - [tick_periodic 处理周期性时钟事件](#tick_periodic 处理周期性时钟事件)
 - [tick_handle_periodic 处理周期性时钟事件](#tick_handle_periodic 处理周期性时钟事件)
 
 - [tick_do_timer_cpu 存储负责调用 `do_timer()` 的 CPU 编号](#tick_do_timer_cpu 存储负责调用 
 - kernel/time/tick-oneshot.c
 - 
- [tick_oneshot_mode_active - 检查系统是否处于 OneShot 模式](#tick_oneshot_mode_active - 检查系统是否处于 OneShot 模式)
 - [tick_setup_oneshot 将事件设备设置为 oneshot 模式(HRES 或 NOHZ)](#tick_setup_oneshot 将事件设备设置为 oneshot 模式(HRES 或 NOHZ))
 - tick_program_event
 
 - kernel/time/tick-sched.c
 - 
- [tick_nohz_start_idle 开始空闲状态](#tick_nohz_start_idle 开始空闲状态)
 - [tick_nohz_idle_enter 准备在当前 CPU 上进入空闲状态](#tick_nohz_idle_enter 准备在当前 CPU 上进入空闲状态)
 - [tick_nohz_stop_idle 停止空闲状态](#tick_nohz_stop_idle 停止空闲状态)
 - [tick_nohz_idle_exit 在空闲任务退出时更新刻度](#tick_nohz_idle_exit 在空闲任务退出时更新刻度)
 - [can_stop_idle_tick 检查是否可以停止空闲刻度](#can_stop_idle_tick 检查是否可以停止空闲刻度)
 - [tick_nohz_idle_stop_tick 停止空闲任务的空闲刻度](#tick_nohz_idle_stop_tick 停止空闲任务的空闲刻度)
 - [tick_check_oneshot_change 检查是否需要重新编程单次刻度](#tick_check_oneshot_change 检查是否需要重新编程单次刻度)
 
 

kernel/time/tick-*.c 内核时钟滴答(The Kernel Tick) 驱动时间流逝与进程抢占
历史与背景
这项技术是为了解决什么特定问题而诞生的?
kernel/time/tick-*.c 目录下的文件集合构成了内核时钟滴答(Kernel Tick)的底层实现。这项技术是所有分时操作系统的心跳(Heartbeat),它的诞生是为了解决两个最根本的问题:
- 时间流逝的度量 :内核需要一个机制来驱动内部的时间概念。没有一个周期性的"滴答",像
jiffies这样的计数器将无法更新,基于jiffies的传统定时器(timer_list)也将永远不会到期。 - 强制性进程抢占(Preemptive Multitasking) :在一个协作式多任务系统中,一个进程会一直运行直到它自愿放弃CPU。这会导致一个死循环的进程就能锁死整个系统。为了实现抢占式多任务,内核需要一个周期性的、不可抗拒的事件来中断当前正在运行的进程,并给调度器一个机会去检查是否有其他进程应该运行。这个事件就是时钟滴答中断。
 
简而言之,内核滴答是调度器得以强制执行时间片轮转 和内核传统时间得以向前流逝的基础驱动力。
它的发展经历了哪些重要的里程碑或版本迭代?
内核滴答的实现方式经历了从简单到复杂的巨大演进,其核心驱动力是节能 和降低操作系统抖动(OS Jitter)。
- 固定周期性滴答(Periodic Tick) :这是最原始、最简单的模型。一个硬件定时器被设置为以固定的
HZ频率(例如每秒100、250或1000次)周期性地产生中断。这种模型的优点是实现简单、行为恒定。但其致命缺点是:即使系统完全空闲,CPU也会被这个中断毫无意义地、频繁地唤醒,这会阻止CPU进入深度睡眠状态,造成巨大的能源浪费。 - 无滴答空闲(Tickless Idle / 
NO_HZ_IDLE) :这是内核电源管理领域的一次革命。其核心思想是:当一个CPU核心即将进入空闲状态时,内核会取消 周期性的滴答中断。取而代之,它会计算出下一个需要处理的定时事件(无论是timer_list还是hrtimer)的精确时间点,然后使用高精度定时器(hrtimer)来编程一个**一次性(one-shot)**的中断,恰好在那个时间点触发。这使得空闲的CPU可以深度睡眠数秒甚至更长时间,极大地节省了功耗。 - 完全无滴答(Full Tickless / 
NO_HZ_FULL) :这是NO_HZ的进一步演进。NO_HZ_IDLE只在CPU完全空闲时才停止滴答。但对于某些特定负载,如高性能计算(HPC)或硬实时任务,即使CPU上正忙于运行一个单一的用户空间进程 ,内核滴答本身也会构成一种不必要的干扰(OS Jitter)。NO_HZ_FULL允许在这种情况下也停止滴答,从而为该进程提供一个几乎"无干扰"的运行环境,将CPU完全让给应用程序。 
目前该技术的社区活跃度和主流应用情况如何?
内核滴答是内核时间子系统的绝对基础。其现代的、可配置的(周期性 vs. 无滴答)实现是所有现代Linux系统的标准配置。
- 主流应用 :
NO_HZ_IDLE是几乎所有现代桌面、服务器和移动Linux系统的默认配置,是实现出色能效的关键。NO_HZ_FULL则是一个更专业的选项,被用于延迟敏感的高性能计算、高频交易和硬实时系统中,以追求极致的低延迟和可预测性。
 
核心原理与设计
它的核心工作原理是什么?
内核滴答的实现被分散在tick-common.c, tick-sched.c, tick-broadcast.c, tick-oneshot.c等文件中,其核心是围绕struct tick_sched对象展开的,并由高精度定时器(hrtimer)驱动。
- 
滴答的"引擎"------
tick_sched_timer:- 在现代内核中,周期性滴答不再由一个独立的、低分辨率的硬件定时器驱动。取而代之的是,内核为每个CPU启动一个周期性的高精度定时器(
hrtimer) ,名为tick_sched_timer。 - 这个
hrtimer的回调函数是tick_sched_timer(),它就是滴答事件的核心处理函数。 
 - 在现代内核中,周期性滴答不再由一个独立的、低分辨率的硬件定时器驱动。取而代之的是,内核为每个CPU启动一个周期性的高精度定时器(
 - 
滴答事件处理 (
tick_sched_timer()/tick_handle_periodic()):- 当这个
hrtimer到期时,它的回调函数会执行一系列关键的周期性任务:
a. 更新jiffies:调用do_timer()来增加全局的jiffies计数。
b. 运行传统定时器 :检查并运行当前jiffies值对应的所有到期的timer_list定时器。
c. 通知调度器 :调用调度器 的scheduler_tick()函数。这是CFS实现时间片轮转的关键,scheduler_tick()会更新当前进程的vruntime,并检查是否需要设置TIF_NEED_RESCHED标志以触发抢占。
d. 更新进程记账:更新当前进程所消耗的CPU时间统计(用户态/内核态)。 
 - 当这个
 - 
无滴答(Tickless)模式的实现:
- 进入空闲/隔离 :当CPU进入空闲(
NO_HZ_IDLE)或隔离(NO_HZ_FULL)状态时,内核会调用tick_stop_idle()或tick_nohz_start_tick()等函数。这些函数的核心操作就是取消 那个周期性的tick_sched_timer。 - 编程下一个事件 :接着,内核会查询所有定时器子系统(
hrtimer,timer_list),找出下一个最早需要触发的事件的时间点。 - 然后,它会使用一个一次性(one-shot)的
hrtimer,将硬件编程为在那个精确的时间点触发中断。 - 退出 :当CPU被唤醒时(无论是被那个一次性定时器还是其他外部中断唤醒),内核会调用
tick_check_idle()或tick_nohz_stop_tick()等函数,重新启动 周期性的tick_sched_timer。 
 - 进入空闲/隔离 :当CPU进入空闲(
 
它的主要优势体现在哪些方面?
- 提供基础节拍:为所有依赖于周期性检查的传统内核子系统提供了驱动力。
 - 实现抢占:是调度器实现公平性的基础。
 - 节能 :通过
NO_HZ机制,在不牺牲功能的前提下,实现了巨大的功耗节省。 - 降低抖动 :通过
NO_HZ_FULL,为性能敏感的应用提供了更稳定、可预测的运行环境。 
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 固定开销(周期性模式下):在传统的周期性模式下,无论系统负载如何,滴答中断都会持续产生固定的CPU开销。
 - 精度限制 :滴答的频率
HZ决定了jiffies的时间粒度,所有依赖它的子系统都无法实现比1/HZ秒更高的精度。 - 复杂性(无滴答模式下) :
NO_HZ的实现逻辑,特别是NO_HZ_FULL,涉及到复杂的定时器管理、CPU间状态同步和记账,是内核中一个相当复杂的领域。 
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
内核滴答是一个内核内部的基础设施,用户或内核开发者不直接"选择"它,而是选择依赖于它的上层API。
- 任何使用
jiffies或timer_list的代码,都隐式地依赖于内核滴答来驱动它们。 - 任何以
SCHED_NORMAL(CFS)运行的进程,都依赖于内核滴答来保证其时间片能够被公平地轮转。 
是否有不推荐使用该技术的场景?为什么?
这个问题应该理解为"何时应该避免依赖滴答的行为?"
- 高精度定时 :任何需要亚毫秒级精度的场景,都不应该 依赖
jiffies或滴答周期,而应该直接使用**hrtimers**。 - 高性能追踪 :不应通过在循环中检查
jiffies变化来进行性能测量,而应使用sched_clock()或ktime。 
对比分析
请将其 与 其他相似技术 进行详细对比。
| 特性 | 内核滴答 (Kernel Tick) | hrtimer (高精度定时器) | 
sched_clock() (调度器时钟) | 
|---|---|---|---|
| 核心用途 | 提供一个周期性的系统节拍,驱动低分辨率时间并触发抢占。 | 提供高精度的、一次性或周期性的未来事件调度。 | 提供一个极低开销的、高精度的当前时间戳。 | 
| 角色关系 | 是hrtimer的客户 。现代滴答是通过一个周期性的hrtimer实现的。 | 
是滴答的底层引擎。为滴答提供精确的触发能力。 | 与滴答互补 。滴答是"事件",sched_clock是"时间源"。 | 
| 分辨率 | 低 (1/HZ秒,毫秒级)。 | 
高 (纳秒级)。 | 高 (纳秒级)。 | 
| 功能 | 触发一系列周期性任务。 | 调度一个特定的回调函数在未来执行。 | 读取当前时间。 | 
| 开销 | 中等 (在周期性模式下是固定开aho销)。 | 较高 (比timer_list高)。 | 
极低。 | 
| 适用场景 | 内核的"心跳",驱动jiffies和CFS时间片。 | 
nanosleep,实时应用,精确超时。 | 
内核内部的性能测量和高精度记账。 | 
kernel/time/tick-internal.h
tick_set_periodic_handler 设置周期性处理程序
            
            
              c
              
              
            
          
          /**
 * tick_set_periodic_handler - 设置周期性处理程序
 * @dev:时钟事件设备
 * @broadcast:是否广播
 *
 * 设置周期性处理程序。
 *
 */
static inline void tick_set_periodic_handler(struct clock_event_device *dev, int broadcast)
{
	dev->event_handler = tick_handle_periodic;
}
        kernel/time/tick-common.c
tick_do_timer_cpu 存储负责调用 do_timer() 的 CPU 编号
tick_do_timer_cpu是一个内核定时器核心的内部变量,用于存储负责调用do_timer()的 CPU 编号(CPU NR)。do_timer()是一个与时间管理相关的函数,负责更新系统的时间信息。该变量的主要作用是协调多核系统中与时间管理相关的任务分配,避免资源竞争并支持动态的 CPU 状态变化
- 防止"惊群效应":
- 在多核系统中,如果没有协调机制,可能会有大量 CPU 同时尝试获取时间管理相关的锁(如时间保持锁 timekeeping lock),导致"惊群效应"(thundering herd problem)。
 - tick_do_timer_cpu 通过指定一个特定的 CPU 来负责 do_timer() 的调用,避免了多个 CPU 同时争抢锁的情况,从而提高了系统的效率
 
 - 支持 NOHZ 空闲模式
- 在 NOHZ(No HZ)模式下,当 CPU 进入空闲状态时,tick_do_timer_cpu 的值会被设置为 TICK_DO_TIMER_NONE,表示当前没有 CPU 负责时间管理任务
 - 这种设计允许下一个检查该变量的 CPU 自动接管时间管理任务,确保系统的时间保持功能不会中断
 - 这种机制还支持 CPU 热插拔(hotplug),即当某个 CPU 被移除或添加时,时间管理任务可以动态地分配给其他 CPU。
 
 
            
            
              c
              
              
            
          
          /* 表示在系统启动时,默认的时间管理任务由引导 CPU(boot CPU)负责 */
int tick_do_timer_cpu __read_mostly = TICK_DO_TIMER_BOOT;
        clockevents_program_min_delta 将时钟事件设备设置为最小延迟
            
            
              c
              
              
            
          
          /**
 * clockevents_program_min_delta - 将时钟事件设备设置为最小延迟。
 * @dev:设备到程序
 *
 * 成功时返回 0,重试循环失败时返回 -ETIME。
 */
static int clockevents_program_min_delta(struct clock_event_device *dev)
{
	unsigned long long clc;
	int64_t delta = 0;
	int i;
	for (i = 0; i < 10; i++) {
		delta += dev->min_delta_ns;
		dev->next_event = ktime_add_ns(ktime_get(), delta);
		if (clockevent_state_shutdown(dev))
			return 0;
		dev->retries++;
		clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
		if (dev->set_next_event((unsigned long) clc, dev) == 0)
			return 0;
	}
	return -ETIME;
}
        tick_setup_periodic 设置为周期时钟
            
            
              c
              
              
            
          
          /*
 * 为周期性滴答声设置设备
 */
void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
{
	/* //在非广播模式下设置周期性处理程序
	static inline void tick_set_periodic_handler(struct clock_event_device *dev, int broadcast)
	{
		dev->event_handler = tick_handle_periodic;
	} */
	tick_set_periodic_handler(dev, broadcast);
	/* 调用 tick_device_is_functional 检查设备是否处于功能正常的状态。
	 * 如果设备不可用,直接返回,不进行进一步配置。 
	 */
	if (!tick_device_is_functional(dev))
		return;
	/* 如果设备支持周期模式(CLOCK_EVT_FEAT_PERIODIC)且当前未处于广播单次触发模式 */
	if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) &&
	    !tick_broadcast_oneshot_active()) {
		/* 将设备的状态切换为周期模式 
			在这种模式下,设备会自动生成周期性中断,无需进一步手动编程*/
		clockevents_switch_state(dev, CLOCK_EVT_STATE_PERIODIC);
	} else {
		/* 配置为单次触发模式 */
		unsigned int seq;
		ktime_t next;
		/* 使用序列计数器(jiffies_seq)读取 tick_next_period 的值,确保读取操作的一致性 */
		do {
			seq = read_seqcount_begin(&jiffies_seq);
			next = tick_next_period;
		} while (read_seqcount_retry(&jiffies_seq, seq));
		/* 切换设备状态为单次触发模式: */
		clockevents_switch_state(dev, CLOCK_EVT_STATE_ONESHOT);
		for (;;) {
			/* 为设备设置下一个触发时间点。如果编程成功,退出循环*/
			if (!clockevents_program_event(dev, next, false))
				return;
			/* 使用 ktime_add_ns 将当前触发时间点 next 增加一个滴答周期(TICK_NSEC),为下一个事件计算新的触发时间。 */
			next = ktime_add_ns(next, TICK_NSEC);
		}
	}
}
        检查新设备是否比 curdev 更合适
            
            
              c
              
              
            
          
          static bool tick_check_percpu(struct clock_event_device *curdev,
			      struct clock_event_device *newdev, int cpu)
{
	if (!cpumask_test_cpu(cpu, newdev->cpumask))
		return false;
	if (cpumask_equal(newdev->cpumask, cpumask_of(cpu)))
		return true;
	/* Check if irq affinity can be set */
	if (newdev->irq >= 0 && !irq_can_set_affinity(newdev->irq))
		return false;
	/* Prefer an existing cpu local device */
	if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))
		return false;
	return true;
}
static bool tick_check_preferred(struct clock_event_device *curdev,
				 struct clock_event_device *newdev)
{
	/* 更喜欢一击即发的设备e */
	if (!(newdev->features & CLOCK_EVT_FEAT_ONESHOT)) {
		if (curdev && (curdev->features & CLOCK_EVT_FEAT_ONESHOT))
			return false;
		if (tick_oneshot_mode_active())
			return false;
	}
	/*
	 * Use the higher rated one, but prefer a CPU local device with a lower
	 * rating than a non-CPU local device
	 */
	return !curdev ||
		newdev->rating > curdev->rating ||
	       !cpumask_equal(curdev->cpumask, newdev->cpumask);
}
/*
 * 检查新设备是否比 curdev 更合适。curdev 可以是 NULL !
 */
bool tick_check_replacement(struct clock_event_device *curdev,
			    struct clock_event_device *newdev)
{
	if (!tick_check_percpu(curdev, newdev, smp_processor_id()))
		return false;
	return tick_check_preferred(curdev, newdev);
}
        tick_setup_device 设置时钟事件设备
            
            
              c
              
              
            
          
          /*
 * Setup the tick device
 */
static void tick_setup_device(struct tick_device *td,
			      struct clock_event_device *newdev, int cpu,
			      const struct cpumask *cpumask)
{
	void (*handler)(struct clock_event_device *) = NULL;
	ktime_t next_event = 0;
	/*
	 * 首次设置设备 ?
	 */
	if (!td->evtdev) {
		/*
		 * 如果没有 cpu 进行do_timer更新,请将其分配给此 cpu:
		 */
		if (READ_ONCE(tick_do_timer_cpu) == TICK_DO_TIMER_BOOT) {
			WRITE_ONCE(tick_do_timer_cpu, cpu);
			/* 获取当前时间并设置 */
			tick_next_period = ktime_get();
		}
		/*
		 * 首先以周期模式启动。
		 */
		td->mode = TICKDEV_MODE_PERIODIC;
	} else {
		handler = td->evtdev->event_handler;
		next_event = td->evtdev->next_event;
		td->evtdev->event_handler = clockevents_handle_noop;
	}
	td->evtdev = newdev;
	/*
	 *更新设备和中断亲和性
	 */
	if (!cpumask_equal(newdev->cpumask, cpumask))
		/* 将中断绑定到当前 CPU */
		irq_set_affinity(newdev->irq, cpumask);
	/* 
	 * 当全局广播处于活动状态时,检查当前设备是否注册为广播模式的占位符。
	 * 这允许我们以通用方式处理这个 x86 错误特性。
	 * 当我们保持此 CPU 的当前活动广播状态时,此函数也会返回 !=0。
	 */
	/* 检查新设备是否被注册为广播模式的占位符。如果是,则直接返回 */
	if (tick_device_uses_broadcast(newdev, cpu))
		return;
	/* 根据设备模式(周期模式或单次触发模式),调用相应的设置函数 */
	if (td->mode == TICKDEV_MODE_PERIODIC)
		tick_setup_periodic(newdev, 0);
	else
		tick_setup_oneshot(newdev, handler, next_event);
}
        tick_check_new_device 检查是否应使用新注册的设备
            
            
              c
              
              
            
          
          /*
 * 检查是否应使用新注册的设备。在保持 clockevents_lock 并禁用中断的情况下调用。
 */
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_replacement(curdev, newdev))
		goto out_bc;
	if (!try_module_get(newdev->owner))
		return;
	/*
	 * 用 newdevice 替换最终存在的设备。
	 * 如果当前设备是广播设备,请不要将其返回给 clockevents 层!
	 */
	if (tick_is_broadcast_device(curdev)) {
		clockevents_shutdown(curdev);
		curdev = NULL;
	}
	clockevents_exchange_device(curdev, newdev);
	tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
	if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
		tick_oneshot_notify();
	return;
out_bc:
	/*
	 * 新设备可以用作广播设备吗?
	 */
	tick_install_broadcast_device(newdev, cpu);
}
        tick_periodic 处理周期性时钟事件
- tick_periodic是Linux内核中周期性时钟节拍(periodic tick)的核心处理函数。在配置为传统tick模式的系统上,这个函数在每一次硬件定时器中断发生时,都会被tick_handle_periodic回调函数调用。
它的核心作用是:推进系统的jiffies计数,并触发所有与tick相关的周期性任务,如进程时间统计和性能剖析。 
            
            
              c
              
              
            
          
          /*
 * 下一次滴答事件:跟踪滴答时间。
 * 它由处理滴答事件的CPU更新,并且受到 jiffies_lock 的保护。
 * 对于它来说,不需要在写操作时持有 jiffies 的 seqcount。
 */
ktime_t tick_next_period;
/*
 * 这是一个静态函数,是周期性tick的核心处理逻辑。
 * 它在每个tick中断中被tick_handle_periodic调用。
 * @cpu: 当前正在执行此中断的CPU的编号。
 */
static void tick_periodic(int cpu)
{
	/*
	 * 检查当前CPU是否是被指定负责更新全局jiffies的那个CPU。
	 * tick_do_timer_cpu是一个全局变量,在启动时被设为某个固定的CPU。
	 * READ_ONCE用于确保原子地读取这个可能被并发访问的变量。
	 */
	if (READ_ONCE(tick_do_timer_cpu) == cpu) {
		/*
		 * 进入jiffies更新的临界区。
		 * 获取专门保护jiffies_64的自旋锁。
		 */
		raw_spin_lock(&jiffies_lock);
		/*
		 * 开始一次序列计数器的写操作。它会使jiffies_seq变为奇数,
		 * 以通知所有乐观的读者,数据正在被修改。
		 */
		write_seqcount_begin(&jiffies_seq);
		/* 注释:追踪下一个tick事件。*/
		/* 将下一个周期性tick的预期时间戳向前推进一个tick周期。*/
		tick_next_period = ktime_add_ns(tick_next_period, TICK_NSEC);
		/*
		 * 调用do_timer宏,它会将全局的jiffies_64计数器加1。
		 * 这是jiffies增长的直接执行点。
		 */
		do_timer(1);
		/* 结束序列计数器的写操作,将计数器值加1使其变回偶数。*/
		write_seqcount_end(&jiffies_seq);
		/* 释放自旋锁。*/
		raw_spin_unlock(&jiffies_lock);
		/* 调用update_wall_time,根据流逝的tick,更新系统的墙上时间。*/
		update_wall_time();
	}
	/*
	 * 以下是每个CPU在每次tick时都必须执行的本地操作。
	 */
	/*
	 * 调用update_process_times,更新当前CPU上正在运行的任务的时间统计。
	 * user_mode(get_irq_regs())会判断中断发生时CPU是处于用户态还是内核态,
	 * 以便将时间正确地记在utime或stime上。
	 * 此函数还会减少任务的时间片。
	 */
	update_process_times(user_mode(get_irq_regs()));
	/*
	 * 调用profile_tick,如果内核开启了性能剖析,此函数会进行采样。
	 */
	// profile_tick(CPU_PROFILING);
}
        tick_handle_periodic 处理周期性时钟事件
            
            
              c
              
              
            
          
          /*
 * Event handler for periodic ticks
 */
void tick_handle_periodic(struct clock_event_device *dev)
{
    int cpu = smp_processor_id(); // 获取当前 CPU ID
    ktime_t next = dev->next_event; // 获取下一个时钟事件的时间
    tick_periodic(cpu); // 处理当前时钟中断
    // 检查是否启用了 TICK_ONESHOT 配置并且事件处理函数是否改变
    if (IS_ENABLED(CONFIG_TICK_ONESHOT) && dev->event_handler != tick_handle_periodic)
        return;
    // 检查时钟事件设备是否处于单次模式
    if (!clockevent_state_oneshot(dev))
        return;
    for (;;) {
        // 设置下一个周期事件
        next = ktime_add_ns(next, TICK_NSEC);
        // 尝试编程时钟事件设备
        if (!clockevents_program_event(dev, next, false))
            return;
        // 检查时间保持有效性,确保使用真实的硬件时钟源
        if (timekeeping_valid_for_hres())
            tick_periodic(cpu);
    }
}
        kernel/time/tick-oneshot.c
tick_oneshot_mode_active - 检查系统是否处于 OneShot 模式
            
            
              c
              
              
            
          
          /**
* tick_oneshot_mode_active - 检查系统是否处于 OneShot 模式
 *
 * 启用 nohz 或 highres 时返回 1。否则为 0。
 */
int tick_oneshot_mode_active(void)
{
	unsigned long flags;
	int ret;
	local_irq_save(flags);
	ret = __this_cpu_read(tick_cpu_device.mode) == TICKDEV_MODE_ONESHOT;
	local_irq_restore(flags);
	return ret;
}
        tick_setup_oneshot 将事件设备设置为 oneshot 模式(HRES 或 NOHZ)
            
            
              c
              
              
            
          
          /**
 * tick_setup_oneshot - 将事件设备设置为 oneshot 模式(HRES 或 NOHZ)
 */
void tick_setup_oneshot(struct clock_event_device *newdev,
			void (*handler)(struct clock_event_device *),
			ktime_t next_event)
{
	newdev->event_handler = handler;
	clockevents_switch_state(newdev, CLOCK_EVT_STATE_ONESHOT);
	clockevents_program_event(newdev, next_event, true);
}
        tick_program_event
            
            
              c
              
              
            
          
          /**
 * tick_program_event - 为下一个事件编程 CPU 本地计时器设备
 */
int tick_program_event(ktime_t expires, int force)
{
	struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev);
	if (unlikely(expires == KTIME_MAX)) {
		/*
		 * 我们不再需要 clock event 设备,停止它。
		 */
		clockevents_switch_state(dev, CLOCK_EVT_STATE_ONESHOT_STOPPED);
		dev->next_event = KTIME_MAX;
		return 0;
	}
	if (unlikely(clockevent_state_oneshot_stopped(dev))) {
		/*
		 * 我们再次需要 clock 事件,在使用它之前在 ONESHOT 模式下配置它。
		 */
		clockevents_switch_state(dev, CLOCK_EVT_STATE_ONESHOT);
	}
	return clockevents_program_event(dev, expires, force);
}
        kernel/time/tick-sched.c
tick_nohz_start_idle 开始空闲状态
            
            
              c
              
              
            
          
          static void tick_nohz_start_idle(struct tick_sched *ts)
{
	write_seqcount_begin(&ts->idle_sleeptime_seq);
	ts->idle_entrytime = ktime_get();
	tick_sched_flag_set(ts, TS_FLAG_IDLE_ACTIVE);
	write_seqcount_end(&ts->idle_sleeptime_seq);
	// sched_clock_idle_sleep_event();
}
        tick_nohz_idle_enter 准备在当前 CPU 上进入空闲状态
            
            
              c
              
              
            
          
          /**
 * tick_nohz_idle_enter - 准备在当前 CPU 上进入空闲状态
 *
 * 当我们开始空闲循环时调用。
 */
void tick_nohz_idle_enter(void)
{
	struct tick_sched *ts;
	lockdep_assert_irqs_enabled();
	local_irq_disable();
	ts = this_cpu_ptr(&tick_cpu_sched);
	WARN_ON_ONCE(ts->timer_expires_base);
	tick_sched_flag_set(ts, TS_FLAG_INIDLE);
	tick_nohz_start_idle(ts);
	local_irq_enable();
}
        tick_nohz_stop_idle 停止空闲状态
            
            
              c
              
              
            
          
          static void tick_nohz_stop_idle(struct tick_sched *ts, ktime_t now)
{
	ktime_t delta;
	if (WARN_ON_ONCE(!tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE)))
		return;
	delta = ktime_sub(now, ts->idle_entrytime);
	write_seqcount_begin(&ts->idle_sleeptime_seq);
	if (nr_iowait_cpu(smp_processor_id()) > 0)
		ts->iowait_sleeptime = ktime_add(ts->iowait_sleeptime, delta);
	else
		ts->idle_sleeptime = ktime_add(ts->idle_sleeptime, delta);
	ts->idle_entrytime = now;
	tick_sched_flag_clear(ts, TS_FLAG_IDLE_ACTIVE);
	write_seqcount_end(&ts->idle_sleeptime_seq);
	// sched_clock_idle_wakeup_event();
}
        tick_nohz_idle_exit 在空闲任务退出时更新刻度
            
            
              c
              
              
            
          
          /**
 * tick_nohz_idle_exit - 在空闲任务退出时更新刻度
 *
 * 当空闲任务退出时,根据
 * 以下情况:
 *
 * 1) 如果 CPU 未处于 nohz_full 模式(大多数情况下),则
 * 重新启动 Tick。
 *
 * 2) 如果 CPU 处于 nohz_full 模式(极端情况):
 * 2.1) 如果 tick 可以保持停止(无 tick 依赖性)
 * 然后重新评估下一个即时报价并尝试保持停止状态
 * 尽可能长。
 * 2.2) 如果 tick 有依赖关系,请重新启动 tick。
 *
 */
void tick_nohz_idle_exit(void)
{
	struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
	bool idle_active, tick_stopped;
	ktime_t now;
	local_irq_disable();
	WARN_ON_ONCE(!tick_sched_flag_test(ts, TS_FLAG_INIDLE));
	WARN_ON_ONCE(ts->timer_expires_base);
	tick_sched_flag_clear(ts, TS_FLAG_INIDLE);
	idle_active = tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE);
	tick_stopped = tick_sched_flag_test(ts, TS_FLAG_STOPPED);
	if (idle_active || tick_stopped)
		now = ktime_get();
	if (idle_active)
		tick_nohz_stop_idle(ts, now);
	if (tick_stopped)
		tick_nohz_idle_update_tick(ts, now);
	local_irq_enable();
}
        can_stop_idle_tick 检查是否可以停止空闲刻度
            
            
              c
              
              
            
          
          static bool can_stop_idle_tick(int cpu, struct tick_sched *ts)
{
	WARN_ON_ONCE(cpu_is_offline(cpu));
	if (unlikely(!tick_sched_flag_test(ts, TS_FLAG_NOHZ)))
		return false;
	if (need_resched())
		return false;
	if (unlikely(report_idle_softirq()))
		return false;
	if (tick_nohz_full_enabled()) {
		int tick_cpu = READ_ONCE(tick_do_timer_cpu);
		/*
		 * Keep the tick alive to guarantee timekeeping progression
		 * if there are full dynticks CPUs around
		 */
		if (tick_cpu == cpu)
			return false;
		/* Should not happen for nohz-full */
		if (WARN_ON_ONCE(tick_cpu == TICK_DO_TIMER_NONE))
			return false;
	}
	return true;
}
        tick_nohz_idle_stop_tick 停止空闲任务的空闲刻度
            
            
              c
              
              
            
          
          /**
 * tick_nohz_idle_stop_tick - 停止空闲任务的空闲刻度
 *
 * 当下一个事件距离未来超过一个即时报价时,停止空闲即时报价
 */
void tick_nohz_idle_stop_tick(void)
{
	struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
	int cpu = smp_processor_id();
	ktime_t expires;
	/*
	 * 如果 tick_nohz_get_sleep_length() 运行 tick_nohz_next_event(),则
	 * Tick 计时器到期时间已知。
	 */
	if (ts->timer_expires_base)
		expires = ts->timer_expires;
	else if (can_stop_idle_tick(cpu, ts))
		expires = tick_nohz_next_event(ts, cpu);
	else
		return;
	ts->idle_calls++;
	if (expires > 0LL) {
		int was_stopped = tick_sched_flag_test(ts, TS_FLAG_STOPPED);
		tick_nohz_stop_tick(ts, cpu);
		ts->idle_sleeps++;
		ts->idle_expires = expires;
		if (!was_stopped && tick_sched_flag_test(ts, TS_FLAG_STOPPED)) {
			ts->idle_jiffies = ts->last_jiffies;
			nohz_balance_enter_idle(cpu);
		}
	} else {
		tick_nohz_retain_tick(ts);
	}
}
        tick_check_oneshot_change 检查是否需要重新编程单次刻度
- tick_check_oneshot_change是一个在内核中被周期性调用的函数(如您提供的hrtimer_run_queues中),用于检查系统条件是否已经满足,可以从传统的周期性时钟节拍(periodic tick)模式,切换到更先进的、基于单次触发(one-shot)的模式。
它的核心作用是:在一个安全的检查点,判断是否可以进行时钟模式的"升级",如果可以,则返回一个信号或直接触发切换。 
            
            
              c
              
              
            
          
          /*
 * 检查是否发生了可以使one-shot模式成为可能的改变。
 *
 * 由hrtimer软中断(由timer软中断驱动)周期性地调用。
 * 'allow_nohz'参数表示我们可以切换到低分辨率的NOHZ模式,因为高分辨率
 * 定时器被禁用了(无论是编译时还是运行时)。
 * 在中断禁用的情况下调用。
 *
 * 返回值:如果可以切换到高分辨率模式,返回1;否则返回0。
 */
int tick_check_oneshot_change(int allow_nohz)
{
	/* ts: 指向当前CPU的tick_sched结构体,包含了所有与tick调度相关的状态。*/
	struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
	/*
	 * 使用test_and_clear_bit原子地检查并清除check_clocks标志的第0位。
	 * 如果该位原本就是0,说明没有时钟源变更事件发生,无需检查,直接返回0。
	 * 这是一个高效的快速路径。
	 */
	if (!test_and_clear_bit(0, &ts->check_clocks))
		return 0;
	/*
	 * 使用tick_sched_flag_test检查当前CPU是否已经处于NOHZ模式。
	 * 如果是,则无需再次切换,返回0。
	 */
	if (tick_sched_flag_test(ts, TS_FLAG_NOHZ))
		return 0;
	/*
	 * 检查当前的时间维持(timekeeping)系统是否对高分辨率(hres)有效,
	 * 以及当前注册的时钟事件设备是否支持one-shot模式。
	 */
	if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
		/* 如果任一条件不满足,则无法切换,返回0。*/
		return 0;
	/*
	 * 如果调用者不允许直接切换到NOHZ模式。
	 * (例如,hrtimer_run_queues调用时,因为高精度定时器还未禁用)
	 */
	if (!allow_nohz)
		/* 返回1,通知调用者:"条件已满足,你可以进行切换到高分辨率模式的操作了"。*/
		return 1;
	/*
	 * 如果调用者允许,则直接在这里调用tick_nohz_switch_to_nohz(),
	 * 将当前CPU的时钟模式切换为NOHZ(tickless)。
	 */
	tick_nohz_switch_to_nohz();
	/* 在这种情况下,切换已由本函数完成,返回0。*/
	return 0;
}