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


title: alarmtimer

categories:

  • linux
  • kernel
  • time
    tags:
  • linux
  • kernel
  • time
    abbrlink: 18d6212c
    date: 2025-10-06 08:07:07

文章目录

https://github.com/wdfk-prog/linux-study

kernel/time/alarmtimer.c

闹钟定时器(Alarm Timer)初始化:构建可挂起的定时器基础框架

本代码片段的核心功能是初始化Linux内核中的闹钟定时器(Alarm Timer)子系统。闹钟定时器的主要特点是它们能够在系统进入挂起(suspend)等低功耗状态后,依然能够到期并唤醒系统。此初始化函数负责建立管理这些定时器的核心数据结构,将它们与具体的时钟源(如CLOCK_REALTIMECLOCK_BOOTTIME)关联起来,并注册相应的驱动以等待与硬件设备绑定。

实现原理分析

此初始化过程是闹钟定时器框架能够工作的基础,它通过配置一个预定义的alarm_bases全局数组来为不同类型的闹钟定时器提供统一的管理接口。

  1. 配置时钟源 : 函数首先为REALTIMEBOOTTIME两个闹钟"基地"(alarm_bases数组的元素)分别配置其clockid和获取时间的函数指针。这使得上层代码可以通过ALARM_REALTIME类型来设置一个基于"墙上时间"(wall-clock time)的定时器,或通过ALARM_BOOTTIME类型设置一个基于系统启动时间的单调递增定时器。
  2. 通用初始化 : 通过一个循环,为每一个闹钟基地初始化其定时器队列头(timerqueue_init_head)和自旋锁(spin_lock_init)。定时器队列是一种高效的数据结构(通常是红黑树),用于按唤醒时间的先后顺序来组织所有的定时器事件。
  3. 硬件接口建立 : 调用alarmtimer_rtc_interface_setup。这是一个关键步骤,它负责在闹钟定时器框架与底层的RTC(Real-Time Clock,实时时钟)设备之间建立联系。RTC是能够在CPU休眠时依然保持计时的硬件,是实现唤醒功能的物理基础。
  4. 驱动注册 : 调用platform_driver_registeralarmtimer_driver注册为一个平台驱动。这意味着闹钟定时器功能被实现为一个等待与平台设备(platform device)进行绑定的驱动程序。这个平台设备通常由底层的RTC驱动或者专门的定时器硬件驱动来注册,代表了能够提供闹钟功能的物理硬件。
  5. 错误处理 : 函数包含了goto跳转的错误处理机制。如果在注册平台驱动时失败,程序会跳转到out_if标签,执行alarmtimer_rtc_interface_remove来撤销之前建立的RTC接口,保证系统状态的一致性。

代码分析

c 复制代码
// alarmtimer_init: 闹钟定时器代码的初始化函数。
// 描述: 此函数初始化闹钟基地并注册POSIX时钟ID。
// __init 标记表示该函数仅在内核初始化期间使用,之后其占用的内存可以被回收。
static int __init alarmtimer_init(void)
{
	int error;
	int i;

	// 初始化RTC相关的定时器部分。
	alarmtimer_rtc_timer_init();

	/* 初始化闹钟基地 */
	// 配置实时闹钟(ALARM_REALTIME)。
	alarm_bases[ALARM_REALTIME].base_clockid = CLOCK_REALTIME;
	alarm_bases[ALARM_REALTIME].get_ktime = &ktime_get_real;
	alarm_bases[ALARM_REALTIME].get_timespec = ktime_get_real_ts64;

	// 配置启动时间闹钟(ALARM_BOOTTIME)。
	alarm_bases[ALARM_BOOTTIME].base_clockid = CLOCK_BOOTTIME;
	alarm_bases[ALARM_BOOTTIME].get_ktime = &ktime_get_boottime;
	alarm_bases[ALARM_BOOTTIME].get_timespec = get_boottime_timespec;

	// 循环遍历所有类型的闹钟基地,进行通用部分的初始化。
	for (i = 0; i < ALARM_NUMTYPE; i++) {
		// 初始化定时器队列头。
		timerqueue_init_head(&alarm_bases[i].timerqueue);
		// 初始化自旋锁。
		spin_lock_init(&alarm_bases[i].lock);
	}

	// 建立闹钟定时器与底层RTC设备的接口。
	error = alarmtimer_rtc_interface_setup();
	if (error)
		return error;

	// 将alarmtimer_driver注册为一个平台驱动,等待与硬件设备匹配。
	error = platform_driver_register(&alarmtimer_driver);
	if (error)
		goto out_if; // 如果注册失败,跳转到清理代码。

	return 0; // 初始化成功。
out_if:
	// 初始化出错时的清理路径:移除之前建立的RTC接口。
	alarmtimer_rtc_interface_remove();
	return error;
}
// 使用device_initcall宏,将此初始化函数注册在设备驱动初始化阶段执行。
device_initcall(alarmtimer_init);

