先給自己打個廣告,本人的微信公衆號正式上線了,搜索:張笑生的地盤,主要關注嵌入式軟件開發,足球等等,希望大家多多關注,有問題可以直接留言給我,一定盡心盡力回答大家的問題
一 why
一般地,在我們嵌入式軟件開發中,使用定時器的目的是爲了實現週期性地執行某項工作;同樣地,linux內核也實現了一種定時器,用於實現內核週期性執行某項工作。
二 what
linux內核實現的這種定時器,稱之爲動態定時器,是內核用來控制在未來某個時間點(基於jiffies)調度執行某個函數的一種機制,它的實現位於Linux/timer.h 和 kernel/timer.c 文件中。
被調度的函數是異步執行的,類似於一種“軟件中斷”,處於非進程的上下文中,所以這個被調度函數應該遵循如下規則
1. 沒有 current 指針,不允許訪問用戶空間。因爲沒有進程上下文,相關代碼和被中斷的進程沒有任何聯繫。
2. 不能執行休眠(或可能引起休眠的函數)和調度。
3. 任何被訪問的數據結構都應該針對併發訪問進行保護,以防止競爭條件。
4. 被調度函數運行過一次後就不會再被運行了(相當於自動註銷),但可以通過在被調度的函數中重新調度自己來週期運行。
2.1 內核定時器的數據結構
struct timer_list {
struct list_head entry; // 當一個定時器被註冊到內核之後,entry 字段用來連接該定時器到一個內核鏈表中
unsigned long expires; // 表示期望定時器執行的 jiffies 值,到達該 jiffies 值時,將調用 function 函數
void (*function)(unsigned long);
unsigned long data;
struct tvec_base *base; //內核內部實現所用的
/* ... */
};
2.2 初始化
由於linux內核版本的不同,關於初始化部分的實現有不同的函數,低版本的用init_timer,高版本是timer_setup
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0)
init_timer(&dev->getIntrTimer);
dev->getIntrTimer.data = (unsigned long) dev;
dev->getIntrTimer.function = GetIntrTimerCallback;
/* ... */
add_timer(&dev->getIntrTimer);
#else
timer_setup(&dev->getIntrTimer, GetIntrTimerCallback, 0);
/* the third argument may include TIMER_* flags */
/* ... */
#endif
比如當前我的linux版本是4.15,所以在我的實驗中,使用的就是timer_setup
2.3 註冊
初始化定時器成功之後,我們需要註冊該定時器,函數爲
add_timer
2.4 修改
定時運行過程中的定時器週期修改函數爲
mod_timer
前面我們知道,當定時週期來到時,會執行被調函數,如果我們此時不修改定時器到期時間,則接下來就不會在執行被調函數了,我們可以在被調函數中修改定時器到期時間,實現週期性執行被調函數。
三 how
定時器內核編程示例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
static struct timer_list test_timer;
static void irq_test_timer_function(struct timer_list *timer)
{
printk("22 again in irq_test_timer_function\n");
mod_timer(&test_timer, jiffies + 2 * HZ);
}
static int timer_drv_init(void)
{
test_timer.expires = jiffies + 2 * HZ;
timer_setup(&test_timer, irq_test_timer_function, 0);
add_timer(&test_timer);
printk(KERN_INFO "already init and add timer\n");
return 0;
}
static void timer_drv_exit(void)
{
printk(KERN_INFO "exit timer drv\n");
del_timer(&test_timer);
}
module_init(timer_drv_init);
module_exit(timer_drv_exit);
MODULE_LICENSE("GPL");
Makefile文件
obj-m:=test_timer.o
KDIR:= /lib/modules/$(shell uname -r)/build
PWD:= $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
四 test
1. 上傳到Ubuntu系統
2. 編譯 make
3. 加載 sudo insmod test_timer.ko
4. 查看運行情況 dmesg | tail
5. 卸載 sudo rmmod test_timer
加載時打印
卸載時打印