第10章 中断与时钟之内核定时器

10.5 内核定时器

10.5.1 内核定时器编程

    软件上的定时器最终依赖硬件定时器来实现,kernel在时钟中断发生后检测各定时器是否到期,到期后的定时器处理函数将作为软中断在下半部执行。实质上,时钟中断处理程序会唤起TIMER_SOFTIRQ软中断,运行当前处理器CPU上到期的所有定时器。

<linux/interrupt.h>

enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS

};

    在Linux设备驱动编程中,可以利用Linux内核中提供的一组函数和数据结构来完成定时触发工作或者完成某周期性的事务。这组函数和数据结构使得驱动工程师在多数情况下不用关心具体的软件定时器对应着怎样的内核和硬件行为。

Linux内核所提供的用于操作定时器的数据结构和函数如下:

1.timer_list

在Linux内核中,timer_list结构体的一个实例对应一个定时器,如代码清单10.9所示。

代码清单10.9 timer_list结构体

#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; // 到期时间
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

};

    当定时器期满后,function()成员被执行,而data成员则是传入function()函数的参数,expires则是定时器到期的时间(单位:jiffies)。

定义一个名为timer的定时器:

struct timer_list timer;

2.初始化定时器

init_timer是一个宏,

#define init_timer(timer) \
__init_timer((timer), 0)

TIMER_INITIALIZER(_function,_expires,_data)宏用于赋值定时器结构体的function、expires、data和base成员。

#define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { \
.entry = { .prev = TIMER_ENTRY_STATIC }, \
.function = (_function), \
.expires = (_expires), \
.data = (_data), \
.base = (void *)(__TIMER_BASE(_flags)), \
.slack = -1, \
__TIMER_LOCKDEP_MAP_INITIALIZER( \
__FILE__ ":" __stringify(__LINE__)) \
}


#define TIMER_INITIALIZER(_function, _expires, _data) \

__TIMER_INITIALIZER((_function), (_expires), (_data), 0)

DEFINE_TIMER(_name,_function,_expires,_data)宏是定义并初始化定时器成员的“快捷方式”,这个宏定义为:

#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \

TIMER_INITIALIZER(_function, _expires, _data)

setup_timer()也可用于初始化定时器并赋值其成员,其源代码为:

#define __setup_timer(_timer, _fn, _data, _flags) \
do { \
__init_timer((_timer), (_flags)); \
(_timer)->function = (_fn); \
(_timer)->data = (_data); \
} while (0)


#define setup_timer(timer, fn, data) \

__setup_timer((timer), (fn), (data), 0)

3.增加定时器

void add_timer(struct timer_list * timer);

该函数用于注册内核定时器,将定时器加入到内核动态定时器链表中。

4.删除定时器

int del_timer(struct timer_list * timer);

该函数函数用于删除定时器。

del_timer_sync()是del_timer()的同步版,在删除一个定时器时需等待其被处理完,该函数的调用不能发生在中断上下文中。

5.修改定时器的expire(到期时间)

int mod_timer(struct timer_list *timer, unsigned long expires);

该函数用于修改定时器的到期时间,在新的被传入的expires到来后才会执行定时器函数。

该函数的定义在kernel/time/timer.c

/**
 * 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);
}

EXPORT_SYMBOL(mod_timer);

代码清单10.10 内核定时器使用模板

    /* xxx设备结构体 */
 struct xxx_dev {
       struct cdev cdev;
          ...
       struct timer_list    xxx_timer;      /* 设备要使用的定时器 */

 };

/* 定时器处理函数 */
static void xxx_do_timer(unsigned long arg)
{
          struct xxx_device *dev = (struct xxx_device *)(arg);
          ...
          /* 调度定时器再执行 */
          dev->xxx_timer.expires = jiffies + delay;
          add_timer(&dev->xxx_timer);
          ...
}

 /* xxx驱动中的某函数 */
 xxx_func1(…)
{
      struct xxx_dev *dev = filp->private_data; // 从文件私有数据中获取该设备结构体的指针
      ...
      /* 初始化定时器 */
      init_timer(&dev->xxx_timer);
      dev->xxx_timer.function = &xxx_do_timer;// 定时器到期后执行的函数
      dev->xxx_timer.data = (unsigned long)dev;/* 设备结构体指针作为定时器处理函数参数 */              
      dev->xxx_timer.expires = jiffies + delay; // 定时器到期时间
      /* 添加(注册)定时器 */
      add_timer(&dev->xxx_timer);// 将定时器加入到内核动态定时器链表中
      ...
}

/* xxx驱动中的某函数 */
xxx_func2(…)
{
         ...
          /* 删除定时器 */
          del_timer (&dev->xxx_timer);
          ...
}

此外,Linux内核支持tickless(无滴答)和NO_HZ模式后,内核也包含对hrtimer(高精度定时器)的支持,可以支持到微秒级别的精度。

内核也定义了hrtimer结构体,

<linux/hrtimer.h>

/**
 * struct hrtimer - the basic hrtimer structure
 * @node: timerqueue node, which also manages node.expires,
 * the absolute expiry time in the hrtimers internal
 * representation. The time is related to the clock on
 * which the timer is based. Is setup by adding
 * slack to the _softexpires value. For non range timers
 * identical to _softexpires.
 * @_softexpires: the absolute earliest expiry time of the hrtimer.
 * The time which was given as expiry time when the timer
 * was armed.
 * @function: timer expiry callback function
 * @base: pointer to the timer base (per cpu and per clock)
 * @state: state information (See bit values above)
 * @start_pid: timer statistics field to store the pid of the task which
 * started the timer
 * @start_site: timer statistics field to store the site where the timer
 * was started
 * @start_comm: timer statistics field to store the name of the process which
 * started the timer
 *
 * The hrtimer structure must be initialized by hrtimer_init()
 */
