[Linux]学习笔记系列 -- [kernel][time]clockevents

#星光不负码向未来·1024鸿蒙征文活动#

title: clockevents
categories:

  • linux
  • kernel
  • time
    tags:
  • linux
  • kernel
  • time
    abbrlink: df20a2f0
    date: 2025-10-06 11:28:50


在这里插入图片描述

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 硬件资源的典范,其核心技术是 远程过程调用状态驱动的原子替换

  1. 跨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),从而实现了在正确的“地点”执行关键代码。

  2. 两阶段解绑与替换 (__clockevents_try_unbind): 此函数体现了对不同设备状态的精确处理:

    • 快速路径: 如果设备已经是 detached(分离)状态,意味着它当前未被使用。此时可以直接从全局链表中移除(list_del_init),操作非常迅速。
    • 慢速路径 (替换): 如果设备正被当前 CPU 用于内核节拍 (ced == per_cpu(tick_cpu_device, cpu).evtdev),直接移除是不行的,会导致系统失去心跳。此时,函数返回 -EAGAIN,通知上层调用者(__clockevents_unbind)必须执行一个更复杂的操作——clockevents_replaceclockevents_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 内核“一切皆文件”思想的典型体现,其实现原理基于对内核设备模型的深度集成。

  1. 设备模型抽象: 代码并未直接创建文件,而是将 clockevents 子系统的内部组件抽象为设备模型的标准元素:

    • 总线 (bus_type): clockevents_subsys 定义了一个名为 “clockevents” 的总线类型。这会在 sysfs 中创建 /sys/bus/clockevents/ 目录,作为所有相关设备的容器。
    • 设备 (device): 系统中的每个 CPU 核心都被视为一个独立的“节拍设备”,代码为此定义了一个 per-CPUstruct 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)。
  2. 两阶段解绑的同步技巧 (unbind_device_store): 解绑设备的操作展示了一种复杂的同步策略。它首先在持有轻量级的 raw_spin_lock_irq 的情况下,调用 __clockevents_try_unbind 尝试快速解绑。自旋锁下禁止睡眠,因此 __clockevents_try_unbind 如果遇到需要等待或可能导致睡眠的情况,会立即返回 -EAGAINunbind_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 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值