[Linux]学习笔记系列 -- lib/timerqueue.c Timer Queue Management 高精度定时器的有序数据结构

文章目录

lib/timerqueue.c Timer Queue Management 高精度定时器的有序数据结构

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

历史与背景

这项技术是为了解决什么特定问题而诞生的?

timerqueue 的诞生是为了解决在内核中高效管理大量、无序、高精度定时器 的问题。在 timerqueue 出现之前,Linux内核主要使用基于"时间轮"(Timer Wheel)的 timer_list 机制来管理定时器。时间轮对于处理大量在同一"节拍"(jiffy)到期的低精度定时器非常高效(O(1)复杂度),但它不适用于以下场景:

  • 高精度需求 :时间轮的精度受限于系统节拍(jiffy),通常是毫秒级别。对于需要纳秒级精度的现代应用(如 nanosleep、POSIX interval timers),时间轮无法满足需求。
  • 无序到期时间:高精度定时器的到期时间是稀疏且随机分布在时间轴上的。如果用时间轮来管理,需要一个非常巨大且大部分为空的时间轮,效率极低。
  • 快速查找下一个到期事件:高精度定时器系统需要能够非常快速地找出"下一个最早将要到期的定时器",以便对硬件时钟进行编程。在时间轮中查找下一个非空的时间槽,开销可能很大。

timerqueue 提供了一个基于红黑树的数据结构,专门用于将定时器按照其精确的到期时间进行排序,从而完美地解决了上述问题。

它的发展经历了哪些重要的里程碑或版本迭代?

timerqueue 的发展与Linux内核**高精度定时器(High-Resolution Timers, hrtimers)**子系统的演进紧密相连。它并非一个独立的用户可见的功能,而是作为 hrtimer 的核心基础被引入的。

  • hrtimer的引入 :内核引入 hrtimer 框架是其发展的最重要里程碑。timerqueue 作为 hrtimer 的核心数据结构被一并开发和集成,用于取代之前一些架构中用于高精度定时的、效率较低的链表实现。
  • 成为标准 :随着 hrtimer 成为内核实现高精度定时(如 nanosleep、POSIX timers)的标准方式,timerqueue 也因此成为了内核时间管理子系统中一个稳定且基础的组件。它本身是一个库文件,提供了一套通用的、按时间排序的队列管理API。
目前该技术的社区活跃度和主流应用情况如何?

timerqueue 是Linux内核时间子系统中一个非常核心、成熟且稳定的组件。它不直接面向驱动开发者或用户空间,而是一个内部库 。它的主要用户是 hrtimer 子系统。因此,所有使用 hrtimer 的内核功能都在间接地使用 timerqueue。这包括:

  • 用户空间的精密睡眠调用 (nanosleep) 和 POSIX timers。
  • 内核中需要精确调度时间的子系统,例如一些网络流量整形器(scheduler)、实时(RT)任务的定时唤醒等。
    其代码库非常稳定,修改通常只涉及性能优化或与内核其他部分的适应性调整。

核心原理与设计

它的核心工作原理是什么?

timerqueue 的核心是一个**红黑树(Red-Black Tree)**数据结构。

  1. 数据结构 :每个需要被管理的定时器都包含一个 struct timerqueue_node 成员。
  2. 排序键 :红黑树的排序键是 timerqueue_node 中的 expires 字段,这是一个 ktime_t 类型的值,能够表示纳秒级别的精确时间。
  3. 操作
    • 添加定时器 (timerqueue_add) :当一个新的定时器被添加时,timerqueue 会将其作为一个新节点插入到红黑树中。红黑树的特性保证了插入操作的时间复杂度为 O(log n),其中 n 是定时器的数量。
    • 删除定时器 (timerqueue_del):删除一个已存在的定时器也是一个 O(log n) 的操作。
    • 查找下一个到期定时器 (timerqueue_getnext) :由于红黑树的有序性,树的最左边的节点永远是即将到期的那个定时器。获取这个节点的操作非常高效。

通过使用红黑树,timerqueue 能够以可扩展且高效的方式维护一个按精确到期时间排序的定时器集合。

它的主要优势体现在哪些方面?
  • 高效的动态排序:对于动态添加和删除的大量定时器,O(log n) 的复杂度远优于简单链表的 O(n)。
  • 高精度 :能够以纳秒精度对定时器进行排序,这是实现 hrtimer 的基础。
  • 快速查找下一个事件 :能够非常快速地定位到下一个要到期的定时器,这对于 hrtimer 子系统编程硬件时钟至关重要。
  • 可扩展性:即使系统中存在大量高精度定时器,其性能也能保持稳定。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
  • 不适用于低精度场景 :对于传统的、大量的、集中在同一 jiffy 到期的低精度定时器,时间轮 O(1) 的分摊复杂度比红黑树 O(log n) 的复杂度更优。因此,timerqueue 在这种场景下是一种性能上的浪费。
  • 实现复杂性:红黑树的实现比时间轮或链表要复杂得多。
  • 内部库的定位 :它本身不是一个完整的定时器系统,只是一个数据结构的管理库。它不负责触发定时器或与硬件交互,这些工作由它的使用者(主要是 hrtimer)来完成。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

