Linux 内核提供了两种主要类型的定时器,以满足不同的时间精度需求:低精度定时器 (timer_list
) 和 高精度定时器 (hrtimer
)。它们各有特点和适用场景。下面,我将分别提供它们在内核代码中的简化使用示例。
1. 低精度定时器 (timer_list
) 示例
timer_list
是最基本的定时器,精度依赖于系统时钟节拍(jiffies),通常是毫秒级。它适用于对时间精度要求不高,允许一定误差的周期性或延迟任务。
特点:
精度:毫秒级(受
HZ
限制)。开销:较小。
用途:简单的后台任务、超时检测、不频繁的周期性事件。
使用步骤:
定义
struct timer_list
变量。初始化
timer_list
: 使用timer_setup()
或init_timer()
(较旧)指定回调函数。设置定时器: 使用
mod_timer()
设定到期时间。删除定时器: 使用
del_timer()
或del_timer_sync()
(等待回调完成)在不再需要时清理。
代码示例:
这是一个简单的内核模块,它会设置一个定时器,在 5 秒后首次触发,然后每隔 3 秒周期性地打印一条消息。
C
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/jiffies.h> // 用于 jiffies 变量// 1. 定义一个 timer_list 结构体变量
static struct timer_list my_low_res_timer;
static int counter = 0;// 2. 定义定时器到期时要执行的回调函数
void low_res_timer_callback(struct timer_list *t)
{// 获取 timer_list 结构体变量的指针// (如果 timer_setup() 的 data 参数不为空,也可以通过 t->data 获取)// 或者直接使用全局变量 my_low_res_timer,这里不需要特殊的解引用pr_info("Low-res timer triggered! Counter: %d\n", ++counter);// 如果需要周期性触发,必须重新设置定时器// 这里设置为从现在起,每隔 3 秒触发一次mod_timer(&my_low_res_timer, jiffies + HZ * 3);
}// 模块初始化函数
static int __init my_low_res_init(void)
{pr_info("Loading low-res timer module...\n");// 3. 初始化定时器:绑定回调函数// timer_setup(&my_low_res_timer, low_res_timer_callback, 0);// 从 Linux 4.14+,推荐使用 timer_setup。第三个参数是 data,这里不需要就设为0。timer_setup(&my_low_res_timer, low_res_timer_callback, 0);// 4. 设置并启动定时器:5 秒后首次触发// jiffies 是当前系统启动以来的时钟节拍数// HZ 是每秒的节拍数(通常是 100、250 或 1000)mod_timer(&my_low_res_timer, jiffies + HZ * 5);pr_info("Low-res timer armed for 5 seconds (initial) and then every 3 seconds.\n");return 0;
}// 模块退出函数
static void __exit my_low_res_exit(void)
{// 5. 删除定时器:防止模块卸载后定时器仍然触发// del_timer_sync() 会等待回调函数执行完毕,更安全del_timer_sync(&my_low_res_timer);pr_info("Low-res timer module unloaded and timer deleted.\n");
}module_init(my_low_res_init);
module_exit(my_low_res_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple low-resolution timer example module.");
dump_stack输出,其使用softirq:
[200643.659695] Call trace:
[200643.662215] dump_backtrace+0x9c/0x100
[200643.666045] show_stack+0x20/0x40
[200643.669435] dump_stack_lvl+0x90/0xb0
[200643.673174] dump_stack+0x18/0x28
[200643.676563] low_res_timer_callback+0x3c/0x60 [low_res_timer_example]
[200643.683081] call_timer_fn+0x3c/0x158
[200643.686819] __run_timers+0x340/0x3a8
[200643.690555] run_timer_softirq+0x28/0x50
[200643.694551] handle_softirqs+0x178/0x3b8
[200643.698549] __do_softirq+0x1c/0x28
[200643.702112] ____do_softirq+0x18/0x30
[200643.705849] call_on_irq_stack+0x24/0x58
[200643.709846] do_softirq_own_stack+0x24/0x38
[200643.714102] irq_exit_rcu+0xd0/0x110
[200643.717752] el1_interrupt+0x38/0x58
[200643.721403] el1h_64_irq_handler+0x18/0x28
[200643.725575] el1h_64_irq+0x64/0x68
[200643.729050] default_idle_call+0x80/0x148
[200643.733136] do_idle+0x298/0x310
[200643.736438] cpu_startup_entry+0x3c/0x50
[200643.740435] rest_init+0xd0/0xd8
[200643.743738] arch_call_rest_init+0x18/0x20
[200643.747913] start_kernel+0x53c/0x6b8
[200643.751650] __primary_switched+0xbc/0xd0
2. 高精度定时器 (hrtimer
) 示例
hrtimer
提供纳秒级的精确控制,不依赖于 jiffies
,并且能主动唤醒 CPU。它适用于实时系统、多媒体处理、电源管理等对时间精度有严格要求的场景。
特点:
精度:纳秒级(使用硬件定时器)。
开销:相对较大。
用途:实时控制、高频事件、精确的延迟和唤醒。
使用步骤:
定义
struct hrtimer
变量。初始化
hrtimer
: 使用hrtimer_init()
指定时钟源和定时器模式。设置回调函数: 将回调函数指针赋值给
hrtimer->function
。设置到期时间: 使用
ktime_set()
创建ktime_t
类型的时间值。启动定时器: 使用
hrtimer_start()
。取消定时器: 使用
hrtimer_cancel()
或hrtimer_try_to_cancel()
。
代码示例:
这是一个内核模块,它会设置一个高精度定时器,每隔 500 微秒(0.5 毫秒)触发一次。
C
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/hrtimer.h> // 高精度定时器头文件
#include <linux/ktime.h> // ktime_t 时间类型// 1. 定义一个 hrtimer 结构体变量
static struct hrtimer my_high_res_timer;
static ktime_t interval_time; // 定义定时器间隔时间
static int hr_counter = 0;// 2. 定义定时器到期时要执行的回调函数
// 回调函数必须返回 enum hrtimer_restart 类型
enum hrtimer_restart high_res_timer_callback(struct hrtimer *timer)
{pr_info("High-res timer triggered! HR_Counter: %d\n", ++hr_counter);// 如果需要周期性触发,必须重新设置到期时间// hrtimer_forward_now() 会在当前时间的基础上,向前推进到下一个间隔点hrtimer_forward_now(timer, interval_time);return HRTIMER_RESTART; // 表示定时器需要重新启动
}// 模块初始化函数
static int __init my_high_res_init(void)
{pr_info("Loading high-res timer module...\n");// 3. 初始化 hrtimer// CLOCK_MONOTONIC: 单调递增时钟(不受系统时间调整影响)// HRTIMER_MODE_REL: 相对模式(时间间隔相对于当前时间)hrtimer_init(&my_high_res_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);// 4. 设置回调函数my_high_res_timer.function = high_res_timer_callback;// 5. 设置定时器间隔:500 微秒 (0.5毫秒)// ktime_set(seconds, nanoseconds)interval_time = ktime_set(0, 500 * 1000); // 0秒,500000纳秒 = 500微秒 //实际测试需要将时间改大一点避免打印过多。// 6. 启动定时器:首次到期时间设置为 interval_timehrtimer_start(&my_high_res_timer, interval_time, HRTIMER_MODE_REL);pr_info("High-res timer armed for every 500 microseconds.\n");return 0;
}// 模块退出函数
static void __exit my_high_res_exit(void)
{// 7. 取消定时器:防止模块卸载后定时器仍然触发// hrtimer_cancel() 返回 0 表示定时器未激活,非 0 表示已取消int ret = hrtimer_cancel(&my_high_res_timer);if (ret) {pr_info("High-res timer was still active and cancelled.\n");} else {pr_info("High-res timer was not active.\n");}pr_info("High-res timer module unloaded.\n");
}module_init(my_high_res_init);
module_exit(my_high_res_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple high-resolution timer example module.");
dump_stack输出:
[ 220.944207][ C2] Call trace:
[ 220.947334][ C2] dump_backtrace+0xa0/0x128
[ 220.951765][ C2] show_stack+0x20/0x38
[ 220.955761][ C2] dump_stack_lvl+0x78/0xc8
[ 220.960106][ C2] dump_stack+0x18/0x28
[ 220.964102][ C2] high_res_timer_callback+0x40/0x78 [high_res_timer_example]
[ 220.971399][ C2] __run_hrtimer+0x84/0x270
[ 220.975743][ C2] __hrtimer_run_queues+0xb4/0x140
[ 220.980694][ C2] hrtimer_interrupt+0x10c/0x348
[ 220.985471][ C2] arch_timer_handler_phys+0x34/0x58
[ 220.990596][ C2] handle_percpu_devid_irq+0x90/0x1c8
[ 220.995807][ C2] handle_irq_desc+0x48/0x68
[ 221.000238][ C2] generic_handle_domain_irq+0x24/0x38
[ 221.005537][ C2] __gic_handle_irq_from_irqson+0x18c/0x2c8
[ 221.011268][ C2] gic_handle_irq+0x2c/0xb0
[ 221.015612][ C2] call_on_irq_stack+0x24/0x30
[ 221.020217][ C2] do_interrupt_handler+0x88/0x98
[ 221.025082][ C2] el1_interrupt+0x54/0x120
[ 221.029426][ C2] el1h_64_irq_handler+0x24/0x30
[ 221.034203][ C2] el1h_64_irq+0x78/0x80
[ 221.038285][ C2] default_idle_call+0x74/0x150
[ 221.042977][ C2] cpuidle_idle_call+0x18c/0x200
[ 221.047754][ C2] do_idle+0xbc/0x188
[ 221.051578][ C2] cpu_startup_entry+0x40/0x50
[ 221.056182][ C2] secondary_start_kernel+0x14c/0x1d8
[ 221.061395][ C2] __secondary_switched+0xb8/0xc0
编译和加载
要编译这些模块,你需要一个 Makefile
:
Makefile
obj-m := low_res_timer_example.o high_res_timer_example.oKDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)all:$(MAKE) -C $(KDIR) M=$(PWD) modulesclean:$(MAKE) -C $(KDIR) M=$(PWD) clean
然后,在 Linux 系统上执行:
make
sudo insmod low_res_timer_example.ko
或sudo insmod high_res_timer_example.ko
通过
dmesg -w
观察内核日志输出。sudo rmmod low_res_timer_example
或sudo rmmod high_res_timer_example
这些例子展示了如何在 Linux 内核中使用这两种不同精度的定时器,帮助你根据实际需求进行选择。