打印

Linux驱动学习笔记之一——高精度定时器(3)

[复制链接]
2283|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
dianzijiangren|  楼主 | 2012-5-15 13:21 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
前面我们讲了定时器的初始化和打开,下面讲定时器中断相关,这里我们只讲在高分辨率模式下的hrtimer的工作情况。
首先我们假定一个高分辨率时钟已经设置好而且正在运行,而向高分辨率模式的迁移已经完全完成,那么接下来,一般会发生以下情形:负责高分辨率定时器的时钟事件设备引发中断,调用hrtimer_interrupt(struct clock_event_device *dev)作为事件处理程序,该函数负责选中所有到期的定时器,然后直接执行中断程序,当然也可能将其转移到过期链表中(如果他们可以在软中断上下文执行的话——这一段接下来会说)。执行中断程序后,接下来重新对硬件进行编程,说白了有点类似于单片机的清除中断标志,当然这里还做了很多的事情,不光是清楚中断标志,还包括对硬件的重新编程和初始化,使得在下一个待决定时器到期时可以引发中断,然后将引发static void run_hrtimer_softirq(struct softirq_action *h)处理到期链表上所有定时器的中断函数。下面讨论一下相关的代码的特性和机制。首先考虑的是hrtimer_interrupt函数,下面贴上它的源代码(务必注意这里的代码版本是以Linux3.2.12为准):
void hrtimer_interrupt(struct clock_event_device *dev)
{

struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);


ktime_t expires_next, now, entry_time, delta;


int i, retries = 0;


BUG_ON(!cpu_base->hres_active);


cpu_base->nr_events++;


dev->next_event.tv64 = KTIME_MAX;


entry_time = now = ktime_get();

retry:

expires_next.tv64 = KTIME_MAX;


raw_spin_lock(&cpu_base->lock);


cpu_base->expires_next.tv64 = KTIME_MAX;


for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {


struct hrtimer_clock_base *base;


struct timerqueue_node *node;


ktime_t basenow;


if (!(cpu_base->active_bases & (1 << i)))


continue;


base = cpu_base->clock_base + i;


basenow = ktime_add(now, base->offset);


while ((node = timerqueue_getnext(&base->active))) {


struct hrtimer *timer;


timer = container_of(node, struct hrtimer, node);


if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer)) {


ktime_t expires;


expires = ktime_sub(hrtimer_get_expires(timer),



base->offset);


if (expires.tv64 < expires_next.tv64)


expires_next = expires;


break;


}


__run_hrtimer(timer, &basenow);


}


}


cpu_base->expires_next = expires_next;


raw_spin_unlock(&cpu_base->lock);


if (expires_next.tv64 == KTIME_MAX ||



!tick_program_event(expires_next, 0)) {


cpu_base->hang_detected = 0;


return;


}


now = ktime_get();


cpu_base->nr_retries++;


if (++retries < 3)


goto retry;


cpu_base->nr_hangs++;


cpu_base->hang_detected = 1;


delta = ktime_sub(now, entry_time);


if (delta.tv64 > cpu_base->max_hang_time.tv64)


cpu_base->max_hang_time = delta;


if (delta.tv64 > 100 * NSEC_PER_MSEC)


expires_next = ktime_add_ns(now, 100 * NSEC_PER_MSEC);


else


expires_next = ktime_add(now, delta);


tick_program_event(expires_next, 1);


printk_once(KERN_WARNING "hrtimer: interrupt took %llu ns\n",



ktime_to_ns(delta));

}
话说有点长啊……把它分成几部分来看,首先进行的是一些必要的初始化配置:

struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);


ktime_t expires_next, now, entry_time, delta;


int i, retries = 0;


BUG_ON(!cpu_base->hres_active);


cpu_base->nr_events++;


dev->next_event.tv64 = KTIME_MAX;


entry_time = now = ktime_get();

retry:

expires_next.tv64 = KTIME_MAX;


raw_spin_lock(&cpu_base->lock);

以上几行程序就是用来初始化的,接下来,其到期时间保存在expires_next中。最初将该变量设置为KTIME_MAX,是表明没有下一个定时器。函数的主要工作是遍历所有的时基(两种时钟都遍历),代码如下:

for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {


struck hrtimer_clock_base *base;


struct timerqueue_node *node;


ktime_t basenow;


if (!(cpu_base->active_bases & (1 << i)))


continue;


base = cpu_base->clock_base + i;


basenow = ktime_add(now, base->offset);

上面那段代码没什么亮点吧,最后一句basenow就是表示当前时间,base->offset仅在已经重新调整了实时时钟的时候是非零,其他的时候不会影响到单调时钟基础。从base->first开始,即可以获得红黑树中到期的结点,代码如下:

while ((node = timerqueue_getnext(&base->active))) {


struct hrtimer *timer;


timer = container_of(node, struct hrtimer, node);


if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer)) {


ktime_t expires;


expires = ktime_sub(hrtimer_get_expires(timer),



base->offset);


if (expires.tv64 < expires_next.tv64)


expires_next = expires;


break;


}


