一、內核定時器介紹
內核定時器是內核用來控制在未某個時間點(基於jiffies(節拍總數))調度執行某個函數的一種機制,相關函數位於<linux/timer.h> 和 kernel/timer.c 文件中。
當內核定時器定時時間到達時,會進入用戶指定的函數,相當於軟中斷。內核定時器註冊開啓後,運行一次就不會再運行(相當於自動註銷),我們可以重新設置定時器的超時時間,讓定時器重複運行。
每當時鐘中斷髮生時,全局變量jiffies(一個32位的unsigned long 變量)就加1,因此jiffies記錄了linux系統啓動後時鐘中斷髮生的次數,驅動程序常利用jiffies來計算不同事件間的時間間隔。內核每秒鐘將jiffies變量增加HZ次。因此,對於HZ值爲100的系統,jiffy+1等於隔了10ms,而對於HZ爲1000 的系統, jiffy+1僅爲1ms。
注意: jiffies 變量不能被修改,修改會出現錯誤。
二、內核定時器相關API
1、內核定時器結構體
struct timer_list {
/*
* All fields that change during normal runtime grouped to the same cacheline
*/
struct hlist_node entry;
unsigned long expires;//設置超時時間,jiffies+xxx
void (*function)(unsigned long);//定時時間到後處理的函數
unsigned long data;//傳遞給上面函數的參數
u32 flags;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
2、初始化定時器
//初始化定時器宏定義
#define init_timer(timer)//timer爲定時器結構體指針 \
__init_timer((timer), 0)
//初始化定時器具體實現
#define __init_timer(_timer, _flags) \
do { \
static struct lock_class_key __key; \
init_timer_key((_timer), (_flags), #_timer, &__key); \
} while (0)
3、初始化定時器並賦值
//初始化定時器並賦值宏定義
#define setup_timer(timer, fn, data) \
__setup_timer((timer), (fn), (data), 0)
//初始化定時器並賦值具體實現
#define __setup_timer(_timer, _fn, _data, _flags) \
do { \
__init_timer((_timer), (_flags)); \
(_timer)->function = (_fn); //定時到處理函數 \
(_timer)->data = (_data);//傳遞給處理函數的參數 \
} while (0)
4、用宏來初始化定時器
//宏定義
#define TIMER_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), 0)
//實現部分
#define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { \
.entry = { .next = TIMER_ENTRY_STATIC }, \
.function = (_function), \
.expires = (_expires), \
.data = (_data), \
.flags = (_flags), \
__TIMER_LOCKDEP_MAP_INITIALIZER( \
__FILE__ ":" __stringify(__LINE__)) \
}
5、用宏來初始化定時器並賦值
//宏定義
#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name =//定義了變量 \
TIMER_INITIALIZER(_function, _expires, _data)
//實現部分
#define TIMER_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), 0)
#define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { \
.entry = { .next = TIMER_ENTRY_STATIC }, \
.function = (_function), \
.expires = (_expires), \
.data = (_data), \
.flags = (_flags), \
__TIMER_LOCKDEP_MAP_INITIALIZER( \
__FILE__ ":" __stringify(__LINE__)) \
}
6、添加定時器
void add_timer(struct timer_list *timer)//參數爲定時器結構體指針
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);//就是設置超時時間,如果只設置一次,執行完畢就結束了
}
7、修改定時器超時時間
在內核定時器的介紹中我們說到,內核定時器註冊開啓後,運行一次就不會再運行(相當於自動註銷),我們可以重新設置定時器的超時時間,讓定時器重複運行。
int mod_timer(struct timer_list *timer, unsigned long expires)
//變量分別爲定時器結構體指針和超時時間
{
return __mod_timer(timer, expires, false);//具體細節看內核源碼
}
8、關閉定時器
/*
函數功能:刪除定時器
*/
int del_timer(struct timer_list *timer)
{
struct timer_base *base;
unsigned long flags;
int ret = 0;
debug_assert_init(timer);
if (timer_pending(timer)) {
base = lock_timer_base(timer, &flags);
ret = detach_if_pending(timer, base, true);
raw_spin_unlock_irqrestore(&base->lock, flags);
}
return ret;
}
/*
函數功能:刪除定時器,和上面函數不同的是,該函數用在多處理器(SMP)時,如何有其他的處理器在使用
定時器時,該函數會睡眠直到使用完畢再將定時器刪除。
*/
int del_timer_sync(struct timer_list *timer)
{
#ifdef CONFIG_LOCKDEP
unsigned long flags;
/*
* If lockdep gives a backtrace here, please reference
* the synchronization rules above.
*/
local_irq_save(flags);
lock_map_acquire(&timer->lockdep_map);
lock_map_release(&timer->lockdep_map);
local_irq_restore(flags);
#endif
/*
* don't use it in hardirq context, because it
* could lead to deadlock.
*/
WARN_ON(in_irq() && !(timer->flags & TIMER_IRQSAFE));
for (;;) {
int ret = try_to_del_timer_sync(timer);
if (ret >= 0)
return ret;
cpu_relax();
}
9、轉換時間
/*
函數功能:微秒轉節拍(usecs_to_jiffies)
*/
static __always_inline unsigned long usecs_to_jiffies(const unsigned int u)
//具體實現如下
static inline unsigned long _usecs_to_jiffies(const unsigned int u)
{
return (u + (USEC_PER_SEC / HZ) - 1) / (USEC_PER_SEC / HZ);
}
#else
static inline unsigned long _usecs_to_jiffies(const unsigned int u)
{
return (USEC_TO_HZ_MUL32 * u + USEC_TO_HZ_ADJ32)
>> USEC_TO_HZ_SHR32;
}
/*
函數功能:毫秒轉節拍(msecs_to_jiffies)
*/
static __always_inline unsigned long msecs_to_jiffies(const unsigned int m)
三、內核定時器的使用步驟
1、定義時間結構體變量。
2、編寫定時器時間到處理函數。
3、初始化定時器,並賦值。
4、刪除定時器
技巧:如果定時器想一直開啓,可以將修改定時器時間放在定時器時間到處理函數中。