寫了一個RTC驅動,爲了實現在驅動中定時將RTC的時間校準到系統時間,研究了一下驅動中定時器的使用。但經過實踐發現這個想法並不可行,原因在下面闡述。
下面記錄定時器的使用方法。
一、知識點引入
定時器在內核中的源碼位置
- kernel/kernel/timer.c
- kernel/include/linux/timer.h
1、結構體 struct timer_list 說明
以下結構體定義在 timer.h 中。
在內核中,一個 **timer_list ** 結構體實例對應一個定時器。
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;
unsigned long expires;
struct tvec_base *base;
void (*function)(unsigned long);
unsigned long data;
int slack;
#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
- entry:雙向鏈表。內核中用來將多個定時器連接成循環隊列。
- expires:定時器超時時間。用來與系統時間變量 jiffies 進行比較。 jiffies 爲自系統啓動以來的時鐘滴答計數(一般一個節拍是10ms,我的系統是這樣的)。一般將 expires 設置爲 jiffies 再加上某個數,直到 jiffies 遞增到與 expires 相等則定時器觸發。
- function:定時器任務函數指針。即到時間後要執行的函數。
- data:傳入function函數的參數,一般可以用作定時器ID傳入,假如多個定時器使用同一個任務函數,那麼可以在函數中根據傳入的data參數進行區分是哪一個定時器。
2、函數說明
(1)初始化定時器
init_timer(timer),這是個宏,用來初始化 struct timer_list 結構體。
通過查看源碼可以知道最後調用的函數是init_timer_key。
(2)添加/啓動定時器
這個函數用來將一個定時器插入定時器鏈表中,當初始化時設置的 expires 值大於 jiffies 的值則定時器啓動。
/**
* add_timer - start a timer
* @timer: the timer to be added
*
* The kernel will do a ->function(->data) callback from the
* timer interrupt at the ->expires point in the future. The
* current time is 'jiffies'.
*
* The timer's ->expires, ->function (and if the handler uses it, ->data)
* fields must be set prior calling this function.
*
* Timers with an ->expires field in the past will be executed in the next
* timer tick.
*/
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);
}
(3)修改定時器時間
這個函數是用來修改定時器的時間的,如果定時器要實現一直循環效果則需要在到點時執行的任務函數中調用此函數,修改下一次定時器的到點時間。
/**
* mod_timer - modify a timer's timeout
* @timer: the timer to be modified
* @expires: new timeout in jiffies
*
* mod_timer() is a more efficient way to update the expire field of an
* active timer (if the timer is inactive it will be activated)
*
* mod_timer(timer, expires) is equivalent to:
*
* del_timer(timer); timer->expires = expires; add_timer(timer);
*
* Note that if there are multiple unserialized concurrent users of the
* same timer, then mod_timer() is the only safe way to modify the timeout,
* since add_timer() cannot modify an already running timer.
*
* The function returns whether it has modified a pending timer or not.
* (ie. mod_timer() of an inactive timer returns 0, mod_timer() of an
* active timer returns 1.)
*/
int mod_timer(struct timer_list *timer, unsigned long expires)
{
expires = apply_slack(timer, expires);
/*
* This is a common optimization triggered by the
* networking code - if the timer is re-modified
* to be the same thing then just return:
*/
if (timer_pending(timer) && timer->expires == expires)
return 1;
return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
}
(4)刪除定時器
當定時器不再使用時,調用此函數將定時器從定時器隊列中刪除。
/**
* del_timer - deactive a timer.
* @timer: the timer to be deactivated
*
* del_timer() deactivates a timer - this works on both active and inactive
* timers.
*
* The function returns whether it has deactivated a pending timer or not.
* (ie. del_timer() of an inactive timer returns 0, del_timer() of an
* active timer returns 1.)
*/
int del_timer(struct timer_list *timer)
{
struct tvec_base *base;
unsigned long flags;
int ret = 0;
debug_assert_init(timer);
timer_stats_timer_clear_start_info(timer);
if (timer_pending(timer)) {
base = lock_timer_base(timer, &flags);
ret = detach_if_pending(timer, base, true);
spin_unlock_irqrestore(&base->lock, flags);
}
return ret;
}
二、程序設計流程
- 初始化定時器。
- 實現任務函數。
- 配置計時時間、任務函數、任務函數參數。
- 添加定時器。
- 刪除定時器。
三、測試源碼
由於代碼不多,就直接貼代碼好了。
這裏實現的是兩個定時器,一個1秒一個5秒,共用同一個任務函數。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
/*************************************************************************************************/
// 局部宏定義
/*************************************************************************************************/
#define EN_DEBUG 1 /* 調試信息開關 */
#if EN_DEBUG
#define PRINT(x...) printk(KERN_EMERG x) /* 提高打印等級 */
#else
#define PRINT(x...)
#endif
typedef enum {
TIMER1 = 1,
TIMER2 = 2
} TIMER_ID; /* 定時器ID枚舉 */
#define TIMER1_PERIOD 1 /* 定時器1週期 */
#define TIMER2_PERIOD 5 /* 定時器2週期 */
/*************************************************************************************************/
// 局部變量
/*************************************************************************************************/
static struct timer_list s_timer1, s_timer2;
/**************************************************************************************************
** 函數名稱: test_task
** 功能描述: 測試任務
** 輸入參數: 無
** 輸出參數: 無
** 返回參數: 無
**************************************************************************************************/
static void test_task(unsigned long arg)
{
if (arg == TIMER1) {
PRINT("[KERNEL]:%s -- timer1 -- \n", __FUNCTION__);
mod_timer(&s_timer1, jiffies + TIMER1_PERIOD * HZ);
}
if (arg == TIMER2) {
PRINT("[KERNEL]:%s -- timer2 -- \n", __FUNCTION__);
mod_timer(&s_timer2, jiffies + TIMER2_PERIOD * HZ);
}
}
/**************************************************************************************************
** 函數名稱: timer_init
** 功能描述: 定時器初始化函數
** 輸入參數: 無
** 輸出參數: 無
** 返回參數: 無
**************************************************************************************************/
static void timer_init(void)
{
init_timer(&s_timer1); /* 初始化定時器 */
s_timer1.data = TIMER1; /* 傳給執行函數的參數 */
s_timer1.expires = jiffies + TIMER1_PERIOD * HZ; /* 設置定時器到期時間 */
s_timer1.function = test_task; /* 設置定時器到期執行函數 */
add_timer(&s_timer1); /* 添加定時器,定時器生效 */
init_timer(&s_timer2); /* 初始化定時器 */
s_timer2.data = TIMER2; /* 傳給執行函數的參數 */
s_timer2.expires = jiffies + TIMER2_PERIOD * HZ; /* 設置定時器到期時間 */
s_timer2.function = test_task; /* 設置定時器到期執行函數 */
add_timer(&s_timer2); /* 添加定時器,定時器生效 */
}
/**************************************************************************************************
** 函數名稱: drv_init
** 功能: 驅動初始化函數,在加載時被調用
** 參數: 無
** 返回: 無
**************************************************************************************************/
static int __init drv_init(void)
{
PRINT("[KERNEL]:%s ------ \n", __FUNCTION__);
timer_init();
return 0;
}
/**************************************************************************************************
** 函數名稱: drv_exit
** 功能描述: 驅動退出函數,在卸載時被調用
** 參數: 無
** 返回: 無
**************************************************************************************************/
static void __exit drv_exit(void)
{
PRINT("[KERNEL]:%s ------ \n", __FUNCTION__);
del_timer(&s_timer1); /* 刪除定時器 */
del_timer(&s_timer2); /* 刪除定時器 */
}
module_init(drv_init); /* 模塊初始化 */
module_exit(drv_exit); /* 模塊卸載 */
MODULE_AUTHOR("hrx"); /* 模塊作者 */
MODULE_DESCRIPTION("Linux Driver"); /* 模塊描述 */
MODULE_VERSION("1.0.0"); /* 模塊版本 */
MODULE_LICENSE("GPL"); /* 模塊遵守的License */
四、產生的問題
1、中斷函數中調用休眠函數
出現以下信息後,整個系統完全卡死。
BUG: scheduling while atomic: swapper/0/0/0x00000102
這是由於定時器是由中斷產生的,而配置的任務函數就是中端函數,而中斷函數中是不可以使用休眠函數的,因爲一旦在中斷函數中休眠會導致CPU無法被搶佔,從而導致中斷函數中的休眠函數無法被喚醒,那麼整個系統就死機了。
結論:中斷函數中不能使用休眠函數以及包含休眠函數的函數。