我们在编写内核驱动的时候,有时候需要挂载驱动的一段时间后执行某项任务,或者周期行的执行某项任务。
这种时候需要借助内核定时器来协助我们。(类似“软件中断”)
定时器调用流程:
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);