闹钟定时器硬件后端发现与绑定

本代码片段展示了闹钟定时器(Alarm Timer)框架如何动态地发现并绑定一个合适的实时时钟(RTC)设备作为其硬件后端。其核心功能是创建一个"类接口"(class interface),监听系统中所有RTC设备的注册事件。一旦一个具备唤醒能力的RTC设备出现,此代码就会尝试获取该设备,并注册一个名为"alarmtimer"的平台设备,从而将通用的闹钟定时器服务与具体的物理硬件连接起来。

实现原理分析

该机制是Linux设备模型中"胶水层"代码的典型范例,它利用class_interface机制来解耦通用子系统与具体设备驱动。

  1. 类接口注册 (alarmtimer_rtc_interface_setup):

    • 此函数是整个机制的入口。它初始化一个class_interface结构体,将其与rtc_class(所有RTC设备都属于这个类)进行关联。
    • 通过调用class_interface_register,它向内核设备模型注册一个回调。其效果是:每当系统中有一个属于rtc_class的设备被添加时,内核就会自动调用该接口中指定的.add_dev函数,即alarmtimer_rtc_add_device
  2. 设备添加处理 (alarmtimer_rtc_add_device):

    • 这是回调的核心逻辑,当一个新的RTC设备被注册时,此函数被调用。
    • 单例检查 : 首先检查全局指针rtcdev是否已经被赋值。闹钟定时器框架设计为只使用一个RTC设备作为后端,因此如果已经绑定了一个,则直接返回-EBUSY
    • 能力检查 : 对传入的RTC设备进行严格的能力验证。
      a. test_bit(RTC_FEATURE_ALARM, rtc->features): 检查该RTC设备是否声称支持硬件闹钟功能。
      b. device_may_wakeup(rtc->dev.parent): 检查该RTC设备是否有能力唤醒系统。这是实现可挂起定时器的物理前提。
      任何一项检查失败,都表示此RTC不适合作为闹钟定时器的后端。
    • 平台设备注册 : 如果能力检查通过,它会调用platform_device_register_data注册一个新的平台设备 ,名称为"alarmtimer"。这一步是关键的抽象转换:它将一个具体的RTC设备(如rtc0)的存在,转化为了一个更通用的"alarmtimer"设备的存在。这个新创建的"alarmtimer"平台设备随后就可以与上一段代码分析中注册的alarmtimer_driver平台驱动进行匹配和绑定。
    • 独占式获取 : 在一个自旋锁的保护下,它再次检查rtcdev,并尝试获取RTC驱动模块和设备的引用计数(try_module_get, get_device)。这确保了在闹钟定时器使用期间,底层的RTC驱动不会被卸载。成功后,将全局rtcdev指向该RTC设备。
  3. 接口注销 (alarmtimer_rtc_interface_remove):

    • 这是一个清理函数,它调用class_interface_unregister来移除之前注册的回调,停止监听新的RTC设备事件。

