4. 初始化定时器:timer_setup() 与 DEFINE_TIMER 宏的使用与区别

在 Linux 内核中,初始化一个低精度定时器(基于 timer_list 结构体)有两种标准方式:静态初始化(编译时)使用 DEFINE_TIMER 宏,动态初始化(运行时)使用 timer_setup() 函数。两者最终都完成对 timer_list 结构体的字段填充,但在使用场景、灵活性以及内核版本兼容性上存在显著差异。

内核版本说明:自 Linux 4.15 起,内核引入了 timer_setup() 和 from_timer() 机制,逐步淘汰了旧的 setup_timer() 和 init_timer() API。本课程基于 4.15+ 内核,推荐使用 timer_setup()。

4.1 DEFINE_TIMER 宏:编译时静态初始化

DEFINE_TIMER 是一个宏,用于在编译阶段定义并初始化一个 timer_list 变量。它适用于定时器数量固定、全局或静态作用域的场景。

原型定义:

#define DEFINE_TIMER(_name, _function)                \
    struct timer_list _name =                         \
        __TIMER_INITIALIZER(_function, 0)

使用示例:

#include <linux/timer.h>

// 定时器回调函数
void my_timer_callback(struct timer_list *t)
{
    pr_info("定时器到期!\n");
}

// 静态定义并初始化定时器
DEFINE_TIMER(my_timer, my_timer_callback);

static int __init my_init(void)
{
    // 设置定时器超时值为 1 秒后
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
    return 0;
}

特点与限制:

  • 编译时分配:定时器结构体在编译阶段即被分配在数据段或 BSS 段。
  • 无法指定数据指针:旧版 DEFINE_TIMER 不提供设置 .data 或私有数据的接口;回调函数只能通过 container_of 或全局变量访问上下文。
  • 作用域受限:通常用于模块全局或文件静态作用域,不适合动态分配的定时器。
  • 简洁性:一行代码完成定义与初始化,代码量少。

4.2 timer_setup() 函数:运行时动态初始化

timer_setup() 是内核提供的内联函数,用于在运行时初始化一个已经分配(静态或动态)的 timer_list 结构体。它支持将驱动私有数据直接传递给回调函数,是当前内核推荐的方式。

原型定义:

static inline void timer_setup(struct timer_list *timer,
                               void (*callback)(struct timer_list *),
                               unsigned int flags)

使用示例:

#include <linux/timer.h>
#include <linux/slab.h>

struct my_device {
    struct timer_list timer;
    int data;
};

void my_timer_callback(struct timer_list *t)
{
    struct my_device *dev = from_timer(dev, t, timer);
    pr_info("定时器到期, data = %d\n", dev->data);
}

static int __init my_init(void)
{
    struct my_device *dev;

    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

    dev->data = 42;

    // 动态初始化定时器,并将 dev 作为上下文
    timer_setup(&dev->timer, my_timer_callback, 0);

    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(1000));
    return 0;
}

特点与优势:

  • 运行时灵活:可以在任意上下文(如 probe 函数)中初始化定时器。
  • 支持私有数据传递:配合 from_timer() 宏,回调函数可以直接获取包含定时器的父结构体指针,避免全局变量。
  • 兼容动态分配:适用于 kmalloc、devm_kzalloc 等动态内存中的定时器。
  • flags 参数:当前内核保留,通常传入 0;未来可用于定时器行为修饰。

4.3 核心区别对比

对比维度 DEFINE_TIMER timer_setup()
初始化时机 编译时(静态) 运行时(动态)
适用作用域 全局 / 文件静态 任意(全局、局部、动态分配)
私有数据传递 不直接支持(需全局变量或 container_of) 通过 from_timer() 原生支持
代码简洁性 一行完成定义+初始化 需先分配结构体,再调用函数
内核版本要求 所有版本 4.15+(推荐)
典型使用场景 模块级固定定时器 设备驱动 probe 中的定时器

4.4 初始化流程对比图

下图展示了两种初始化方式在代码执行路径上的差异:

定时器初始化流程对比:DEFINE_TIMER vs timer_setup() DEFINE_TIMER(my_timer, cb) 编译时分配 timer_list 到数据段 回调函数固定,无法携带私有数据 直接调用 mod_timer() 启动 timer_setup(&timer, cb, 0) 运行时填充 timer_list 字段 (回调、flags、内部链表) 通过 from_timer() 获取父结构体 (支持私有数据传递) 调用 mod_timer() 启动

4.5 回调函数中获取私有数据:from_timer() 宏

使用 timer_setup() 时,回调函数接收的是 struct timer_list * 指针。要获取包含该定时器的父结构体,必须使用 from_timer() 宏,它本质上是 container_of() 的封装。

宏定义:

#define from_timer(var, callback_timer, timer_field) \
    container_of(callback_timer, typeof(*(var)), timer_field)

使用模式:

struct my_driver {
    struct timer_list gc_timer;
    spinlock_t lock;
    int pending_ops;
};

void gc_timer_callback(struct timer_list *t)
{
    struct my_driver *drv = from_timer(drv, t, gc_timer);
    unsigned long flags;

    spin_lock_irqsave(&drv->lock, flags);
    // 访问 drv->pending_ops 等字段
    spin_unlock_irqrestore(&drv->lock, flags);

    // 重新设置定时器(如果需要周期性执行)
    mod_timer(&drv->gc_timer, jiffies + HZ);
}
最佳实践:在设备驱动中,始终将 timer_list 嵌入到设备结构体中,并使用 timer_setup() + from_timer() 组合。这避免了全局变量,使代码更模块化、可测试。

4.6 选择指南

  • 选择 DEFINE_TIMER:当定时器是模块全局的、不需要携带私有数据、且数量固定时。例如内核中的周期性的全局统计定时器。
  • 选择 timer_setup():在绝大多数设备驱动场景中,定时器与特定设备实例绑定,需要访问设备私有数据。这是 4.15+ 内核的推荐做法。
  • 避免混用:不要对同一个定时器同时使用两种初始化方式,会导致未定义行为。
注意:旧版内核中的 setup_timer() 和 init_timer() 已在 4.15+ 中被标记为废弃。如果你的代码需要向后兼容旧内核,请使用条件编译;否则请统一使用 timer_setup()。