ESC
输入关键词搜索文章标题和内容

嵌入式Linux内核定时器与延迟工作:jiffies、hrtimer、delayed_work实战

本文由 linuxROS 整理发布,首发于 linuxros.cn,转载请注明出处。

嵌入式Linux内核定时器与延迟工作:jiffies、hrtimer、delayed_work实战

内核时间子系统是驱动开发绕不开的基础。本文从jiffies/HZ出发,拆解timer_list、hrtimer、delayed_work三种定时机制,配合API实例和选型流程图,解决定时器选型和常见踩坑问题。

一、原理简析

Linux内核用jiffies全局变量记录系统启动以来的时钟滴答数,每来一次硬件定时器中断,jiffies加1。HZ是编译期配置的常量,表示每秒的滴答数,直接决定了定时器的精度上限。

CONFIG_HZ 滴答周期 典型场景
100 10ms 服务器、嵌入式低功耗
250 4ms 桌面默认
1000 1ms 低延迟桌面、实时场景

内核提供一组转换辅助函数,避免手动计算HZ:

#include <linux/jiffies.h>

msecs_to_jiffies(500);       /* 毫秒 → jiffies */
usecs_to_jiffies(100);       /* 微秒 → jiffies(向上取整) */
jiffies_to_msecs(jiffies);   /* jiffies → 毫秒 */
nsecs_to_jiffies(1000000UL); /* 纳秒 → jiffies */

jiffies是unsigned long类型,约49.7天回绕一次(HZ=1000时)。内核提供了安全的时间比较宏,自动处理回绕:

time_after(a, b);    /* a 在 b 之后? */
time_before(a, b);   /* a 在 b 之前? */
time_after_eq(a, b); /* a 在 b 之后或相等? */
time_before_eq(a, b);/* a 在 b 之前或相等? */

一条规则:永远不要用a > b比较jiffies,必须用上述宏。

二、低精度定时器(timer_list)

timer_list基于分层时间轮(Timer Wheel)实现,插入和删除复杂度O(1),精度受限于HZ(1~10ms),适合超时检测、看门狗等"大概率被取消"的场景。

2.1 数据结构

struct timer_list {
    struct hlist_node  entry;    /* 时间轮桶链表 */
    unsigned long      expires;  /* 绝对到期时间(jiffies) */
    void (*function)(struct timer_list *); /* 回调函数 */
    u32                flags;    /* CPU亲和性 + TIMER_*标志 */
};

2.2 API用法

#include <linux/timer.h>

struct my_dev {
    int counter;
    struct timer_list tick_timer;
};

/* 回调函数:4.15+ 签名为 void (*)(struct timer_list *) */
static void tick_callback(struct timer_list *t)
{
    /* from_timer:从timer_list反查包含它的父结构体 */
    struct my_dev *dev = from_timer(dev, t, tick_timer);

    dev->counter++;
    printk("tick #%d\n", dev->counter);

    /* 重新定时,实现周期触发 */
    mod_timer(&dev->tick_timer, jiffies + msecs_to_jiffies(1000));
}

static int my_probe(struct platform_device *pdev)
{
    struct my_dev *dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);

    /* 初始化定时器(4.15+ 替代了旧的 setup_timer) */
    timer_setup(&dev->tick_timer, tick_callback, 0);

    /* 首次启动:mod_timer 同时完成 add + 设定expires */
    mod_timer(&dev->tick_timer, jiffies + msecs_to_jiffies(1000));

    return 0;
}

static void my_remove(struct platform_device *pdev)
{
    struct my_dev *dev = platform_get_drvdata(pdev);

    /* 同步删除:等待回调执行完毕再返回 */
    del_timer_sync(&dev->tick_timer);
}

2.3 API速查

API 功能 返回值
timer_setup(timer, cb, flags) 初始化定时器并绑定回调 无
add_timer(timer) 添加定时器到时间轮 无
mod_timer(timer, expires) 修改/激活定时器 1=原pending,0=原不pending
del_timer(timer) 删除定时器(不等回调完成) 1=已删除,0=未pending
del_timer_sync(timer) 同步删除(等回调完成) 同del_timer
timer_pending(timer) 检查定时器是否pending 布尔值