代码分析

c 复制代码
// alarmtimer_rtc_add_device: 当一个RTC设备被添加到系统时,此回调函数被调用。
// @dev: 指向新添加设备的通用 device 结构体指针。
// 返回值: 成功绑定则为0,否则为负数错误码。
static int alarmtimer_rtc_add_device(struct device *dev)
{
	// 将通用的device指针转换为rtc_device指针。
	struct rtc_device *rtc = to_rtc_device(dev);
	struct platform_device *pdev;
	int ret = 0;

	// 如果已经有一个RTC设备被用作闹钟定时器,则返回忙碌。
	if (rtcdev)
		return -EBUSY;

	// 检查RTC设备是否支持闹钟功能。如果不支持,则返回错误。
	if (!test_bit(RTC_FEATURE_ALARM, rtc->features))
		return -1;
	// 检查RTC设备是否能够唤醒系统。如果不能,则返回错误。
	if (!device_may_wakeup(rtc->dev.parent))
		return -1;

	// 注册一个名为"alarmtimer"的平台设备。这个新设备将与alarmtimer_driver匹配。
	pdev = platform_device_register_data(dev, "alarmtimer",
					     PLATFORM_DEVID_AUTO, NULL, 0);
	// 如果平台设备注册成功,则将其标记为可唤醒设备。
	if (!IS_ERR(pdev))
		device_init_wakeup(&pdev->dev, true);

	// 使用一个带自动解锁功能的锁保护对全局变量rtcdev的访问。
	// spinlock_irqsave 会在加锁时禁用中断。
	scoped_guard(spinlock_irqsave, &rtcdev_lock) {
		// 在锁内再次检查,并确保可以获取RTC驱动模块的引用。
		if (!IS_ERR(pdev) && !rtcdev && try_module_get(rtc->owner)) {
			// 将此RTC设备设为全局唯一的闹钟定时器后端。
			rtcdev = rtc;
			/* 持有一个设备引用,防止其在使用过程中被释放。 */
			get_device(dev);
			// pdev指针置为NULL,防止在函数末尾被错误地注销。
			pdev = NULL;
		} else {
			// 如果获取失败,设置返回值为错误。
			ret = -1;
		}
	}

	// 如果pdev不为NULL(意味着上面的独占获取失败了),则注销之前注册的平台设备。
	platform_device_unregister(pdev);
	return ret;
}

// alarmtimer_rtc_timer_init: 初始化一个rtc_timer结构体。
static inline void alarmtimer_rtc_timer_init(void)
{
	// 这是一个简单的包装函数,用于初始化rtctimer。
	rtc_timer_init(&rtctimer, NULL, NULL);
}

// 定义一个类接口,指定当有设备添加到rtc_class时要调用的函数。
static struct class_interface alarmtimer_rtc_interface = {
	.add_dev = &alarmtimer_rtc_add_device,
};

// alarmtimer_rtc_interface_setup: 设置并注册上述的类接口。
static int alarmtimer_rtc_interface_setup(void)
{
	// 指定接口要监听的设备类别为rtc_class。
	alarmtimer_rtc_interface.class = &rtc_class;
	// 向内核注册此接口。
	return class_interface_register(&alarmtimer_rtc_interface);
}

// alarmtimer_rtc_interface_remove: 注销类接口。
static void alarmtimer_rtc_interface_remove(void)
{
	// 从内核中注销此接口,停止监听RTC设备事件。
	class_interface_unregister(&alarmtimer_rtc_interface);
}

系统挂起处理与唤醒闹钟设置

