我們在編寫內核驅動的時候,有時候需要掛載驅動的一段時間後執行某項任務,或者週期行的執行某項任務。
這種時候需要藉助內核定時器來協助我們。(類似“軟件中斷”)
定時器調用流程:
1. 初始化
a. 宏 init_timer
b. 宏DEFINE_TIMER
c. 宏 setup_timer
2.向內核中添加定時器
a. add_timer - 初始化完成後,向內核中添加定時器
b. mod_timer - 如果需要週期性執行任務,在定時器回調函數中添加 mod_timer
3. 手動/自動釋放
a. 定時器運行後,不會再運行,類似自動註銷;
b. 定時器未運行,可手動釋放 - del_timer
源碼: linux-3.10.y
struct timer_list
// linux-3.10.y/include/linux\timer.h
struct timer_list
{
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry; // 定時器列表
unsigned long expires; // 期望定時器執行的jiffies值
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
};
重點 :
- expires,定時器的到期時間,單位是jiffies
- function,定時器到期,要執行的函數
- data,傳入要執行的函數的參數
初始化定時器 - 三種方法
方法一 : 宏 init_timer 初始化 struct timer_list
init_timer(timer) // 傳參 time 是 struct timer_list 指針
// linux-3.10.y/include/linux\timer.h
// 功能是初始化 timer_list 結構體的 entry 的next 爲 NULL,並給 base 指針賦值
#define init_timer(timer) \
__init_timer((timer), 0)
// linux-3.10.y/kernel/timer.c
void init_timer_key(struct timer_list *timer, unsigned int flags,
const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, flags, name, key);
}
EXPORT_SYMBOL(init_timer_key);
static inline void debug_init(struct timer_list *timer)
{
debug_timer_init(timer);
trace_timer_init(timer);
}
static void do_init_timer(struct timer_list *timer, unsigned int flags,
const char *name, struct lock_class_key *key)
{
struct tvec_base *base = __raw_get_cpu_var(tvec_bases);
timer->entry.next = NULL;
timer->base = (void *)((unsigned long)base | flags);
timer->slack = -1;
#ifdef CONFIG_TIMER_STATS
timer->start_site = NULL;
timer->start_pid = -1;
memset(timer->start_comm, 0, TASK_COMM_LEN);
#endif
lockdep_init_map(&timer->lockdep_map, name, key, 0);
}
方法二 :宏DEFINE_TIMER 初始化 struct timer_list
DEFINE_TIMER 中創建 struct timer_list 型變量,無需再外部定義 timer_list 變量
// linux-3.10.y/include/linux\timer.h
#define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { \
.entry = { .prev = TIMER_ENTRY_STATIC }, \
.function = (_function), \
.expires = (_expires), \
.data = (_data), \
.base = (void *)((unsigned long)&boot_tvec_bases + (_flags)), \
.slack = -1, \
__TIMER_LOCKDEP_MAP_INITIALIZER( \
__FILE__ ":" __stringify(__LINE__)) \
}
#define TIMER_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), 0)
#define TIMER_DEFERRED_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), TIMER_DEFERRABLE)
#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)
方法三 :宏 setup_timer 初始化 struct timer_list
setup_timer 和 init_timer 類似。
// linux-3.10.y/include/linux\timer.h
#define __setup_timer(_timer, _fn, _data, _flags) \
do { \
__init_timer((_timer), (_flags)); \
(_timer)->function = (_fn); \
(_timer)->data = (_data); \
} while (0)
#define __setup_timer_on_stack(_timer, _fn, _data, _flags) \
do { \
__init_timer_on_stack((_timer), (_flags)); \
(_timer)->function = (_fn); \
(_timer)->data = (_data); \
} while (0)
#define setup_timer(timer, fn, data) \
__setup_timer((timer), (fn), (data), 0)
增加/修改定時器
先看函數聲明:
void add_timer(struct timer_list *timer) // 增加定時器, 定時器運行後,自動釋放
int mod_timer(struct timer_list *timer, unsigned long expires) // 重設,增加定時器
源碼如下:
// linux-3.10.y/kernel/timer.c
// 註冊內核定時器,將定時器加入到內核動態定時器鏈表,即啓動定時器
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);
}
EXPORT_SYMBOL(add_timer);
// 重設延時時間,啓動定時器
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);
}
EXPORT_SYMBOL(mod_timer);
static inline int
__mod_timer(struct timer_list *timer, unsigned long expires,
bool pending_only, int pinned)
{
struct tvec_base *base, *new_base;
unsigned long flags;
int ret = 0, cpu;
timer_stats_timer_set_start_info(timer);
BUG_ON(!timer->function);
base = lock_timer_base(timer, &flags);
ret = detach_if_pending(timer, base, false);
if(!ret && pending_only)
goto out_unlock;
debug_activate(timer, expires);
cpu = smp_processor_id();
#if defined(CONFIG_NO_HZ_COMMON) && defined(CONFIG_SMP)
if(!pinned && get_sysctl_timer_migration() && idle_cpu(cpu))
cpu = get_nohz_timer_target();
#endif
new_base = per_cpu(tvec_bases, cpu);
if(base != new_base)
{
/*
* We are trying to schedule the timer on the local CPU.
* However we can't change timer's base while it is running,
* otherwise del_timer_sync() can't detect that the timer's
* handler yet has not finished. This also guarantees that
* the timer is serialized wrt itself.
*/
if(likely(base->running_timer != timer))
{
/* See the comment in lock_timer_base() */
timer_set_base(timer, NULL);
spin_unlock(&base->lock);
base = new_base;
spin_lock(&base->lock);
timer_set_base(timer, base);
}
}
timer->expires = expires;
internal_add_timer(base, timer);
out_unlock:
spin_unlock_irqrestore(&base->lock, flags);
return ret;
}
刪除定時器
函數聲明:
// 直接刪除
int del_timer(struct timer_list * timer);
源碼:
// linux-3.10.y/kernel/timer.c
/**
* 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;
}
EXPORT_SYMBOL(del_timer);