注意:del_timer_sync() 会睡眠,不能在中断上下文调用。6.2+ 内核新增 timer_shutdown_sync(),删除后标记定时器为永久停用,后续 mod_timer() 静默忽略,适合驱动卸载场景。

三、高精度定时器(hrtimer)

hrtimer基于每CPU红黑树实现,精度可达纳秒级,直接编程硬件clockevent设备。适合音频处理、网络 pacing、实时控制等低延迟场景。

3.1 数据结构

struct hrtimer {
    struct timerqueue_node  node;          /* 红黑树节点 + 到期时间 */
    ktime_t                 _softexpires;  /* 最早到期时间 */
    enum hrtimer_restart (*function)(struct hrtimer *); /* 回调 */
    struct hrtimer_clock_base *base;       /* 所属时钟基 */
    u8                      state;         /* INACTIVE / ENQUEUED */
    u8                      is_rel;        /* 是否相对定时器 */
    u8                      is_soft;       /* 是否软中断投递 */
    u8                      is_hard;       /* 是否硬中断投递 */
};

3.2 API用法

#include <linux/hrtimer.h>
#include <linux/ktime.h>

struct my_dev {
    struct hrtimer poll_timer;
};

static enum hrtimer_restart poll_callback(struct hrtimer *timer)
{
    struct my_dev *dev = container_of(timer, struct my_dev, poll_timer);

    /* 执行业务逻辑 */
    do_poll(dev);

    /* 前进到下一个到期时间,实现周期触发 */
    hrtimer_forward_now(timer, ms_to_ktime(10));
    return HRTIMER_RESTART;   /* 重新调度 */

    /* 若只需单次触发,返回 HRTIMER_NORESTART */
}

static int my_probe(struct platform_device *pdev)
{
    struct my_dev *dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);

    /* 传统初始化方式(6.14之前) */
    hrtimer_init(&dev->poll_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    dev->poll_timer.function = poll_callback;

    /* 新版初始化方式(6.14+,推荐) */
    /* hrtimer_setup(&dev->poll_timer, poll_callback,
                      CLOCK_MONOTONIC, HRTIMER_MODE_REL); */

    /* 启动:10ms后触发 */
    hrtimer_start(&dev->poll_timer, ms_to_ktime(10), HRTIMER_MODE_REL);

    return 0;
}

static void my_remove(struct platform_device *pdev)
{
    struct my_dev *dev = platform_get_drvdata(pdev);
    hrtimer_cancel(&dev->poll_timer);  /* 取消并等待回调完成 */
}

3.3 定时器模式

模式 含义 典型场景
HRTIMER_MODE_ABS 绝对时间触发 定时闹钟
HRTIMER_MODE_REL 相对当前时间触发 周期轮询
HRTIMER_MODE_PINNED 绑定当前CPU 避免迁移开销
HRTIMER_MODE_SOFT 软中断上下文执行(4.16+) 回调可稍延迟
HRTIMER_MODE_HARD 硬中断上下文执行 最低延迟需求

模式可组合使用,如 HRTIMER_MODE_REL_SOFT 表示相对时间 + 软中断投递。

3.4 时钟基选择

时钟基 含义 说明
CLOCK_MONOTONIC 单调递增时钟 不受NTP调整影响,最常用
CLOCK_REALTIME 系统实时时钟 受settimeofday影响
CLOCK_BOOTTIME 含休眠的单调时钟 休眠期间仍计时
CLOCK_TAI 国际原子时 不含闰秒

约束:hrtimer回调默认在硬中断上下文执行,不能睡眠。如需在回调中执行可能睡眠的操作,使用 HRTIMER_MODE_SOFT(4.16+),回调在软中断上下文执行,但仍不能睡眠。如需睡眠,应结合workqueue使用。

四、延迟工作队列(delayed_work)

delayed_work是timer_list + workqueue的组合封装:到期后由定时器触发,将work投递到kworker线程执行。回调运行在进程上下文,可以睡眠,这是它和裸timer_list/hrtimer的根本区别。

4.1 数据结构