本代码片段是 Linux 内核闹钟定时器(alarmtimer)子系统的电源管理核心。其主要功能是在系统进入挂起(suspend)状态前,通过注册的 alarmtimer_suspend 回调函数,精确计算出下一次需要唤醒系统的时间点,并利用绑定的硬件实时时钟(RTC)设置一个物理唤醒闹钟。在系统恢复(resume)时,通过 alarmtimer_resume 回调函数清理该闹钟。这套机制确保了基于闹钟定时器的定时任务(如 CLOCK_REALTIME_ALARM)能够在系统深度睡眠时依然被准确唤醒和执行。

实现原理分析

该功能通过标准的平台驱动(platform_driver)模型,将闹钟定时器的逻辑与内核的电源管理(Power Management, PM)框架进行挂钩。

  1. 驱动与设备绑定:

    • alarmtimer_driver 是一个平台驱动,其名称为 "alarmtimer"。在系统初始化阶段,当 alarmtimer_rtc_add_device 函数成功发现并绑定一个合适的 RTC 设备后,会创建一个同名为 "alarmtimer" 的平台设备。内核的设备模型会自动将此驱动与该设备进行匹配。
    • 匹配成功后,alarmtimer_driver 中定义的 dev_pm_ops(设备电源管理操作集)即被激活。
  2. 系统挂起处理 (alarmtimer_suspend):

    • 当系统准备进入挂起状态时,内核的 PM 核心会调用所有已注册驱动的 .suspend 回调,alarmtimer_suspend 随之被执行。
    • 获取最早到期时间 : 函数的核心任务是找出所有已注册的闹钟中,最先到期的那一个。它会遍历 alarm_bases 数组中所有的闹钟类型(ALARM_REALTIMEALARM_BOOTTIME)。
    • 查询定时器队列 : 对于每种闹钟类型,它会持有该基座的自旋锁,并调用 timerqueue_getnext 从其红黑树实现的定时器队列中,获取到期时间最早的节点。
    • 计算最小时间差 : 它计算出每个队列中最早到期时间与当前时间的差值(delta),并与一个全局最小值 min 进行比较,持续更新 min 以保存全局最小的时间差。
    • 安全边界检查 : 如果计算出的最小时间差 min 小于2秒,这意味着有一个闹钟即将到期。为了确保该闹钟能够被精确处理,函数会调用 pm_wakeup_event 来短暂地阻止系统挂起,并返回 -EBUSY。这使得系统有时间在活动状态下处理这个即将到期的软件定时器。
    • 硬件闹钟编程 : 如果 min 大于安全边界,函数将执行以下步骤来设置硬件唤醒:
      a. rtc_timer_cancel: 首先取消任何可能存在的旧的 RTC 闹钟。
      b. rtc_read_time: 读取当前 RTC 硬件的精确时间。
      c. rtc_bound_alarmtime: 考虑到某些 RTC 硬件可能不支持设置过长时间的闹钟,此函数会将 min 调整到硬件支持的最大范围内。
      d. rtc_timer_start: 将当前 RTC 时间与调整后的 min 相加,得到未来的绝对唤醒时间,并调用此函数将该时间编程到 RTC 硬件的闹钟寄存器中。
  3. 系统恢复处理 (alarmtimer_resume):

    • 当系统从挂起状态被唤醒后(无论是由 RTC 闹钟还是其他唤醒源),PM 核心会调用 .resume 回调。
    • alarmtimer_resume 的逻辑非常简单:它会调用 rtc_timer_cancel 来无条件地取消之前设置的 RTC 硬件闹钟。这是一个必要的清理步骤,因为系统已经被唤醒,后续的定时将重新由高精度的软件定时器(hrtimer)接管。

代码分析

c 复制代码
// alarmtimer_get_rtcdev: 获取并返回当前被选定用于唤醒闹钟的rtc_device结构体指针。
// 该函数是线程和中断安全的。
struct rtc_device *alarmtimer_get_rtcdev(void)
{
	// 声明一个用于存储返回值的rtc_device指针。
	struct rtc_device *ret;

