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 內核中使用這兩種不同精度的定時器,幫助你根據實際需求進行選擇。