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

Linux内核定时器与延迟工作:四种机制选型与实战

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

Linux内核定时器与延迟工作:四种机制选型与实。

内核驱动开发中,过一会儿再做是最常见的需求之一:轮询传感器数据、超时重传、周期性心跳、延迟初始化——每种场景对精度、上下文、CPU占用的要求不同,Linux提供了timer_list、hrtimer、delayed_work、短延迟函数四种机制。本文基于Linux 6.8内核,逐一拆解原理和API,给出可编译运行的完整示例,最后用决策流程图帮你快速选型。

一、内核时间概。

1.1 jiffies:内核的时钟心跳

jiffies是内核全局变量,每发生一次时钟中断就。。时钟中断频率由HZ宏决定,编译内核时配置:

HZ 中断周期 典型场景
100 10ms 嵌入式低功耗设备
250 4ms Ubuntu 24.04默认
1000 1ms 桌面/服务器低延迟
#include <linux/jiffies.h>

unsigned long timeout = jiffies + 5 * HZ;  // 5秒后的jiffies```

`HZ`是个频率值,`5 * HZ`就是5秒对应的滴答数,用jiffies做超时判断时*绝对不能直接用`>`或`<`比较**——jiffies会回绕(32位系统约49天回绕一次),必须用内核提供的宏。
### 1.2 安全比较。
```c
#include <linux/jiffies.h>

// time_after(a, b):a在b之后为真(处理回绕)
if (time_after(jiffies, timeout)) {
    // 超时。}

// time_before(a, b):a在b之前为真
if (time_before(jiffies, deadline)) {
    // 还没到截止时。}

// time_in_range(a, b, c):a在[b, c]区间内为真
if (time_in_range(jiffies, start, end)) {
    // 在时间窗口内
}

这三个宏内部处理了回绕问题,不管jiffies是否溢出都能正确判断。

1.3 jiffies_64

64位系统上jiffies仍是unsigned long。4位),但32位系统上只有32位,需64位时用get_jiffies_64()。

#include <linux/jiffies.h>

u64 j64 = get_jiffies_64();

直接读jiffies_64变量不是原子操作集2位系统上),必须通过get_jiffies_64()读取。

1.4 ktime_t:纳秒级时间

ktime_t是内核高精度时间子系统的时间表示,内部是64位纳秒值:

#include <linux/ktime.h>

ktime_t kt = ktime_set(1, 500000000);  // 1。+ 500毫秒 = 1.5。ktime_t kt_ns = ns_to_ktime(1000000);  // 1毫秒 = 1000000纳秒

s64 ns = ktime_to_ns(kt);       // 转纳秒
s64 us = ktime_to_us(kt);       // 转微秒
s64 ms = ktime_to_ms(kt);       // 转毫秒

ktime_t是hrtimer的基础,也是内核时间子系统的核心数据类型。

二、低精度定时器(timer_list)

2.1 timer_list结构建

struct timer_list {
    struct hlist_node   entry;          // 内核链表节点
    unsigned long       expires;        // 到期jiffies。    void                (*function)(struct timer_list *t);  // 回调函数
    u32                 flags;          // 标志。    // ... 内核内部字段
};

6.8内核中timer_list的回调函数签名是void (*function)(struct timer_list *t),从t可以用from_timer宏反推出自定义结构体。

2.2 timer_setup:初始化定时。

Linux 6.8已经移除了旧版setup_timer()和init_timer(),统一用timer_setup()。

// 6.8 API:初始化定时器并绑定回调
void timer_setup(struct timer_list *timer,
                 void (*callback)(struct timer_list *t),
                 unsigned int flags);

2.3 核心API

API 功能 说明
timer_setup(timer, cb, flags) 初始化定时器 绑定回调函数,flags一般填0
mod_timer(timer, expires) 修改/激活定时器 定时器未激活则激活,已激活则修改到期时间
del_timer(timer) 删除定时器 不等待回调执行完毕,可能正在其他CPU上跑
del_timer_sync(timer) 同步删除定时器 等待回调执行完毕再返回,多CPU安全

2.4 完整示例:周期性定时器

// timer_periodic.c - 周期性定时器示例(Linux 6.8验证:#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>

#define TIMER_INTERVAL_MS  1000  // 1秒周。
struct my_timer_dev {
    struct timer_list timer;
    int count;
};

static struct my_timer_dev tdev;

/* 定时器回调函数- 运行在中断上下文件*/
static void timer_callback(struct timer_list *t)
{
    struct my_timer_dev *dev = from_timer(dev, t, timer);

    dev->count++;
    pr_info("timer: tick #%d\n", dev->count);

    /* 重新设置到期时间,实现周期触*/
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(TIMER_INTERVAL_MS));
}

static int __init timer_init(void)
{
    tdev.count = 0;

    /* 初始化定时器,绑定回调*/
    timer_setup(&tdev.timer, timer_callback, 0);

    /* 设置首次到期时间并激*/
    mod_timer(&tdev.timer, jiffies + msecs_to_jiffies(TIMER_INTERVAL_MS));

    pr_info("timer: periodic timer started, interval=%dms\n", TIMER_INTERVAL_MS);
    return 0;
}

static void __exit timer_exit(void)
{
    /* 同步删除:等待可能正在执行的回调完成 */
    del_timer_sync(&tdev.timer);
    pr_info("timer: stopped after %d ticks\n", tdev.count);
}

module_init(timer_init);
module_exit(timer_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros");
MODULE_DESCRIPTION("Periodic timer_list example");

Makefile。

obj-m += timer_periodic.o

KDIR := /lib/modules/$(shell uname -r)/build

all:
    make -C $(KDIR) M=$(PWD) modules

clean:
    make -C $(KDIR) M=$(PWD) clean

测试。

make && sudo insmod timer_periodic.ko
dmesg | tail -10
# timer: periodic timer started, interval=1000ms
# timer: tick #1
# timer: tick #2
# ...
sudo rmmod timer_periodic

2.5 关键约束:中断上下文

timer_list的回调运行在中断上下*(准确说是软中断上下文),这意味着。
-
禁止睡眠:不能调用msleep()、schedule()、mutex_lock()等可能阻塞的函数
-
禁止访问用户空间:没有进程上下文,copy_to_user/copy_from_user不可。- 执行要快**:长时间占用会影响整个系统的软中断处。
需要睡眠或长时间处理?用第四节介绍的delayed_work。

三、高精度定时器(hrtimer)

3.1 为什么需要hrtimer

timer_list的精度受限于HZ,HZ=250时最小粒度4ms。很多场景需要微秒甚至纳秒级精度:音频采样、硬件PWM、精确计时。hrtimer基于高精度时钟源(TSC/HPET等),不依赖jiffies,精度可达纳秒级别

3.2 hrtimer结构体与初始化

struct hrtimer {
    struct timerqueue_node      node;       // 红黑树节。    ktime_t                     _softexpires;  // 软到期时。    enum hrtimer_restart        (*function)(struct hrtimer *t);  // 回调
    struct hrtimer_clock_base   *base;      // 时钟。    u8                          state;      // 状态    // ...
};
#include <linux/hrtimer.h>

void hrtimer_init(struct hrtimer *timer, clockid_t clock_id,
                  enum hrtimer_mode mode);
  • clock_id:时钟源,一般用CLOCK_MONOTONIC(单调递增,不受系统时间调整影响)
  • mode:HRTIMER_MODE_REL(相对时间)或HRTIMER_MODE_ABS(绝对时间)

3.3 核心API

API 功能
hrtimer_init(timer, clock, mode) 初始化hrtimer
hrtimer_start(timer, time, mode) 启动定时器
hrtimer_cancel(timer) 取消定时器(等待回调完成)
hrtimer_forward(timer, now, interval) 推进到下一个到期时间

3.4 回调函数返回调

enum hrtimer_restart my_callback(struct hrtimer *t)
{
    // 处理逻辑...

    return HRTIMER_RESTART;    // 重新启动,周期定。    // return HRTIMER_NORESTART;  // 不重启,一次性定。}

周期性hrtimer的标准写法:

来自 linuxros.cn · linuxROS
static enum hrtimer_restart my_hrtimer_callback(struct hrtimer *t)
{
    struct my_dev *dev = container_of(t, struct my_dev, htimer);

    /* 业务处理 */

    /* 推进到下一个周*/
    hrtimer_forward_now(t, ms_to_ktime(1));  // 1ms周期
    return HRTIMER_RESTART;
}

3.5 完整示例:1ms精度周期定时器

// hrtimer_periodic.c - 高精度1ms周期定时器(Linux 6.8验证)#include <linux/module.h>
#include <linux/init.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>

#define PERIOD_MS  1  // 1ms周期

struct hrtimer_dev {
    struct hrtimer htimer;
    ktime_t period;
    int count;
};

static struct hrtimer_dev hdev;

static enum hrtimer_restart hrtimer_callback(struct hrtimer *t)
{
    struct hrtimer_dev *dev = container_of(t, struct hrtimer_dev, htimer);

    dev->count++;

    /* 推进到下一个周期点 */
    hrtimer_forward(t, hrtimer_cb_get_time(t), dev->period);
    return HRTIMER_RESTART;
}

static int __init hrtimer_init_mod(void)
{
    hdev.count = 0;
    hdev.period = ms_to_ktime(PERIOD_MS);

    /* 初始化:单调时钟,相对模块*/
    hrtimer_init(&hdev.htimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    hdev.htimer.function = hrtimer_callback;

    /* 启动定时*/
    hrtimer_start(&hdev.htimer, hdev.period, HRTIMER_MODE_REL);

    pr_info("hrtimer: started, period=%dms\n", PERIOD_MS);
    return 0;
}

static void __exit hrtimer_exit_mod(void)
{
    /* 取消定时器,等待回调完成 */
    hrtimer_cancel(&hdev.htimer);
    pr_info("hrtimer: stopped after %d ticks\n", hdev.count);
}

module_init(hrtimer_init_mod);
module_exit(hrtimer_exit_mod);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros");
MODULE_DESCRIPTION("High-resolution periodic timer example");

3.6 hrtimer vs timer_list对比

对比 timer_list hrtimer
精度 jiffies级别。-10ms 纳秒
底层实现 轮转桶(wheel) 红黑树
回调上下文 软中断 硬中断(可配置软中断号
周期重置 手动mod_timer HRTIMER_RESTART + hrtimer_forward
内核配置 默认启用 需CONFIG_HIGH_RES_TIMERS
适用场景 秒级超时、低精度轮询 精确计时、音频、PWM
CPU开销 略高(红黑树插入)

四、延迟工作队。

4.1 delayed_work结构建

struct delayed_work {
    struct work_struct work;     // 实际工作者    struct timer_list timer;     // 内部定时器(驱动延迟。    struct workqueue_struct *wq; // 所属工作队。    int cpu;                     // 目标CPU
};

delayed_work本质上是timer_list + work_struct的组合:到期后由内部定时器触发,把work_struct提交到工作队列执行关键区别:回调运行在进程上下文。

4.2 核心API

API 功能
INIT_DELAYED_WORK(dw, fn) 初始化delayed_work
schedule_delayed_work(dw, delay) 调度延迟执行,delay是jiffies
cancel_delayed_work_sync(dw) 同步取消,等待执行完成
flush_scheduled_work() 等待所有scheduled work完成

4.3 完整示例:延迟N秒后执行任务

// delayed_work_demo.c - 延迟工作队列示例(Linux 6.8验证:#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>
#include <linux/slab.h>

struct dw_dev {
    struct delayed_work dwork;
    int data;
};

static struct dw_dev *dwdev;

/* 工作函数 - 运行在进程上下文,可以睡眠! */
static void work_handler(struct work_struct *work)
{
    struct dw_dev *dev = container_of(work, struct dw_dev, dwork.work);

    dev->data++;
    pr_info("delayed_work: executed, data=%d, pid=%d, comm=%s\n",
            dev->data, current->pid, current->comm);

    /* 进程上下文:可以睡眠、可以访问用户空间、可以持*/
    msleep(100);  // 这里睡眠完全没问题    pr_info("delayed_work: after sleep, data=%d\n", dev->data);
}

static int __init dw_init(void)
{
    dwdev = kzalloc(sizeof(*dwdev), GFP_KERNEL);
    if (!dwdev)
        return -ENOMEM;

    dwdev->data = 0;

    /* 初始化延迟工*/
    INIT_DELAYED_WORK(&dwdev->dwork, work_handler);

    /* 调度5秒后执行 */
    schedule_delayed_work(&dwdev->dwork, 5 * HZ);

    pr_info("delayed_work: scheduled, will run in 5 seconds\n");
    return 0;
}

static void __exit dw_exit(void)
{
    /* 同步取消:如果工作正在执行,等待完成 */
    cancel_delayed_work_sync(&dwdev->dwork);
    kfree(dwdev);
    pr_info("delayed_work: module unloaded\n");
}

module_init(dw_init);
module_exit(dw_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros");
MODULE_DESCRIPTION("Delayed workqueue example");

4.4 delayed_work vs timer_list适用场景

场景 timer_list delayed_work
简单超时检查 ,轻量 ,杀鸡用牛刀
回调中需要睡眠 ,死锁 ,进程上下文
回调中访问用户空 。无进程上下文 。可以
回调中持mutex 。可能睡眠 。允许
需要高精度 。jiffies 。也是jiffies
周期性任务(轻量 。简 。也可
周期性任务(重活 。不能耗时 ,进程上下文

简单判断:回调里干轻活用timer_list,回调里干重活或需要睡眠用delayed_work。

五、短延迟函数

有些场景不需要定时器,只需要等一小会。内核提供了几组短延迟函数。

5.1 忙等待延迟:ndelay/udelay/mdelay

#include <linux/delay.h>

ndelay(100);    // 纳秒级延迟,忙等
udelay(100);    // 微秒级延迟,忙等
mdelay(10);     // 毫秒级延迟,忙等

这三个函数的实现方式*忙等待(busy-wait):CPU死循环数圈,不释放CPU。精度高,但浪费CPU时间。udelay和ndelay用于微秒/纳秒级短延迟(比如硬件复位时序),mdelay一般不推荐——毫秒级延迟应该用睡眠方式。

5.2 睡眠延迟:usleep_range

#include <linux/delay.h>

usleep_range(1000, 2000);  // 睡眠1ms~2ms

usleep_range让出CPU,进程进入睡眠状态。内核会在这个范围内选择合适的唤醒时机,有利于合并定时器中断、省电。min和max越接近,延迟越精确;范围越大,省电效果越好。

5.3 智能延迟:fsleep

Linux 6.8推荐的短延迟函数,根据延迟时长自动选择最优策略:

#include <linux/delay.h>

fsleep(100);       // 100纳秒 → 忙等
fsleep(10000);     // 10微秒 → 忙等
fsleep(2000000);   // 2毫秒 → 睡眠

fsleep内部逻辑:短延迟用udelay,长延迟用usleep_range,不需要开发者自己判断。

5.4 短延迟函数对。

函数 精度范围 上下 CPU占用 适用场景
ndelay(n) 纳秒 任意 100% 硬件时序(极少用途
udelay(n) 微秒 任意 100% 硬件复位、总线时序
mdelay(n) 毫秒 任意 100% 极少推荐,改用usleep_range
usleep_range(min, max) 微秒~毫秒 进程 0% 毫秒级延迟、省电场
fsleep(us) 纳秒~毫秒 进程 自动 *6.8推荐,通用短延

注意:udelay/ndelay可以在中断上下文使用(不睡眠),usleep_range/fsleep只能在进程上下文使用。

六、定时器选型指南

6.1 决策流程。

flowchart TB A(["需要定时或延迟"]) --> B{"需要执行回调?"} B -->|"否,只需等一会儿"| C{"延迟时长。} B -->|"是,到期后执行逻辑"| D{"回调中需要睡<br/>或长时间处理。} C -->|"纳秒~微秒"| E["udelay / ndelay<br/>忙等待,任意上下文"] C -->|"微秒~毫秒"| F{"当前上下文?"} F -->|"中断上下文 | G["udelay<br/>忙等"] F -->|"进程上下文 | H["fsleep<br/>智能选择"] D -->|"| I["delayed_work<br/>进程上下文,可睡眠"] D -->|"| J{"精度要求。} J -->|"毫秒级够 | K["timer_list<br/>轻量,jiffies精度"] J -->|"微秒/纳秒 | L["hrtimer<br/>高精度,纳秒"] style A fill:#E3F2FD style I fill:#E8F5E9 style K fill:#E3F2FD style L fill:#F3E5F5 style E fill:#FFF8E1 style G fill:#FFF8E1 style H fill:#E8F5E9 style F fill:#FFF8E1 style J fill:#FFF8E1 style B fill:#FFF8E1 style C fill:#FFF8E1 style D fill:#FFF8E1

6.2 四种机制对比总表

维度 timer_list hrtimer delayed_work 短延迟函数
精度 jiffies。-10ms 纳秒 jiffies。-10ms 纳秒~毫秒
上下 软中 硬中 进程 任意/进程
可睡眠 看函数
周期 手动mod_timer HRTIMER_RESTART 手动重新调度 。一次
CPU开销 忙等。
内核配置 默认启用 CONFIG_HIGH_RES_TIMERS 默认启用 默认启用
典型用途 超时检测、心 精确计时、PWM 延迟初始化、重启 硬件时序、短暂等

七、常见问题

Q1:timer回调里调了msleep会怎样。

死锁或内核崩溃。timer_list回调运行在软中断上下文,msleep()会让当前"进程"睡眠——但软中断上下文没有进程,调度器无法处理。解决方案:改用delayed_work,它运行在进程上下文,可以安全睡眠。

Q2:hrtimer精度达不到预期怎么办?

检查内核配置:

grep CONFIG_HIGH_RES_TIMERS /boot/config-$(uname -r)

必须为CONFIG_HIGH_RES_TIMERS=y。如果没开,hrtimer会退化为jiffies精度。另外,hrtimer回调虽然精度高,但实际触发时间受中断延迟影响,回调里不要做耗时操作。集

Q3:del_timer和del_timer_sync怎么选?

单CPU环境下两者等价。SMP多核环境下,del_timer只从定时器链表中移除,不等待正在其他CPU上执行的回调——回调可能还在跑。del_timer_sync会等待回调执行完毕再返回。模块卸载时必须用del_timer_sync,否则回调可能访问已释放的数据导致崩溃。注意:del_timer_sync不能在定时器回调内部调用(会死锁),回调里用del_timer。

八、总结

速查看

需 选用 一句话理由
毫秒级超时检 timer_list 轻量够用
微秒/纳秒级精确计数 hrtimer 唯一选择
回调需要睡眠或干重启 delayed_work 进程上下文
硬件时序短等待(中断上下文) udelay 忙等待,不睡
通用短延迟(进程上下文) fsleep 6.8推荐,自动选策
周期性轻量任 timer_list + mod_timer 简单直
周期性重活任 delayed_work + 重新调度 可睡眠

记住一条主线:中断上下文不能睡眠。timer_list和hrtimer的回调都在中断上下文,delayed_work在进程上下文。选型的核心就是搞清楚你的回调里要干什么。

本文首发于linuxros.cn,转载请注明出处。

版权声明

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