title: clockevents
categories:
- linux
- kernel
- time
tags: - linux
- kernel
- time
abbrlink: df20a2f0
date: 2025-10-06 11:28:50
文章目录
- kernel/time/clockevents.c
- clockevents_switch_state - 设置时钟事件设备的运行状态
- clockevents_config Clockevents 配置
- clockevents_register_device 注册一个时钟事件设备
- clockevents_init 配置和注册时钟事件设备
- clockevents_shutdown 关闭设备并清除next_event
- clockevents_exchange_device 释放和请求时钟设备
- clockevents_program_event 重新编程 clock event device
- clockevents_program_event 重新编程 clock event device
- Clockevents 跨CPU解绑与替换 (__clockevents_try_unbind, clockevents_unbind)
- Clockevents Sysfs 接口初始化 (clockevents_init_sysfs, tick_init_sysfs)
https://github.com/wdfk-prog/linux-study
kernel/time/clockevents.c
clockevents_switch_state - 设置时钟事件设备的运行状态
static int __clockevents_switch_state(struct clock_event_device *dev,
enum clock_event_state state)
{
if (dev->features & CLOCK_EVT_FEAT_DUMMY)
return 0;
/* Transition with new state-specific callbacks */
switch (state) {
case CLOCK_EVT_STATE_DETACHED:
/* The clockevent device is getting replaced. Shut it down. */
case CLOCK_EVT_STATE_SHUTDOWN:
if (dev->set_state_shutdown)
return dev->set_state_shutdown(dev);
return 0;
case CLOCK_EVT_STATE_PERIODIC:
/* Core internal bug */
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:
/* Core internal bug */
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:
/* Core internal bug */
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;
}
}
/**
* clockevents_switch_state - 设置时钟事件设备的运行状态
* @dev:设备修改
* @state:新状态
*
* 必须在禁用中断的情况下调用!
*/
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);
/*
* A nsec2cyc multiplicator of 0 is invalid and we'd crash
* on it, so fix it up and emit a warning:
*/
if (clockevent_state_oneshot(dev)) {
if (WARN_ON(!dev->mult))
dev->mult = 1;
}
}
}
clockevents_config Clockevents 配置
static void clockevents_config(struct clock_event_device *dev, u32 freq)
{
u64 sec;
if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT))
return;
/*
* 计算我们可以睡觉的最大秒数。
* 对于可以编程超过 32 位时钟周期的硬件,限制为 10 分钟,
* 因此我们仍然可以获得合理的转换值。
*/
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 注册一个时钟事件设备
/**
* clockevents_register_device - 注册一个时钟事件设备
* @dev:要注册的设备
*/
void clockevents_register_device(struct clock_event_device *dev)
{
unsigned long flags;
/* 将状态初始化为 DETACHED */
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 配置和注册时钟事件设备
/**
* clockevents_config_and_register - 配置和注册时钟事件设备
* @dev:要注册的设备
* @freq:时钟频率
* @min_delta:在 oneshot 模式下编程的最小时钟滴答声
* @max_delta:在 oneshot 模式下编程的最大时钟滴答声
*
* min/max_delta 对于不支持 oneshot 模式的设备,可以是 0。
*/
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
/**
* clockevents_shutdown - 关闭设备并清除next_event
* @dev:设备关机
*/
void clockevents_shutdown(struct clock_event_device *dev)
{
clockevents_switch_state(dev, CLOCK_EVT_STATE_SHUTDOWN);
dev->next_event = KTIME_MAX;
}
clockevents_exchange_device 释放和请求时钟设备
/**
* clockevents_exchange_device - 释放和请求时钟设备
* @old:要释放的设备(可以是 NULL)
* @new:要请求的设备(可以为 NULL)
*
* 从各种 tick 函数中调用,同时保持 clockevents_lock 并禁用中断。
*/
void clockevents_exchange_device(struct clock_event_device *old,
struct clock_event_device *new)
{
/*
* 调用方释放一个 clock event device。我们将其排入 released 列表中,并在稍后执行通知添加。
*/
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
/**
* clockevents_program_event - 重新编程 clock event device。
* @dev:设备到程序
* @expires:绝对到期时间(单调时钟)
* @force:如果过期时程序最小延迟无法设置
*
* 成功时返回 0,如果事件过去,则返回 -ETIME。
*/
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;
/* 我们这里必须处于 ONESHOT 状态*/
WARN_ONCE(!clockevent_state_oneshot(dev), "Current state: %d\n",
clockevent_get_state(dev));
/* 可以处理 ktime 的 clockevent 设备的快捷方式。*/
if (dev->features & CLOCK_EVT_FEAT_KTIME)
return dev->set_next_ktime(expires, dev);
/* 计算当前时间与到期时间之间的时间增量 delta(以纳秒为单位 */
delta = ktime_to_ns(ktime_sub(expires, ktime_get()));
if (delta <= 0)
/* 如果增量小于等于零,表示事件已经过期。
* 如果 force 标志为真,则调用 clockevents_program_min_delta
* 设置最小延迟; */
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;
/* 调用设备的 set_next_event 方法设置下一个事件 */
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)”,然后调用硬件驱动提供的回调函数,将这个计数值编程到硬件中,以使硬件在精确的时刻触发一次中断。
/**
* clockevents_program_event - 重新编程时钟事件设备。
* @dev: 需要被编程的设备。
* @expires: 绝对的到期时间(基于monotonic时钟)。
* @force: 如果expires无法被设置,是否强制编程一个最小延迟。
*
* 返回值: 成功时返回0,当事件已在过去时返回-ETIME。
*/
int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,
bool force)
{
/* clc: "cycles", 64位无符号整型,用于存储转换后的硬件周期计数值。*/
unsigned long long clc;
/* delta: 64位有符号整型,用于存储从现在到expires的纳秒级时间差。*/
int64_t delta;
/* rc: 用于存储驱动回调函数的返回值。*/
int rc;
/* 这是一个健壮性检查。到期时间不应为负数。如果为负,则触发一次性警告并返回错误。*/
if (WARN_ON_ONCE(expires < 0))
return -ETIME;
/* 将目标到期时间保存到设备结构体的next_event字段中。*/
dev->next_event = expires;
/* 如果设备当前处于“关闭”状态,则无需编程,直接返回成功。*/
if (clockevent_state_shutdown(dev))
return 0;
/* 我们在这里必须处于ONESHOT状态。这是一个断言,如果不是,则打印警告。*/
WARN_ONCE(!clockevent_state_oneshot(dev), "Current state: %d\n",
clockevent_get_state(dev));
/* 对那些能直接处理ktime的硬件设备,提供一条快速路径。*/
if (dev->features & CLOCK_EVT_FEAT_KTIME)
/* 直接调用驱动提供的set_next_ktime回调,将绝对到期时间传递给它。*/
return dev->set_next_ktime(expires, dev);
/*
* --- 常规路径:手动进行时间转换 ---
*/
/* 计算从现在(ktime_get())到目标时间expires之间的时间差(单位:纳秒)。*/
delta = ktime_to_ns(ktime_sub(expires, ktime_get()));
/* 如果delta小于等于0,说明expires已经是一个过去或现在的时刻。*/
if (delta <= 0)
/* 如果force为true,则尝试用最小延迟编程一次中断;否则返回-ETIME错误。*/
return force ? clockevents_program_min_delta(dev) : -ETIME;
/* 将delta限制在硬件支持的最大和最小延迟范围内。*/
delta = min(delta, (int64_t) dev->max_delta_ns);
delta = max(delta, (int64_t) dev->min_delta_ns);
/*
* 核心转换:使用预先计算好的乘数(mult)和移位数(shift),
* 将纳秒级的时间差delta,转换为硬件定时器需要的周期计数值clc。
*/
clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
/* 调用驱动提供的set_next_event回调,将计算出的周期数clc编程到硬件中。*/
rc = dev->set_next_event((unsigned long) clc, dev);
/* 如果驱动回调返回错误,并且force为true,则再次尝试以最小延迟编程。*/
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 系统上可以无差别地正确执行。
代码分析
// __clockevents_try_unbind: 在持有锁的情况下,尝试解绑一个时钟事件设备。
// @ced: 指向待解绑的时钟事件设备(clock_event_device)的指针。
// @cpu: 目标CPU的ID。
// 返回值: 0 表示成功解绑;-EAGAIN 表示需要执行替换操作;-EBUSY 表示设备正忙。
/*
* 该函数必须在持有 clockevents_mutex 和 clockevents_lock 的情况下被调用。
*/
static int __clockevents_try_unbind(struct clock_event_device *ced, int cpu)
{
// 快速路径:检查设备是否处于“分离”(detached)状态。
if (clockevent_state_detached(ced)) {
// 如果设备未使用,直接从全局设备链表中移除它。
list_del_init(&ced->list);
return 0; // 返回成功。
}
// 检查该设备是否正是当前目标CPU正在使用的内核节拍(tick)设备。
// 如果是,则不能直接移除,必须先找到替代者。返回 -EAGAIN 表示“请重试”(并执行替换)。
// 如果不是,则意味着设备可能被用于其他目的(如高精度定时器),正处于繁忙状态,返回 -EBUSY。
return ced == per_cpu(tick_cpu_device, cpu).evtdev ? -EAGAIN : -EBUSY;
}
// __clockevents_unbind: 在目标CPU上实际执行解绑操作的函数。
// @arg: 一个void指针,指向包含解绑信息的 ce_unbind 结构体。
/*
* 这是一个通过SMP函数调用(IPI)在指定CPU上执行的函数。
*/
static void __clockevents_unbind(void *arg)
{
struct ce_unbind *cu = arg; // 将void指针转换为 ce_unbind 结构体指针。
int res;
// 获取本地CPU的 clockevents_lock 锁(这是一个原始自旋锁)。
raw_spin_lock(&clockevents_lock);
// 尝试解绑。
res = __clockevents_try_unbind(cu->ce, smp_processor_id());
// 如果 __clockevents_try_unbind 返回 -EAGAIN,说明需要执行替换操作。
if (res == -EAGAIN)
// clockevents_replace 会寻找一个新的定时器来接管内核节拍,然后才将旧设备置为分离状态。
res = clockevents_replace(cu->ce);
// 将操作结果存入传入的结构体中,以便发起者可以获取。
cu->res = res;
// 释放锁。
raw_spin_unlock(&clockevents_lock);
}
// clockevents_unbind: 发起一个对指定CPU上时钟事件设备的解绑请求。
// @ced: 指向待解绑的时钟事件设备的指针。
// @cpu: 目标CPU的ID。
// 返回值: 解绑操作的最终结果。
/*
* 该函数在调用时必须持有 clockevents_mutex 互斥锁。
*/
static int clockevents_unbind(struct clock_event_device *ced, int cpu)
{
// 定义一个 ce_unbind 结构体在栈上,用于传递参数和接收返回值。
struct ce_unbind cu = { .ce = ced, .res = -ENODEV };
// 发起一个SMP调用:请求在目标CPU(cpu)上执行 __clockevents_unbind 函数,
// 参数为 cu 的地址,最后的 '1' 表示这是一个同步调用,即本函数会等待目标CPU执行完毕。
smp_call_function_single(cpu, __clockevents_unbind, &cu, 1);
// 返回从目标CPU上获取的操作结果。
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在此过程中被销毁。
代码分析
#ifdef CONFIG_SYSFS // 仅在内核配置了SYSFS文件系统支持时,以下代码才会被编译。
// 定义一个名为 "clockevents" 的总线(bus)类型。
// 这将在 /sys/bus/ 目录下创建一个名为 "clockevents" 的子目录。
static const struct bus_type clockevents_subsys = {
.name = "clockevents",
.dev_name = "clockevent", // 设备的基础名称
};
// 为每个CPU定义一个独立的 device 结构体实例。
static DEFINE_PER_CPU(struct device, tick_percpu_dev);
// 声明一个辅助函数,用于从通用的 device 结构体指针找到对应的 tick_device 结构体。
static struct tick_device *tick_get_tick_dev(struct device *dev);
// "current_device" sysfs 属性文件的 "show" (读) 操作实现函数。
// @dev: 指向此属性所属的设备结构体。
// @attr: 指向设备属性结构体。
// @buf: 用于存储输出字符串的缓冲区。
// 返回值: 写入缓冲区的字节数。
static ssize_t current_device_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tick_device *td;
ssize_t count = 0;
// 获取保护 clockevents 子系统的自旋锁,并禁用中断。
raw_spin_lock_irq(&clockevents_lock);
// 根据 dev 指针找到对应的 tick_device。
td = tick_get_tick_dev(dev);
// 如果 tick_device 存在并且已经关联了一个时钟事件设备(evtdev)。
if (td && td->evtdev)
// 使用 sysfs_emit 格式化输出当前时钟事件设备的名称到缓冲区。
count = sysfs_emit(buf, "%s\n", td->evtdev->name);
// 释放自旋锁,并恢复中断。
raw_spin_unlock_irq(&clockevents_lock);
return count;
}
// 使用宏定义一个只读的 "current_device" 设备属性。
static DEVICE_ATTR_RO(current_device);
// "unbind_device" sysfs 属性文件的 "store" (写) 操作实现函数。
// @dev: 指向此属性所属的设备结构体。
// @attr: 指向设备属性结构体。
// @buf: 指向用户空间写入的数据。
// @count: 写入数据的字节数。
// 返回值: 成功则为写入的字节数,失败则为负数错误码。
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); // 从buf中解析出设备名。
struct clock_event_device *ce = NULL, *iter;
if (ret < 0)
return ret;
ret = -ENODEV; // 默认返回“无此设备”错误。
// 获取互斥锁,用于保护可能引起睡眠的解绑操作。
mutex_lock(&clockevents_mutex);
// 获取自旋锁,用于保护对 clockevent_devices 链表的遍历。
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);
// 此时仍然持有 clockevents_mutex 互斥锁,所以 ce 指针是安全的。
// 如果快速路径解绑失败并返回 -EAGAIN,说明需要执行可能睡眠的慢速路径。
if (ret == -EAGAIN)
ret = clockevents_unbind(ce, dev->id);
// 释放互斥锁。
mutex_unlock(&clockevents_mutex);
// 如果操作成功(ret=0),则返回用户写入的字节数,否则返回错误码。
return ret ? ret : count;
}
// 使用宏定义一个只写的 "unbind_device" 设备属性。
static DEVICE_ATTR_WO(unbind_device);
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST // 如果配置了广播时钟事件。
// 为广播时钟事件定义一个全局的 device 结构体。
static struct device tick_bc_dev = {
.init_name = "broadcast",
.id = 0, // ID 为0,但通过与 per-cpu 设备的指针比较来区分。
.bus = &clockevents_subsys, // 关联到 clockevents 总线。
};
// 辅助函数,根据 dev 指针找到对应的 tick_device。
static struct tick_device *tick_get_tick_dev(struct device *dev)
{
// 如果是广播设备,则获取广播 tick_device;否则,获取与 dev->id 对应的 per-cpu tick_device。
return dev == &tick_bc_dev ? tick_get_broadcast_device() :
&per_cpu(tick_cpu_device, dev->id);
}
// 初始化广播时钟事件的 sysfs 接口。
static __init int tick_broadcast_init_sysfs(void)
{
int err = device_register(&tick_bc_dev); // 注册广播设备。
if (!err)
// 为广播设备创建 "current_device" 属性文件。
err = device_create_file(&tick_bc_dev, &dev_attr_current_device);
return err;
}
#else // 如果没有配置广播时钟事件。
// 辅助函数,直接获取与 dev->id 对应的 per-cpu tick_device。
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
// 初始化 per-cpu tick 设备的 sysfs 接口。
static int __init tick_init_sysfs(void)
{
int cpu;
// 遍历系统中所有可能存在的CPU。
for_each_possible_cpu(cpu) {
// 获取该CPU对应的 per-cpu device 结构体指针。
struct device *dev = &per_cpu(tick_percpu_dev, cpu);
int err;
dev->id = cpu; // 设置设备的ID为CPU号。
dev->bus = &clockevents_subsys; // 关联到 clockevents 总线。
// 注册这个 per-cpu 设备。
err = device_register(dev);
if (!err)
// 为设备创建 "current_device" 属性文件。
err = device_create_file(dev, &dev_attr_current_device);
if (!err)
// 为设备创建 "unbind_device" 属性文件。
err = device_create_file(dev, &dev_attr_unbind_device);
if (err)
return err; // 如果任何一步失败,则返回错误。
}
// 初始化广播设备的 sysfs 接口。
return tick_broadcast_init_sysfs();
}
// clockevents 子系统 sysfs 接口的总初始化函数。
static int __init clockevents_init_sysfs(void)
{
// 注册 "clockevents" 总线。
int err = subsys_system_register(&clockevents_subsys, NULL);
if (!err)
// 如果总线注册成功,则继续初始化 tick 设备的 sysfs 接口。
err = tick_init_sysfs();
return err;
}
// 将 clockevents_init_sysfs 注册为一个设备初始化调用,在内核启动的相应阶段执行。
device_initcall(clockevents_init_sysfs);
#endif /* SYSFS */
770

被折叠的 条评论
为什么被折叠?