	// 使用guard宏和spinlock_irqsave来保护对全局变量rtcdev的访问。
	// 这会获取rtcdev_lock自旋锁,并在期间禁用本地中断,以保证操作的原子性。
	guard(spinlock_irqsave)(&rtcdev_lock);
	// 读取全局的rtcdev指针。
	ret = rtcdev;

	// 返回获取到的rtcdev指针。guard宏会在作用域结束时自动释放锁并恢复中断。
	return ret;
}
// 将alarmtimer_get_rtcdev函数导出,使其可以被其他内核模块调用。
EXPORT_SYMBOL_GPL(alarmtimer_get_rtcdev);

// alarmtimer_suspend: 系统进入挂起状态时的回调函数。
// @dev: 指向与此驱动关联的设备结构体指针(此处未使用)。
// 返回值: 成功设置唤醒闹钟则为0,若短暂阻止挂起则为-EBUSY,其他错误则为负值。
static int alarmtimer_suspend(struct device *dev)
{
	// 声明用于时间计算的ktime_t类型的变量。
	ktime_t min, now, expires;
	// 声明一个指向RTC设备结构体的指针。
	struct rtc_device *rtc;
	// 声明一个用于读取RTC时间的rtc_time结构体。
	struct rtc_time tm;
	// 声明循环计数器、返回值和闹钟类型变量。
	int i, ret, type;

	// 使用scoped_guard宏保护对freezer相关全局变量的访问。
	// 这是一个原子操作,用于获取与进程冻结相关的预设闹钟信息。
	scoped_guard(spinlock_irqsave, &freezer_delta_lock) {
		min = freezer_delta;
		expires = freezer_expires;
		type = freezer_alarmtype;
		// 读取后立即清零,确保只处理一次。
		freezer_delta = 0;
	}

	// 获取当前绑定的RTC设备。
	rtc = alarmtimer_get_rtcdev();
	// 如果没有绑定的RTC设备,则无法设置唤醒闹钟,直接返回0表示成功(无事可做)。
	if (!rtc)
		return 0;

	// 遍历所有闹钟类型(REALTIME, BOOTTIME),以找出最早到期的那一个。
	for (i = 0; i < ALARM_NUMTYPE; i++) {
		// 获取当前类型的闹钟基准。
		struct alarm_base *base = &alarm_bases[i];
		// 声明一个指向定时器队列节点的指针。
		struct timerqueue_node *next;
		// 声明一个用于存储时间差的变量。
		ktime_t delta;

		// 在锁保护下,从定时器队列中获取下一个(即最早)到期的节点。
		scoped_guard(spinlock_irqsave, &base->lock)
			next = timerqueue_getnext(&base->timerqueue);
		// 如果该类型的队列为空,则继续检查下一种类型。
		if (!next)
			continue;
		// 计算该闹钟的到期时间与当前时间的差值。
		delta = ktime_sub(next->expires, base->get_ktime());
		// 如果min为0(尚未初始化)或者当前delta更小,则更新全局最早到期信息。
		if (!min || (delta < min)) {
			expires = next->expires;
			min = delta;
			type = i;
		}
	}
	// 如果遍历完所有闹钟后,min仍为0,表示没有活动的唤醒闹钟,直接返回。
	if (min == 0)
		return 0;

	// 如果最早的闹钟在2秒内就要到期,则阻止系统挂起。
	// 这样做是为了让即将到期的软件定时器能在系统活动状态下被精确处理。
	if (ktime_to_ns(min) < 2 * NSEC_PER_SEC) {
		// 发送一个唤醒事件,推迟挂起过程。
		pm_wakeup_event(dev, 2 * MSEC_PER_SEC);
		// 返回-EBUSY,通知电源管理核心当前无法挂起。
		return -EBUSY;
	}

	// 记录将要设置的唤醒闹钟的追踪信息。
	trace_alarmtimer_suspend(expires, type);

	// 取消任何可能已存在的由本模块设置的RTC定时器。
	rtc_timer_cancel(rtc, &rtctimer);
	// 从RTC硬件读取当前的日历时间。
	rtc_read_time(rtc, &tm);
	// 将读取到的rtc_time格式转换为内核通用的ktime_t格式。
	now = rtc_tm_to_ktime(tm);

	// 考虑到RTC硬件可能对可设置的闹钟时间范围有限制,
	// 调用rtc_bound_alarmtime将min调整到硬件支持的最大值以内。
	min = rtc_bound_alarmtime(rtc, min);

	// 计算出绝对的唤醒时间点(当前时间 + 相对时间差)。
	now = ktime_add(now, min);

	// 使用计算出的绝对时间,启动RTC硬件闹钟。
	// 如果设置的唤醒时间已经过去,该函数会返回错误。
	ret = rtc_timer_start(rtc, &rtctimer, now, 0);
	// 如果rtc_timer_start返回错误(例如时间已过去),则发送一个唤醒事件。
	// 这会短暂地阻止挂起,让系统有机会处理这个错过的事件。
	if (ret < 0)
		pm_wakeup_event(dev, MSEC_PER_SEC);
	// 返回rtc_timer_start的结果。
	return ret;
}

