Linux定时器

  1. 内核定时器
    1. 内核定时器编程
      1. 普通定时器
      2. 高精度定时器
  2. 内核延迟
    1. 短延迟
    2. 长延迟
    3. 睡着延迟
    4. Delayed Work

内核定时器

内核定时器编程

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

普通定时器

在linux上针对定时器已经形成了一套统一的标准和接口用于对定时器进行操作。其中关键的数据结构为timer_list。定义如下(linux/timer.h):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
};

其对应的操作接口为:

1
2
3
4
5
6
7
8
9
10
11
12
/* 定时器定义及初始化 */
DEFINE_TIMER(_name, _function);
timer_setup(timer, callback, flags);

/* 增加定时器 */
void add_timer(struct timer_list *timer);

/* 删除定时器 */
int del_timer(struct timer_list * timer);

/* 修改定时器的expire */
int mod_timer(struct timer_list *timer, unsigned long expires);

有了如上这些基本定时器操作接口,那么我们就可以对定时器进行编程,对于内核编程而言,通常都有其固定的模式,如下为使用普通定时器的模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct xxx_dev {
struct cdev cdev;
...
timer_list xxx_timer;
};

xxx_funcl(...)
{
struct xxx_dev *dev = filp->private_data;
...
timer_setup(&dev->xxx_timer, &xxx_do_timer, 0);
mod_timer(&dev->xxx_timer, jiffies + delay);
add_timer(&dev->xxx_timer);
...
}

xxx_func2(...)
{
...
del_timer(&dev->xxx_timer);
...
}

static void xxx_do_timer(struct timer_list *timer)
{
struct xxx_dev *dev = from_timer(dev, timer, xxx_timer);
...
mod_timer(&dev->xxx_timer, jiffies + delay);
}

高精度定时器

数据结构定义及接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 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)
* @is_rel: Set if the timer was armed relative
* @is_soft: Set if hrtimer will be expired in soft interrupt context.
* @is_hard: Set if hrtimer will be expired in hard interrupt context
* even on RT.
*
* 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;
u8 state;
u8 is_rel;
u8 is_soft;
u8 is_hard;
};

struct hrtimer_sleeper {
struct hrtimer timer;
struct task_struct *task;
};

/* 定时器初始化 */
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,
enum hrtimer_mode mode);
void hrtimer_start(struct hrtimer *timer, ktime_t tim,
const enum hrtimer_mode mode);
int hrtimer_cancel(struct hrtimer *timer);
bool hrtimer_active(const struct hrtimer *timer);

而高精度定时器的使用与普通定时器类似,它们的作用都是为了设置定时任务。

内核延迟

短延迟

linux内核中分别提供了纳秒、微秒和毫秒的延迟接口:

1
2
3
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

这几个接口的实现原理本质上是忙等,需要消耗CPU资源。因此,在内核中最好不要直接使用mdelay()函数,对于毫秒级以上的时延,内核提供了如下函数:

1
2
3
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);

这几个接口在调用后会让出cpu资源,直到延迟时间到来。

长延迟

在内核中进行延迟的一个很直观的方法是比较当前的jiffies和目标jiffies,直到未来的jiffies达到目标jiffies。如下为忙等时延的示例:

1
2
3
4
5
6
7
/* 延迟100个jiffies */
unsigned long delay = jiffies + 100;
while (time_before(jiffies, delay));

/* 再延迟2s */
unsigned long delay = jiffies + 2*Hz;
while(time_before(jiffies, delay));

其中jiffies再内核中被定义为volaile变量,器目的为避免在比较时产生读合并而造成错误。

睡着延迟

睡着延迟是比忙等延迟更好的一种方式,其原理是在等待时间到来之前让进程进入睡眠状态,让出CPU资源,schedule_timeout()可以使当前任务休眠指定的jiffies之后再重新被调度执行。msleep()和msleep_interruptible()都是依靠schedule_timeout()来实现的。

而schedule_timeout()的实现原理则是想系统中添加一个定时器,在定时器处理函数中唤醒其对应的进程

而睡着延迟还有一种实现是依靠等待队列,可以依靠如:

1
wait_event_killable_timeout(wq_head, condition, timeout);

这样的接口来将进程加入等待队列,并让出cpu资源,具体可参考linux/wait.h,其中包含了关于等待队列运用与操作的各种接口。

Delayed Work

对于周期性的任务,除了单独用定时器做延迟外,在Linux内核中还可以利用一套封装好的快捷机制,其本质就是利用工作队列和定时器实现,这套机制叫做delayed work。

如下为delayed work的数据结构定义及接口定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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;
};

typedef void (*work_func_t)(struct work_struct *work);

DECLARE_DELAYED_WORK(n, f);
INIT_DELAYED_WORK(_work, _func);

void destroy_delayed_work_on_stack(struct delayed_work *work);

bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq,
struct delayed_work *work, unsigned long delay);
bool queue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork,
unsigned long delay)

bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq,
struct delayed_work *dwork, unsigned long delay);
bool mod_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork,
unsigned long delay);

bool flush_delayed_work(struct delayed_work *dwork);

bool cancel_delayed_work(struct delayed_work *dwork);
bool cancel_delayed_work_sync(struct delayed_work *dwork);

bool schedule_delayed_work_on(int cpu, struct delayed_work *dwork,
unsigned long delay);
bool schedule_delayed_work(struct delayed_work *dwork,
unsigned long delay);

我们可以通过schedule_delayed_work函数调度一个delayed work在指定延时后执行,当指定的delay到来时,delayed work结构体中的work成员work_func_t类型成员func()会被执行。其中delay参数的单位是jiffies,因此一种常见的用法如下:

1
schedule_delayed_work(&work, msecs_to_jiffies(poll_interval));

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


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com

文章标题:Linux定时器

本文作者:红尘追风

发布时间:2016-05-26, 20:19:43

原始链接:http://www.micernel.com/2016/05/26/Linux%E5%AE%9A%E6%97%B6%E5%99%A8/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录