struct delayed_work {
    struct work_struct      work;   /* 工作项(最终由kworker执行) */
    struct timer_list       timer;  /* 内部定时器(仅负责延时投递) */
    struct workqueue_struct *wq;    /* 目标工作队列 */
    int                     cpu;    /* 目标CPU */
};

4.2 API用法

#include <linux/workqueue.h>

struct my_dev {
    int count;
    struct delayed_work dwork;
};

static void dwork_handler(struct work_struct *work)
{
    struct my_dev *dev = container_of(work, struct my_dev, dwork.work);

    dev->count++;
    printk("delayed work #%d\n", dev->count);

    /* 周期执行:在工作函数中重新调度 */
    schedule_delayed_work(&dev->dwork, msecs_to_jiffies(500));
}

static int my_init(void)
{
    struct my_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);

    /* 初始化delayed_work并绑定回调 */
    INIT_DELAYED_WORK(&dev->dwork, dwork_handler);

    /* 首次调度:500ms后执行 */
    schedule_delayed_work(&dev->dwork, msecs_to_jiffies(500));

    return 0;
}

static void my_exit(void)
{
    /* 同步取消:等待正在执行的回调完成 */
    cancel_delayed_work_sync(&dev->dwork);
    kfree(dev);
}

4.3 API速查

API 功能 上下文限制
INIT_DELAYED_WORK(dwork, fn) 初始化并绑定回调 任意
DECLARE_DELAYED_WORK(name, fn) 静态声明+初始化 任意
schedule_delayed_work(dwork, delay) 调度到system_wq 任意
queue_delayed_work(wq, dwork, delay) 调度到指定wq 任意
cancel_delayed_work(dwork) 异步取消 任意(含中断)
cancel_delayed_work_sync(dwork) 同步取消 进程上下文

注意:schedule_delayed_work() 的delay参数单位是jiffies,用 msecs_to_jiffies() 转换。若delay为0,立即投递到workqueue异步执行。

五、短延迟函数

当需要在代码中短暂等待时,内核提供两类延迟机制:忙等待和睡眠等待。

来自 linuxros.cn · linuxROS

5.1 忙等待类(占用CPU)

#include <linux/delay.h>

udelay(us);   /* 微秒级忙等待,基于BogoMIPS校准 */
ndelay(ns);   /* 纳秒级忙等待,精度有限 */
mdelay(ms);   /* 毫秒级忙等待,本质是udelay循环 */
  • udelay() 基于启动时校准的BogoMIPS值做忙循环,精度受CPU频率和缓存影响
  • 仅用于微秒级短延迟,超过1ms应改用睡眠方式
  • 可在中断上下文使用(不睡眠)

5.2 睡眠类(释放CPU)

#include <linux/delay.h>

msleep(ms);                  /* 不可中断的毫秒级睡眠 */
msleep_interruptible(ms);    /* 可中断的毫秒级睡眠,返回剩余ms */
ssleep(sec);                 /* 秒级睡眠(不可中断) */
  • msleep() 基于 schedule_timeout_uninterruptible() 实现
  • 不能在中断上下文使用(会睡眠)
  • 实际睡眠时间可能略长于请求值

5.3 推荐选择

#include <linux/delay.h>

/* 5.10+ 统一延迟API,自动选择最优实现 */
fsleep(us);  /* 内部自动判断:短延迟用udelay,长延迟用msleep */

六、对比表格

维度 timer_list hrtimer delayed_work
精度 ~1/HZ(1~10ms) 纳秒级 ~1/HZ(1~10ms)
数据结构 分层时间轮 红黑树 时间轮 + 工作队列
插入复杂度 O(1) O(log n) O(1)
回调上下文 软中断 硬中断/软中断 进程(kworker线程)
回调能否睡眠 否 否 是
时间单位 jiffies ktime_t(纳秒) jiffies
周期重调度 mod_timer hrtimer_forward_now + RESTART schedule_delayed_work
典型场景 超时检测、看门狗 音频、实时控制 按键消抖、延时重试
适用内核版本 全版本 2.6.16+ 全版本

七、流程图

7.1 定时机制选择决策树