// alarmtimer_resume: 系统从挂起状态恢复时的回调函数。
// @dev: 指向与此驱动关联的设备结构体指针(此处未使用)。
static int alarmtimer_resume(struct device *dev)
{
	// 声明一个指向RTC设备结构体的指针。
	struct rtc_device *rtc;

	// 获取当前绑定的RTC设备。
	rtc = alarmtimer_get_rtcdev();
	// 如果存在绑定的RTC设备,则取消之前在suspend期间设置的硬件闹钟。
	// 这是一个必要的清理步骤,因为系统已被唤醒,软件定时器将接管。
	if (rtc)
		rtc_timer_cancel(rtc, &rtctimer);
	// 返回0表示成功。
	return 0;
}

// 定义一个设备电源管理操作集结构体。
static const struct dev_pm_ops alarmtimer_pm_ops = {
	// 将.suspend回调指向alarmtimer_suspend函数。
	.suspend = alarmtimer_suspend,
	// 将.resume回调指向alarmtimer_resume函数。
	.resume = alarmtimer_resume,
};

// 定义一个平台驱动结构体。
static struct platform_driver alarmtimer_driver = {
	.driver = {
		// 设置驱动的名称为"alarmtimer"。
		// 这个名称将用于与由alarmtimer_rtc_add_device创建的平台设备进行匹配。
		.name = "alarmtimer",
		// 将驱动的电源管理操作集指向上面定义的alarmtimer_pm_ops。
		.pm = &alarmtimer_pm_ops,
	}
};
相关推荐
小志biubiu3 小时前
【Linux】Ext系列文件系统
linux·服务器·c语言·经验分享·笔记·ubuntu·操作系统
charlie1145141913 小时前
现代 Python 学习笔记:Statements & Syntax
笔记·python·学习·教程·基础·现代python·python3.13
ha20428941943 小时前
Linux操作系统学习之---基于环形队列的生产者消费者模型(毛坯版)
linux·c++·学习
dxnb223 小时前
Datawhale25年10月组队学习:math for AI+Task5解析几何
人工智能·学习
哲Zheᗜe༘4 小时前
了解学习Redis主从复制
数据库·redis·学习
南林yan5 小时前
Debian、Ubuntu、CentOS:Linux 三大发行版的核心区别
linux·ubuntu·debian·linux内核
井队Tell5 小时前
打造高清3D虚拟世界|零基础学习Unity HDRP高清渲染管线(第九天)
学习·3d·unity
FserSuN7 小时前
Mem0:构建具有可扩展长期记忆的生产级AI代理 - 论文学习总结1
人工智能·学习
im_AMBER7 小时前
Leetcode 41
笔记·学习·算法·leetcode