struct hrtimer {
struct timerqueue_node node;
ktime_t _softexpires;
enum hrtimer_restart (*function)(struct hrtimer *);
struct hrtimer_clock_base *base;
unsigned long state;
#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif

};

hrtimer_set_expires()、hrtimer_start_expires()、hrtimer_forward_now()、hrtimer_restart()等类似的API来完成hrtimer的设置、时间推移以及到期回调。

代码清单10.11 内核高精度定时器(hrtimer)使用模板

struct imx_pcm_runtime_data {
......
struct hrtimer hrt; // 内核高精度定时器
......
};

static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
 {
         ...
 
         hrtimer_forward_now(hrt, ns_to_ktime(
iprtd->poll_time_ns));//把hrtimer的时间前移了iprtd->poll_time_ns纳秒

         return
HRTIMER_RESTART;
 }

static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct imx_pcm_runtime_data *iprtd = runtime->private_data;

        switch (cmd) {
        case
SNDRV_PCM_TRIGGER_START:
        case SNDRV_PCM_TRIGGER_RESUME:
        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
                ...
                hrtimer_start(&iprtd->hrt, ns_to_ktime(
iprtd->poll_time_ns),
                      HRTIMER_MODE_REL);// 启动hrtimer
                ...
}

static int snd_imx_open(struct snd_pcm_substream *substream)
{
......
hrtimer_init(&iprtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); /* 初始化高精度定时器 */
iprtd->hrt.function = snd_hrtimer_callback; /* 指定回调函数 */
        ......
return 0;
}

static int snd_imx_close(struct snd_pcm_substream *substream)
{
        ...
        hrtimer_cancel(&iprtd->hrt);// 取消在open时初始化的hrtimer
        ...
}

10.5.2 内核中延迟的工作delayed_work

对于周期性的任务,除定时器外,在Linux内核中还可利用一套封装得很好的快捷机制,这套快捷机制就是延迟工作delayed_work,其本质利用工作队列和定时器实现

delayed_work结构体的定义:

<linux/workqueue.h>

struct delayed_work {
        struct work_struct work; // 工作队列
        struct timer_list timer;// 定时器

        /* target workqueue and CPU ->timer uses to queue ->work */
        struct workqueue_struct *wq;
        int cpu;
};

通过如下函数调度一个delayed_work在指定的延时后执行:

/**
 * schedule_delayed_work - put work task in global workqueue after delay
 * @dwork: job to be done
 * @delay: number of jiffies to wait or 0 for immediate execution
 *
 * After waiting for a given time this puts a job in the kernel-global
 * workqueue.
 */
static inline bool
schedule_delayed_work(struct delayed_work *dwork,
unsigned long
delay)
{
return queue_delayed_work(system_wq, dwork, delay);
}

当指定的delay到来时,struct delayed_work结构体中的work成员work_func_t类型成员func()会被执行。

work_func_t类型定义为:

typedef void (*work_func_t)(struct work_struct *work); // 函数指针类型

其中,delay参数的单位是jiffies,一种常见的用法如下:

schedule_delayed_work(&work, msecs_to_jiffies(poll_interval));

msecs_to_jiffies()用于将毫秒转化为jiffies。

如果要周期性地执行任务,通常会在delayed_work的工作函数中再次调用schedule_delayed_work(),周而复始。

用来取消delayed_work的函数:<linux/workqueue.h>

extern bool cancel_delayed_work(struct delayed_work *dwork);

extern bool cancel_delayed_work_sync(struct delayed_work *dwork);

这两个函数定义在kernel/workqueue.c

/**
 * cancel_delayed_work - cancel a delayed work
 * @dwork: delayed_work to cancel
 *
 * Kill off a pending delayed_work.
 *
 * Return: %true if @dwork was pending and canceled; %false if it wasn't
 * pending.
 *
 * Note:
 * The work callback function may still be running on return, unless
 * it returns %true and the work doesn't re-arm itself.  Explicitly flush or
 * use cancel_delayed_work_sync() to wait on it.
 *
 * This function is safe to call from any context including IRQ handler.
 */
bool cancel_delayed_work(struct delayed_work *dwork)
{
unsigned long flags;
int ret;

do {
ret = try_to_grab_pending(&dwork->work, true, &flags);
} while (unlikely(ret == -EAGAIN));

if (unlikely(ret < 0))
return false;

set_work_pool_and_clear_pending(&dwork->work,
get_work_pool_id(&dwork->work));
local_irq_restore(flags);
return ret;
}

EXPORT_SYMBOL(cancel_delayed_work);

/**
 * cancel_delayed_work_sync - cancel a delayed work and wait for it to finish
 * @dwork: the delayed work cancel
 *
 * This is cancel_work_sync() for delayed works.
 *
 * Return:
 * %true if @dwork was pending, %false otherwise.
 */
bool cancel_delayed_work_sync(struct delayed_work *dwork)
{
return __cancel_work_timer(&dwork->work, true);
}
EXPORT_SYMBOL(cancel_delayed_work_sync);

猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/80421246