timerqueue 是内核 hrtimer 子系统的内部实现和唯一选择 。因此,它的使用场景等同于 hrtimer 的使用场景。

  • 例一:nanosleep 系统调用
    当用户空间程序调用 nanosleep(const struct timespec *req, ...) 请求进行一次高精度的睡眠时,内核会创建一个 hrtimer,其到期时间设置为当前时间加上请求的睡眠时间。这个 hrtimer 随后被添加到 timerqueue 中进行管理。当硬件时钟中断发生,并且 hrtimer 子系统发现这个定时器已到期时,就会唤醒该进程。
  • 例二:POSIX Interval Timers
    当用户使用 timer_createtimer_settime 创建一个周期性的高精度定时器时,内核同样会使用 hrtimer 来实现。每次定时器到期后,hrtimer 的回调函数会发送信号给用户进程,并重新设置下一次到期时间,然后再次将 hrtimer 添加回 timerqueue
  • 例三:内核高精度任务
    内核中的某些调度器或驱动,如果需要在未来的一个精确时间点执行某个动作(而不是模糊的下一个 jiffy),就会使用 hrtimer
是否有不推荐使用该技术的场景?为什么?

不推荐(甚至禁止)驱动程序或其他内核子系统直接使用 timerqueue

  • 原因timerqueue 只是一个数据结构库。内核已经提供了封装好的、功能完整的定时器API:
    • timer_list / add_timer:用于传统的、毫秒(jiffy)级别的低精度定时。
    • hrtimer :用于纳秒级别的高精度定时。
      驱动开发者应该使用这两个高层API,而不是直接操作底层的 timerqueue,否则就是不必要的重复劳动,并且很容易出错。

对比分析

请将其 与 其他相似技术 进行详细对比。

在Linux内核中,timerqueue 最直接的对比对象是用于传统 timer_list 的**时间轮(Timer Wheel)**数据结构。

特性 timerqueue (lib/timerqueue.c) Timer Wheel (kernel/time/timer.c)
核心数据结构 红黑树 (Red-Black Tree) 哈希链表数组 (Array of Linked Lists)
排序/分组方式 精确的纳秒级到期时间 (ktime_t) 严格排序。 按**粗略的节拍(jiffy)**分桶(bucket),同一桶内的定时器无序。
插入/删除复杂度 O(log n) O(1) (分摊复杂度)
精度 高精度 (Nanosecond) 低精度 (Jiffy, typically 1-10ms)
查找下一个事件 高效。下一个到期的定时器总是在树的最左侧。 可能低效。需要扫描时间轮的桶,直到找到一个非空的。
主要使用者 高精度定时器子系统 (hrtimer) 传统内核定时器 (timer_list)
典型用途 nanosleep, POSIX timers, 实时内核任务的精确唤醒。 网络协议超时、块设备请求超时、轮询等不要求精确时间点的场景。
资源开销 内存与定时器数量成正比,每次操作有对数时间的CPU开销。 有一个固定大小的基座(时间轮数组),但每个定时器的操作非常快。

include/linux/timerqueue.h

timerqueue_init

c 复制代码
static inline void timerqueue_init(struct timerqueue_node *node)
{
	RB_CLEAR_NODE(&node->node);
}

lib/timerqueue.c

timerqueue_add 将计时器添加到 timerqueue

c 复制代码
/**
 * timerqueue_add - 将计时器添加到 timerqueue。
 *
 * @head:timerqueue 的头部
 * @node:待添加的定时器节点
 *
 * 将 timer 节点添加到 timerqueue,按节点的 expires 值排序。
 * 如果新添加的计时器是队列中第一个过期的计时器,则返回 true。
 */
bool timerqueue_add(struct timerqueue_head *head, struct timerqueue_node *node)
{
	/* 确保我们没有添加已经添加的节点d */
	WARN_ON_ONCE(!RB_EMPTY_NODE(&node->node));

	return rb_add_cached(&node->node, &head->rb_root, __timerqueue_less);
}
EXPORT_SYMBOL_GPL(timerqueue_add);
相关推荐
zhuzhuxia⌓‿⌓2 小时前
线性表的顺序和链式存储
数据结构·c++·算法
大聪明-PLUS3 小时前
如何从 USB 闪存驱动器安装 Debian Linux
linux·嵌入式·arm·smarc
高山有多高3 小时前
栈:“后进先出” 的艺术,撑起程序世界的底层骨架
c语言·开发语言·数据结构·c++·算法
报错小能手3 小时前
linux学习笔记(18)进程间通讯——共享内存
linux·服务器·前端
YouEmbedded3 小时前
解码查找算法与哈希表
数据结构·算法·二分查找·散列表·散列查找·线性查找
第四维度43 小时前
【全志V821_FoxPi】6-2 IMX219 MIPI摄像头适配
linux·ipc·tina·v821·imx219
杜子不疼.3 小时前
【Linux】进程的初步探险:基本概念与基本操作
linux·人工智能·ai
wdfk_prog4 小时前
构建基于Hexo、Butterfly、GitHub与Cloudflare的高性能个人博客
笔记·学习·github·hexo·blog
初级炼丹师(爱说实话版)4 小时前
MySql速成笔记6(DQL多表)
笔记