本文已参与「新人创作礼」活动,一起开启掘金创作之路。
写了一个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无法被抢占,从而导致中断函数中的休眠函数无法被唤醒,那么整个系统就死机了。
结论:中断函数中不能使用休眠函数以及包含休眠函数的函数。