[TOC]
kernel/time/clockevents.c clockevents_switch_state - 设置时钟事件设备的运行状态 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 64 65 66 67 68 69 70 71 72 73 74 75 static int __clockevents_switch_state(struct clock_event_device *dev, enum clock_event_state state) { if (dev->features & CLOCK_EVT_FEAT_DUMMY) return 0 ; switch (state) { case CLOCK_EVT_STATE_DETACHED: case CLOCK_EVT_STATE_SHUTDOWN: if (dev->set_state_shutdown) return dev->set_state_shutdown(dev); return 0 ; case CLOCK_EVT_STATE_PERIODIC: if (!(dev->features & CLOCK_EVT_FEAT_PERIODIC)) return -ENOSYS; if (dev->set_state_periodic) return dev->set_state_periodic(dev); return 0 ; case CLOCK_EVT_STATE_ONESHOT: if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT)) return -ENOSYS; if (dev->set_state_oneshot) return dev->set_state_oneshot(dev); return 0 ; case CLOCK_EVT_STATE_ONESHOT_STOPPED: if (WARN_ONCE(!clockevent_state_oneshot(dev), "Current state: %d\n" , clockevent_get_state(dev))) return -EINVAL; if (dev->set_state_oneshot_stopped) return dev->set_state_oneshot_stopped(dev); else return -ENOSYS; default : return -ENOSYS; } } void clockevents_switch_state (struct clock_event_device *dev, enum clock_event_state state) { if (clockevent_get_state(dev) != state) { if (__clockevents_switch_state(dev, state)) return ; clockevent_set_state(dev, state); if (clockevent_state_oneshot(dev)) { if (WARN_ON(!dev->mult)) dev->mult = 1 ; } } }
clockevents_config Clockevents 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static void clockevents_config (struct clock_event_device *dev, u32 freq) { u64 sec; if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT)) return ; sec = dev->max_delta_ticks; do_div(sec, freq); if (!sec) sec = 1 ; else if (sec > 600 && dev->max_delta_ticks > UINT_MAX) sec = 600 ; clockevents_calc_mult_shift(dev, freq, sec); dev->min_delta_ns = cev_delta2ns(dev->min_delta_ticks, dev, false ); dev->max_delta_ns = cev_delta2ns(dev->max_delta_ticks, dev, true ); }
clockevents_register_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 void clockevents_register_device (struct clock_event_device *dev) { unsigned long flags; clockevent_set_state(dev, CLOCK_EVT_STATE_DETACHED); if (!dev->cpumask) { WARN_ON(num_possible_cpus() > 1 ); dev->cpumask = cpumask_of(smp_processor_id()); } if (dev->cpumask == cpu_all_mask) { WARN(1 , "%s cpumask == cpu_all_mask, using cpu_possible_mask instead\n" , dev->name); dev->cpumask = cpu_possible_mask; } raw_spin_lock_irqsave(&clockevents_lock, flags); list_add(&dev->list , &clockevent_devices); tick_check_new_device(dev); clockevents_notify_released(); raw_spin_unlock_irqrestore(&clockevents_lock, flags); } EXPORT_SYMBOL_GPL(clockevents_register_device);
clockevents_init 配置和注册时钟事件设备 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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); } EXPORT_SYMBOL_GPL(clockevents_config_and_register);
clockevents_shutdown 关闭设备并清除next_event 1 2 3 4 5 6 7 8 9 void clockevents_shutdown (struct clock_event_device *dev) { clockevents_switch_state(dev, CLOCK_EVT_STATE_SHUTDOWN); dev->next_event = KTIME_MAX; }
clockevents_exchange_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 void clockevents_exchange_device (struct clock_event_device *old, struct clock_event_device *new) { if (old) { module_put(old->owner); clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED); list_move(&old->list , &clockevents_released); } if (new) { BUG_ON(!clockevent_state_detached(new)); clockevents_shutdown(new); } }
clockevents_program_event 重新编程 clock event 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 int clockevents_program_event (struct clock_event_device *dev, ktime_t expires, bool force) { unsigned long long clc; int64_t delta; int rc; if (WARN_ON_ONCE(expires < 0 )) return -ETIME; dev->next_event = expires; if (clockevent_state_shutdown(dev)) return 0 ; WARN_ONCE(!clockevent_state_oneshot(dev), "Current state: %d\n" , clockevent_get_state(dev)); if (dev->features & CLOCK_EVT_FEAT_KTIME) return dev->set_next_ktime(expires, dev); delta = ktime_to_ns(ktime_sub(expires, ktime_get())); if (delta <= 0 ) return force ? clockevents_program_min_delta(dev) : -ETIME; delta = min(delta, (int64_t ) dev->max_delta_ns); delta = max(delta, (int64_t ) dev->min_delta_ns); clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; rc = dev->set_next_event((unsigned long ) clc, dev); return (rc && force) ? clockevents_program_min_delta(dev) : rc; }
clockevents_program_event 重新编程 clock event device
它的核心作用是:接收一个未来的、绝对的到期时间expires,计算出从“现在”到这个未来时间点的时间差,并将这个时间差转换为底层硬件定时器可以理解的“周期计数值(cycle/count)”,然后调用硬件驱动提供的回调函数,将这个计数值编程到硬件中,以使硬件在精确的时刻触发一次中断。
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 64 int clockevents_program_event (struct clock_event_device *dev, ktime_t expires, bool force) { unsigned long long clc; int64_t delta; int rc; if (WARN_ON_ONCE(expires < 0 )) return -ETIME; dev->next_event = expires; if (clockevent_state_shutdown(dev)) return 0 ; WARN_ONCE(!clockevent_state_oneshot(dev), "Current state: %d\n" , clockevent_get_state(dev)); if (dev->features & CLOCK_EVT_FEAT_KTIME) return dev->set_next_ktime(expires, dev); delta = ktime_to_ns(ktime_sub(expires, ktime_get())); if (delta <= 0 ) return force ? clockevents_program_min_delta(dev) : -ETIME; delta = min(delta, (int64_t ) dev->max_delta_ns); delta = max(delta, (int64_t ) dev->min_delta_ns); clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; rc = dev->set_next_event((unsigned long ) clc, dev); return (rc && force) ? clockevents_program_min_delta(dev) : rc; }
Clockevents 跨CPU解绑与替换 (__clockevents_try_unbind, clockevents_unbind) 本代码片段是 Linux 内核 clockevents
(时钟事件)子系统中实现硬件定时器与 CPU 解绑的核心逻辑,特别针对多核(SMP)环境设计。其主要功能是通过跨 CPU 调用(IPI - Inter-Processor Interrupt),安全地在指定的 CPU 上执行解绑操作。clockevents_unbind
负责发起请求,__clockevents_unbind
是在目标 CPU 上执行的函数,而 __clockevents_try_unbind
则是实际执行解绑或替换逻辑的内部核心。
实现原理分析 此代码段的实现原理是 SMP 环境下安全修改 per-CPU 硬件资源的典范,其核心技术是 远程过程调用 和 状态驱动的原子替换 。
跨CPU执行的必要性 (smp_call_function_single
) : clockevents_unbind
的核心是调用 smp_call_function_single
。这是因为用于内核节拍(tick)的 clock_event_device
通常是 per-CPU 的硬件资源(例如,每个核心独立的 Local APIC 定时器或 ARM architected timer)。对这些硬件的任何操作,如停止、重新编程或禁用中断,都必须 在它们所属的那个 CPU 核心上执行,直接操作其他核心的定时器硬件是不可能或不安全的。smp_call_function_single
通过向目标 cpu
发送一个 IPI,强制该 CPU 中断当前工作,转而执行指定的函数(__clockevents_unbind
),从而实现了在正确的“地点”执行关键代码。
两阶段解绑与替换 (__clockevents_try_unbind
) : 此函数体现了对不同设备状态的精确处理:
快速路径 : 如果设备已经是 detached
(分离)状态,意味着它当前未被使用。此时可以直接从全局链表中移除(list_del_init
),操作非常迅速。
慢速路径 (替换) : 如果设备正被当前 CPU 用于内核节拍 (ced == per_cpu(tick_cpu_device, cpu).evtdev
),直接移除是不行的,会导致系统失去心跳。此时,函数返回 -EAGAIN
,通知上层调用者(__clockevents_unbind
)必须执行一个更复杂的操作——clockevents_replace
。clockevents_replace
会原子地在系统中寻找一个合适的替代定时器,将内核节拍切换到新定时器上,然后才安全地将旧设备置于 detached
状态。
繁忙状态 : 如果设备正在被使用,但不是用于当前 CPU 的内核节拍(例如,它可能是一个共享的定时器,或被用于 hrtimers
),则返回 -EBUSY
,表示当前无法解绑。
特定场景分析:单核、无MMU的STM32H750平台 硬件交互 在 STM32H750 平台上,一个 clock_event_device
实例通常代表 ARMv7-M 架构的 SysTick
定时器,或者是 STM32 的某个通用定时器(TIM)。当 clockevents_unbind
被调用试图解绑 SysTick
时:
__clockevents_unbind
会在 STM32H750 的唯一核心上被执行。
__clockevents_try_unbind
会发现 SysTick
正被用作内核节拍,返回 -EAGAIN
。
clockevents_replace
会被调用。它会尝试寻找系统中的其他可用定时器(例如,一个配置为时钟事件模式的 TIM)。如果找到,它会停止并禁用 SysTick
的中断,然后启动并使能新 TIM 的中断来接管内核节拍。如果找不到替代者,操作将失败。
单核环境影响 这组函数虽然为 SMP 设计,但在单核系统上依然能够正确工作,只是其行为被极大地简化了:
smp_call_function_single
的退化 : 在单核配置下,smp_call_function_single
不会发送任何 IPI。它会退化为一个简单的本地函数调用。内核会通过禁用本地中断来模拟一个“原子”的执行环境,然后直接调用 __clockevents_unbind
函数。所有复杂的跨核同步和等待逻辑都被绕过。
CPU ID : smp_processor_id()
将始终返回 0。
因此,在 STM32H750 上,这个流程变成了一个纯粹的本地操作:clockevents_unbind
通过一个简单的函数调用(带有中断屏蔽)来执行 __clockevents_unbind
,后者再根据设备状态决定是直接移除还是进行替换。
无MMU影响 本代码片段的功能完全位于内核核心调度和时间管理层,其所有操作——包括链表操作、per-CPU 变量访问、函数调用——都与内存管理单元(MMU)无关。它在内核的平坦地址空间中工作,不依赖任何虚拟内存机制,因此在无 MMU 的 STM32H750 系统上可以无差别地正确执行。
代码分析 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 64 static int __clockevents_try_unbind(struct clock_event_device *ced, int cpu){ if (clockevent_state_detached(ced)) { list_del_init(&ced->list ); return 0 ; } return ced == per_cpu(tick_cpu_device, cpu).evtdev ? -EAGAIN : -EBUSY; } static void __clockevents_unbind(void *arg){ struct ce_unbind *cu = arg; int res; raw_spin_lock(&clockevents_lock); res = __clockevents_try_unbind(cu->ce, smp_processor_id()); if (res == -EAGAIN) res = clockevents_replace(cu->ce); cu->res = res; raw_spin_unlock(&clockevents_lock); } static int clockevents_unbind (struct clock_event_device *ced, int cpu) { struct ce_unbind cu = { .ce = ced, .res = -ENODEV }; smp_call_function_single(cpu, __clockevents_unbind, &cu, 1 ); return cu.res; }
Clockevents Sysfs 接口初始化 (clockevents_init_sysfs, tick_init_sysfs) 本代码片段的核心功能是为 Linux 内核的 clockevents
(时钟事件)子系统创建一套 sysfs 接口。它通过标准的 Linux 设备模型,在 /sys/bus/clockevents/
目录下注册一个总线,并为系统中的每个 CPU(以及可能的广播通道)创建一个对应的逻辑设备。这些逻辑设备下又包含属性文件(如 current_device
),允许用户空间程序查看当前哪个硬件定时器正被用于该 CPU 的内核节拍(tick),并提供了在特定条件下手动解绑定时器的调试能力。
实现原理分析 此代码是 Linux 内核“一切皆文件”思想的典型体现,其实现原理基于对内核设备模型的深度集成。
设备模型抽象 : 代码并未直接创建文件,而是将 clockevents
子系统的内部组件抽象为设备模型的标准元素:
总线 (bus_type
) : clockevents_subsys
定义了一个名为 “clockevents” 的总线类型。这会在 sysfs 中创建 /sys/bus/clockevents/
目录,作为所有相关设备的容器。
设备 (device
) : 系统中的每个 CPU 核心都被视为一个独立的“节拍设备”,代码为此定义了一个 per-CPU
的 struct device
实例 (tick_percpu_dev
)。在初始化时,tick_init_sysfs
函数会遍历所有 CPU,为每一个注册一个设备实例,从而在 sysfs 中创建出如 /sys/bus/clockevents/devices/clockevent0
, /sys/bus/clockevents/devices/clockevent1
等目录。
属性 (device_attribute
) : DEVICE_ATTR_RO(current_device)
和 DEVICE_ATTR_WO(unbind_device)
这两个宏定义了设备的属性,它们分别对应 sysfs 中的一个文件。当用户读写这些文件时,内核会调用其对应的 _show
或 _store
函数(如 current_device_show
)。
两阶段解绑的同步技巧 (unbind_device_store
) : 解绑设备的操作展示了一种复杂的同步策略。它首先在持有轻量级的 raw_spin_lock_irq
的情况下,调用 __clockevents_try_unbind
尝试快速解绑。自旋锁下禁止睡眠,因此 __clockevents_try_unbind
如果遇到需要等待或可能导致睡眠的情况,会立即返回 -EAGAIN
。unbind_device_store
捕捉到这个返回值后,会释放自旋锁,并调用允许睡眠的 clockevents_unbind
函数来完成这个慢速路径的操作。在此期间,它持有 clockevents_mutex
互斥锁,以防止目标设备 ce
在此过程中被销毁。
代码分析 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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 #ifdef CONFIG_SYSFS static const struct bus_type clockevents_subsys = { .name = "clockevents" , .dev_name = "clockevent" , }; static DEFINE_PER_CPU (struct device, tick_percpu_dev) ;static struct tick_device *tick_get_tick_dev (struct device *dev) ;static ssize_t current_device_show (struct device *dev, struct device_attribute *attr, char *buf) { struct tick_device *td ; ssize_t count = 0 ; raw_spin_lock_irq(&clockevents_lock); td = tick_get_tick_dev(dev); if (td && td->evtdev) count = sysfs_emit(buf, "%s\n" , td->evtdev->name); raw_spin_unlock_irq(&clockevents_lock); return count; } static DEVICE_ATTR_RO (current_device) ;static ssize_t unbind_device_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { char name[CS_NAME_LEN]; ssize_t ret = sysfs_get_uname(buf, name, count); struct clock_event_device *ce = NULL , *iter; if (ret < 0 ) return ret; ret = -ENODEV; mutex_lock(&clockevents_mutex); raw_spin_lock_irq(&clockevents_lock); list_for_each_entry(iter, &clockevent_devices, list ) { if (!strcmp (iter->name, name)) { ret = __clockevents_try_unbind(iter, dev->id); ce = iter; break ; } } raw_spin_unlock_irq(&clockevents_lock); if (ret == -EAGAIN) ret = clockevents_unbind(ce, dev->id); mutex_unlock(&clockevents_mutex); return ret ? ret : count; } static DEVICE_ATTR_WO (unbind_device) ;#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST static struct device tick_bc_dev = { .init_name = "broadcast" , .id = 0 , .bus = &clockevents_subsys, }; static struct tick_device *tick_get_tick_dev (struct device *dev) { return dev == &tick_bc_dev ? tick_get_broadcast_device() : &per_cpu(tick_cpu_device, dev->id); } static __init int tick_broadcast_init_sysfs (void ) { int err = device_register(&tick_bc_dev); if (!err) err = device_create_file(&tick_bc_dev, &dev_attr_current_device); return err; } #else static struct tick_device *tick_get_tick_dev (struct device *dev) { return &per_cpu(tick_cpu_device, dev->id); } static inline int tick_broadcast_init_sysfs (void ) { return 0 ; }#endif static int __init tick_init_sysfs (void ) { int cpu; for_each_possible_cpu(cpu) { struct device *dev = &per_cpu(tick_percpu_dev, cpu); int err; dev->id = cpu; dev->bus = &clockevents_subsys; err = device_register(dev); if (!err) err = device_create_file(dev, &dev_attr_current_device); if (!err) err = device_create_file(dev, &dev_attr_unbind_device); if (err) return err; } return tick_broadcast_init_sysfs(); } static int __init clockevents_init_sysfs (void ) { int err = subsys_system_register(&clockevents_subsys, NULL ); if (!err) err = tick_init_sysfs(); return err; } device_initcall(clockevents_init_sysfs); #endif