flowchart TB A(["需要定时/延迟"]) --> B{"回调需要睡眠?"} B -->|"是"| C["delayed_work"] B -->|"否"| D{"精度要求?"} D -->|"纳秒级"| E{"回调延迟敏感?"} D -->|"毫秒级足够"| F{"大概率被取消?"} E -->|"是"| G["hrtimer<br/>HRTIMER_MODE_HARD"] E -->|"否"| H["hrtimer<br/>HRTIMER_MODE_SOFT"] F -->|"是"| I["timer_list<br/>O(1)插入/删除"] F -->|"否"| J["timer_list<br/>通用场景"] C --> K(["完成选型"]) G --> K H --> K I --> K J --> K

7.2 hrtimer工作流程

flowchart TB A(["hrtimer_start()"]) --> B["计算到期ktime"] B --> C["插入红黑树"] C --> D{"是否最早到期?"} D -->|"是"| E["重编程clockevent"] D -->|"否"| F["等待到期"] E --> F F --> G["硬件中断触发"] G --> H{"回调模式?"} H -->|"HARD"| I["硬中断上下文<br/>执行回调"] H -->|"SOFT"| J["标记软中断待处理"] J --> K["软中断上下文<br/>执行回调"] I --> L{"返回值?"} K --> L L -->|"RESTART"| M["hrtimer_forward_now()<br/>重新插入红黑树"] L -->|"NORESTART"| N(["定时器停止"]) M --> C

7.3 短延迟函数选择流程

flowchart TB A(["需要短延迟"]) --> B{"当前上下文?"} B -->|"中断上下文"| C{"延迟时长?"} B -->|"进程上下文"| D{"延迟时长?"} C -->|"≤几微秒"| E["udelay()"] C -->|">1ms"| F["设计有问题<br/>中断中不应长等待"] D -->|"≤几微秒"| G["udelay()"] D -->|"微秒~毫秒"| H["usleep_range()"] D -->|"≥1ms"| I["msleep()"] E --> J(["延迟完成"]) F --> J G --> J H --> J I --> J

八、常见问题解决

Q1:del_timer_sync() 调用时内核崩溃?

del_timer_sync() 内部会睡眠等待回调完成,不能在中断上下文或持有自旋锁时调用。中断上下文应使用 del_timer(),但要注意回调可能仍在其他CPU上运行,存在use-after-free风险。

Q2:timer_setup() 和旧版 setup_timer() 的区别?

4.15起 timer_setup() 替代了 setup_timer()。变化:回调签名从 void (*func)(unsigned long data) 变为 void (*func)(struct timer_list *t),传参方式从 data 字段改为 from_timer() 宏反查父结构体。

Q3:hrtimer回调中能调用msleep吗?

不能。hrtimer回调默认在硬中断上下文执行,即使使用 HRTIMER_MODE_SOFT 也是软中断上下文,都不能睡眠。如需睡眠操作,应在回调中唤醒workqueue,在进程上下文中执行。

Q4:schedule_delayed_work的delay为0会怎样?

delay为0时,work立即投递到workqueue异步执行(不等当前函数返回)。这等价于 schedule_work(&dwork->work)。

Q5:udelay() 精度不准怎么办?

udelay() 基于BogoMIPS校准的忙循环,受CPU频率缩放和缓存影响。精度要求高的场景应使用hrtimer,或确保CPU频率锁定(禁用节能模式)。

Q6:6.2+内核应该用什么替代del_timer_sync?

6.2+ 新增 timer_shutdown_sync(),删除定时器后标记为永久停用,后续 mod_timer() 调用被静默忽略。驱动卸载时推荐使用,防止卸载后定时器被重新激活。5.x内核仍用 del_timer_sync()。

九、总结

内核定时与延迟机制的选择,看三个维度:精度需求、回调上下文限制、操作复杂度。毫秒级超时用timer_list,纳秒级实时用hrtimer,回调需睡眠用delayed_work,微秒忙等待用udelay。记住一条红线:中断上下文不能睡眠,del_timer_sync不能在中断中调用。下期拆解内核中断下半部机制。

版权声明

作者linuxROS
协议本作品采用 CC BY-NC-SA 4.0 许可协议:署名-非商业性使用-相同方式共享
关注欢迎关注微信公众号 linuxROS,获取更多机器人 / 嵌入式 / Linux 干货
返回首页