__run_hrtimer(timer, &basenow);


}

这里面涉及到另一个东西——红黑树,其实在之前我们已经说了,高精度定时器之所以精度高,就是因为定时器引入了二叉树算法,确切点说是红黑树算法,红黑树算饭的算法的源代码文件及位置如下:
Rbtree.c (linux-3.2.12\lib)

Rbtree.h (linux-3.2.12\include\linux)
Rbtree.txt (linux-3.2.12\documentation)
在这里不细细讲解红黑树,只是简单的提一下相关的性质吧:
l
每个结点或是红的或是黑的;

l
根节点是黑的;

l
每个叶子节点(NIL)是黑的;

l
如果一个结点是红的,则他的两个儿子都是黑的;

l
对于每个结点,从该结点到其子孙节点的结点的所有路径上包含相同数目的黑结点。

一个二叉查找树,如果符合上述特点,那么他就是红黑树。说的太玄乎了……其实引入红黑树的目的就是让计时更加精确而已,这里我们只关心以下几个结构体:
struct rb_root
{

struct rb_node *rb_node;

};

struct rb_node
{

unsigned long
rb_parent_color;

#define
RB_RED
0

#define
RB_BLACK
1


struct rb_node *rb_right;


struct rb_node *rb_left;

} __attribute__((aligned(sizeof(long))));
可能有人会诧异,就这俩?!我的答案是:嗯,就这俩。struct rb_root定义树的根,struct rb_node定义树的结点,然后再就是几个常用的API函数,这里就不细细说了,毕竟红黑树涉及的东西太多了。
高精度定时器之所以引入红黑树,就是为了更加精确,这个说起来可能很难理解,我们先说说二叉查找树,二叉查找树有个性质,左孩子的关键字≤父结点≤右孩子的关键字,这样的规定就大大加快了查找的效率,降低了算法的复杂度,同样的,在二叉查找树上再加上一些规定,可以将查找效率更大的提高,这样,可以更加精确的计时,这是高精度定时器引入红黑树的核心思想。
关于红黑树,就说这么些,继续说高精度定时器。
如果下一个定时器到期时间在未来,那么可以停止处理,离开while循环。但需要记住该到期时间,以便在稍后对时钟事件设备重新编程。
如果当前的定时器已经到期,那么在允许在软中断上下文执行处理程序的情况下,会将该定时器移动到回调链表。continue确保了处理代码转向下一个到期的候选定时器;如果不允许在软中断上下文执行定时器的处理程序,那么将直接在硬件中断上下文中执行定时器回调函数。
回调处理程序通过timer->function(timer)来执行(原型在linux-3.2.12\include\linux\ Hrtimer.h中,注意这个大的for循环里面有struct hrtimer *timer;这一句)。如果处理函数返回HRTIMER_RESTART,请求重启定时器,那么通过enqueue_hrtimer来完成该请求。在处理程序已经执行后,可以清除HRTIMER_STATE_CALLBACK标志。在已经选择了所有时钟基础的待决定时器之后,内核需要对时钟事件设备重新编程,以便在下一个定时器到期时依法中断。另外,我们前面说的,中断引发多了,不能同时处理的,就挂到连边上等待,处理完这个,再从链表上弄些一个来处理,直到链表为空。
最后还需要一个步骤,就是引发软中断执行待决定时器的回调函数,该软中断的处理程序是:
static void run_hrtimer_softirq(struct softirq_action *h)
{

hrtimer_peek_ahead_timers();

}
从本质上来看,该函数将遍历所有待决定的链表,hrtimer_peek_ahead_timers();代码如下:
void hrtimer_peek_ahead_timers(void)
{

unsigned long flags;


local_irq_save(flags);


__hrtimer_peek_ahead_timers();


local_irq_restore(flags);

}
hrtimer_peek_ahead_timers()函数的注释里写的很清楚,英文翻译如下:
hrtimer_peek_ahead_timers——run soft-expired timers now现在运行软中断执行待决定时期
hrtimer_peek_ahead_timers会监视目前的cpu定时器队列,检查是否有任何过期的软中断的定时器。如有此类定时器存在,他们立即运行,然后从定时器队列移除。
移除定时器的函数为remove_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base)
大致上,到此已经讲完了了高精度定时器在高精度模式下的整个使用过程。


dianzijiangren


QQ
:991844552

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:终于,我也改行到互联网了

18

主题

558

帖子

3

粉丝