[toc]
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()
,它就是滴答事件的核心处理函数 。
滴答事件处理 (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
。
它的主要优势体现在哪些方面?
提供基础节拍 :为所有依赖于周期性检查的传统内核子系统提供了驱动力。
实现抢占 :是调度器实现公平性的基础。
节能 :通过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 设置周期性处理程序 1 2 3 4 5 6 7 8 9 10 11 12 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。
1 2 int tick_do_timer_cpu __read_mostly = TICK_DO_TIMER_BOOT;
clockevents_program_min_delta 将时钟事件设备设置为最小延迟 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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 设置为周期时钟 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 void tick_setup_periodic (struct clock_event_device *dev, int broadcast) { tick_set_periodic_handler(dev, broadcast); if (!tick_device_is_functional(dev)) return ; 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; 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 ; next = ktime_add_ns(next, TICK_NSEC); } } }
检查新设备是否比 curdev 更合适 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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 ; if (newdev->irq >= 0 && !irq_can_set_affinity(newdev->irq)) return false ; 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) { if (!(newdev->features & CLOCK_EVT_FEAT_ONESHOT)) { if (curdev && (curdev->features & CLOCK_EVT_FEAT_ONESHOT)) return false ; if (tick_oneshot_mode_active()) return false ; } return !curdev || newdev->rating > curdev->rating || !cpumask_equal(curdev->cpumask, newdev->cpumask); } 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 设置时钟事件设备 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 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) { 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)) irq_set_affinity(newdev->irq, cpumask); 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 检查是否应使用新注册的设备 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 ; 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相关的周期性任务,如进程时间统计和性能剖析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 ktime_t tick_next_period;static void tick_periodic (int cpu) { if (READ_ONCE(tick_do_timer_cpu) == cpu) { raw_spin_lock(&jiffies_lock); write_seqcount_begin(&jiffies_seq); tick_next_period = ktime_add_ns(tick_next_period, TICK_NSEC); do_timer(1 ); write_seqcount_end(&jiffies_seq); raw_spin_unlock(&jiffies_lock); update_wall_time(); } update_process_times(user_mode(get_irq_regs())); }
tick_handle_periodic 处理周期性时钟事件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void tick_handle_periodic (struct clock_event_device *dev) { int cpu = smp_processor_id(); ktime_t next = dev->next_event; tick_periodic(cpu); 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 模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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) 1 2 3 4 5 6 7 8 9 10 11 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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)) { clockevents_switch_state(dev, CLOCK_EVT_STATE_ONESHOT_STOPPED); dev->next_event = KTIME_MAX; return 0 ; } if (unlikely(clockevent_state_oneshot_stopped(dev))) { clockevents_switch_state(dev, CLOCK_EVT_STATE_ONESHOT); } return clockevents_program_event(dev, expires, force); }
kernel/time/tick-sched.c tick_nohz_start_idle 开始空闲状态 1 2 3 4 5 6 7 8 9 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); }
tick_nohz_idle_enter 准备在当前 CPU 上进入空闲状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 停止空闲状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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); }
tick_nohz_idle_exit 在空闲任务退出时更新刻度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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 检查是否可以停止空闲刻度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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); if (tick_cpu == cpu) return false ; if (WARN_ON_ONCE(tick_cpu == TICK_DO_TIMER_NONE)) return false ; } return true ; }
tick_nohz_idle_stop_tick 停止空闲任务的空闲刻度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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; 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)的模式。 它的核心作用是:在一个安全的检查点,判断是否可以进行时钟模式的“升级”,如果可以,则返回一个信号或直接触发切换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 int tick_check_oneshot_change (int allow_nohz) { struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched); if (!test_and_clear_bit(0 , &ts->check_clocks)) return 0 ; if (tick_sched_flag_test(ts, TS_FLAG_NOHZ)) 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 ; }