Linux驱动定时器的使用

内核使用绝对时间来了解具体的时间,也就是一天的日期和时间,而相对时间则被内核调度程序使用。对于绝对时间,有一个称为实时时钟的硬件芯片(RTC)。

内核定时器有两种

  • 标准定时器或系统定时器
  • 高精度定时器

标准定时器
标准定时器是内核定时器,它以jiffy为粒度运行。

常量HZ是jiffies在1s内递增的次数,每个增量被称为一个Tick。

定时器API
定时器在内核中表示为timer_list的一个实例

struct timer_list {
	/*
	 * All fields that change during normal runtime grouped to the
	 * same cacheline
	 */
	struct hlist_node	entry;
	unsigned long		expires;
	void			(*function)(struct timer_list *);
	u32			flags;

#ifdef CONFIG_LOCKDEP
	struct lockdep_map	lockdep_map;
#endif
};

expires以jiffies为单位,function为回调函数。

初始化定时器

  • 设置定时器。设置定时器,提供用户自定义的回调函数
/**
 * timer_setup - prepare a timer for first use
 * @timer: the timer in question
 * @callback: the function to call when timer expires
 * @flags: any TIMER_* flags
 *
 * Regular timer initialization should use either DEFINE_TIMER() above,
 * or timer_setup(). For timers on the stack, timer_setup_on_stack() must
 * be used and must be balanced with a call to destroy_timer_on_stack().
 */
#define timer_setup(timer, callback, flags)			\
	__init_timer((timer), (callback), (flags))

#define timer_setup_on_stack(timer, callback, flags)		\
	__init_timer_on_stack((timer), (callback), (flags))
  • 设置过期时间。当定时器初始化时,需要在启动回调之前设置定时时间。
/**
 * 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)
{
        return __mod_timer(timer, expires, 0);
}
EXPORT_SYMBOL(mod_timer);
  • 释放定时器。定时器使用完毕之后需要释放。
/**
 * del_timer - deactivate 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 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;
}
EXPORT_SYMBOL(del_timer);

对于不活动的定时器,返回0,对于活动的定时器,返回1。

/**
 * del_timer_sync - deactivate a timer and wait for the handler to finish.
 * @timer: the timer to be deactivated
 *
 * This function only differs from del_timer() on SMP: besides deactivating
 * the timer it also makes sure the handler has finished executing on other
 * CPUs.
 *
 * Synchronization rules: Callers must prevent restarting of the timer,
 * otherwise this function is meaningless. It must not be called from
 * interrupt contexts unless the timer is an irqsafe one. The caller must
 * not hold locks which would prevent completion of the timer's
 * handler. The timer's handler must not call add_timer_on(). Upon exit the
 * timer is not queued and the handler is not running on any CPU.
 *
 * Note: For !irqsafe timers, you must not hold locks that are held in
 *   interrupt context while calling this function. Even if the lock has
 *   nothing to do with the timer in question.  Here's why::
 *
 *    CPU0                             CPU1
 *    ----                             ----
 *                                     <SOFTIRQ>
 *                                       call_timer_fn();
 *                                       base->running_timer = mytimer;
 *    spin_lock_irq(somelock);
 *                                     <IRQ>
 *                                        spin_lock(somelock);
 *    del_timer_sync(mytimer);
 *    while (base->running_timer == mytimer);
 *
 * Now del_timer_sync() will never return and never release somelock.
 * The interrupt on the other CPU is waiting to grab somelock but
 * it has interrupted the softirq that CPU0 is waiting to finish.
 *
 * The function returns whether it has deactivated a pending timer or not.
 */
int del_timer_sync(struct timer_list *timer);

等待处理程序(即使在另一个cpu上执行)执行完成,不应该持有阻止处理程序完成的锁,这样会导致死锁,应该在模块清理流程中释放定时器,可以独立检查定时器是否在运行。

/**
 * timer_pending - is a timer pending?
 * @timer: the timer in question
 *
 * timer_pending will tell whether a given timer is currently pending,
 * or not. Callers must ensure serialization wrt. other operations done
 * to this timer, eg. interrupt contexts, or other CPUs on SMP.
 *
 * return value: 1 if the timer is pending, 0 if not.
 */
static inline int timer_pending(const struct timer_list * timer)
{
        return !hlist_unhashed_lockless(&timer->entry);
}

三、测试代码

注册定时器之后,在timer_callback()修改定时器的延时时间,这个是第一种方式实现,大家可以尝试使用add_timer()方式实现。

#include <linux/module.h> 
#include <linux/init.h> 
#include <linux/sched.h> 
#include <linux/timer.h> 
#include <linux/kernel.h>

//毫秒
#define TIMEOUT 5000

//定义定时器 
struct timer_list mytimer;
unsigned int count = 0;

//定时器处理函数 mod_timer(&mytimer, jiffies + TIMEOUT);
static void timer_callback(struct timer_list *data) {  
    printk("Timer Callback function Called [%d]\n", count++);
    mod_timer(&mytimer, jiffies + msecs_to_jiffies(TIMEOUT));
}

//定时器初始化过程 
static int __init timer_init(void) { 
    /* 初始化一个定时器 */
    timer_setup(&mytimer, timer_callback, 0);

    /* 修改定时器的延时时间 */
    mod_timer(&mytimer, jiffies + msecs_to_jiffies(TIMEOUT));
    return 0;
}

static void __exit timer_exit(void) {
    /* 卸载驱动移除注册的定时器 */
    del_timer(&mytimer);
}

module_init(timer_init);
module_exit(timer_exit);
MODULE_AUTHOR("curtis"); 
MODULE_LICENSE("GPL"); 

四、注意事项

  • 没有 current 指针、不允许访问用户空间。因为没有进程上下文,相关代码和被中断的进程没有任何联系。

  • 不能执行休眠(或可能引起休眠的函数)和调度。

  • 任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