转自:http://blog.csdn.net/beyondioi/article/details/9212795

1.hrtimers - 为高分辨率kernel定时器,可作为超时或周期性定时器使用

1). hrtimer_init初始化定时器工作模式。

hrtimer_init(&vibe_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
vibe_timer.function = timer_func;

/* 设置定时器的回调函数,定时器到时该函数将被调用 */

static enum hrtimer_restart timer_func(struct hrtimer *timer)

注:该回调函数为原子操作不能被中断

2). hrtimer_start的第二个参数用于设置超时参数。
  hrtimer_start(&vibe_timer,
  ktime_set(value / 1000, (value % 1000) * 1000000),HRTIMER_MODE_REL);

3).int hrtimer_cancel(struct hrtimer *timer);

要取消一个hrtimer,使用hrtimer_cancel:

static enum hrtimer_restart timer_func(struct hrtimer *timer)
{

hrtimer_start(&vibe_timer,
ktime_set(value / 1000, (value % 1000) * 1000000),HRTIMER_MODE_REL);

}

static int __init ker_driver_init(void)
{

int value = 2000; /* Time out setting,2 seconds */
struct timespec uptime;
KER_PRINT("ker_driver_init/n");
hrtimer_init(&vibe_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
vibe_timer.function = timer_func;
hrtimer_start(&vibe_timer,
ktime_set(value / 1000, (value % 1000) * 1000000),HRTIMER_MODE_REL);
}

关于krtimer的详细资料可以查看如下博客:
Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现
来源:http://stevenysb.blog.163.com/blog/static/165402497201301491721471/
-----------------------------------------------------------------------------------------

Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现

上一篇文章,我介绍了传统的低分辨率定时器的实现原理。而随着内核的不断演进,大牛们已经对这种低分辨率定时器的精度不再满足,而且,硬件也在不断地发展,系统中的定时器硬件的精度也越来越高,这也给高分辨率定时器的出现创造了条件。内核从2.6.16开始加入了高精度定时器架构。在实现方式上,内核的高分辨率定时器的实现代码几乎没有借用低分辨率定时器的数据结构和代码,内核文档给出的解释主要有以下几点:
 
  • 低分辨率定时器的代码和jiffies的关系太过紧密,并且默认按32位进行设计,并且它的代码已经经过长时间的优化,目前的使用也是没有任何错误,如果硬要基于它来实现高分辨率定时器,势必会打破原有的时间轮概念,并且会引入一大堆#if--#else判断;
  • 虽然大部分时间里,时间轮可以实现O(1)时间复杂度,但是当有进位发生时,不可预测的O(N)定时器级联迁移时间,这对于低分辨率定时器来说问题不大,可是它大大地影响了定时器的精度;
  • 低分辨率定时器几乎是为“超时”而设计的,并为此对它进行了大量的优化,对于这些以“超时”未目的而使用定时器,它们大多数期望在超时到来之前获得正确的结果,然后删除定时器,精确时间并不是它们主要的目的,例如网络通信、设备IO等等。
 
为此,内核为高精度定时器重新设计了一套软件架构,它可以为我们提供纳秒级的定时精度,以满足对精确时间有迫切需求的应用程序或内核驱动,例如多媒体应用,音频设备的驱动程序等等。以下的讨论用hrtimer(high resolution timer)表示高精度定时器。
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

1. 如何组织hrtimer?

我们知道,低分辨率定时器使用5个链表数组来组织timer_list结构,形成了著名的时间轮概念,对于高分辨率定时器,我们期望组织它们的数据结构至少具备以下条件:
 
  • 稳定而且快速的查找能力;
  • 快速地插入和删除定时器的能力;
  • 排序功能;
 
内核的开发者考察了多种数据结构,例如基数树、哈希表等等,最终他们选择了红黑树(rbtree)来组织hrtimer,红黑树已经以库的形式存在于内核中,并被成功地使用在内存管理子系统和文件系统中,随着系统的运行,hrtimer不停地被创建和销毁,新的hrtimer按顺序被插入到红黑树中,树的最左边的节点就是最快到期的定时器,内核用一个hrtimer结构来表示一个高精度定时器:
 
  1. struct hrtimer {
  2. struct timerqueue_node node;
  3. ktime_t _softexpires;
  4. enum hrtimer_restart (*function)(struct hrtimer *);
  5. struct hrtimer_clock_base *base;
  6. unsigned long state;
  7. ......
  8. };
定时器的到期时间用ktime_t来表示,_softexpires字段记录了时间,定时器一旦到期,function字段指定的回调函数会被调用,该函数的返回值为一个枚举值,它决定了该hrtimer是否需要被重新激活:
 
 
  1. enum hrtimer_restart {
  2. HRTIMER_NORESTART, /* Timer is not restarted */
  3. HRTIMER_RESTART, /* Timer must be restarted */
  4. };
state字段用于表示hrtimer当前的状态,有几下几种位组合:
 
 
  1. #define HRTIMER_STATE_INACTIVE 0x00 // 定时器未激活
  2. #define HRTIMER_STATE_ENQUEUED 0x01 // 定时器已经被排入红黑树中
  3. #define HRTIMER_STATE_CALLBACK 0x02 // 定时器的回调函数正在被调用
  4. #define HRTIMER_STATE_MIGRATE 0x04 // 定时器正在CPU之间做迁移
hrtimer的到期时间可以基于以下几种时间基准系统:
 
 
  1. enum hrtimer_base_type {
  2. HRTIMER_BASE_MONOTONIC, // 单调递增的monotonic时间,不包含休眠时间
  3. HRTIMER_BASE_REALTIME, // 平常使用的墙上真实时间
  4. HRTIMER_BASE_BOOTTIME, // 单调递增的boottime,包含休眠时间
  5. HRTIMER_MAX_CLOCK_BASES, // 用于后续数组的定义
  6. };
和低分辨率定时器一样,处于效率和上锁的考虑,每个cpu单独管理属于自己的hrtimer,为此,专门定义了一个结构hrtimer_cpu_base:
 
 
  1. struct hrtimer_cpu_base {
  2. ......
  3. struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES];
  4. };
其中,clock_base数组为每种时间基准系统都定义了一个hrtimer_clock_base结构,它的定义如下:
 
 
  1. struct hrtimer_clock_base {
  2. struct hrtimer_cpu_base *cpu_base; // 指向所属cpu的hrtimer_cpu_base结构
  3. ......
  4. struct timerqueue_head active; // 红黑树,包含了所有使用该时间基准系统的hrtimer
  5. ktime_t resolution; // 时间基准系统的分辨率
  6. ktime_t (*get_time)(void); // 获取该基准系统的时间函数
  7. ktime_t softirq_time;// 当用jiffies
  8. ktime_t offset; //
  9. };
active字段是一个timerqueue_head结构,它实际上是对rbtree的进一步封装:
  1. struct timerqueue_node {
  2. struct rb_node node; // 红黑树的节点
  3. ktime_t expires; // 该节点代表队hrtimer的到期时间,与hrtimer结构中的_softexpires稍有不同
  4. };
  5. struct timerqueue_head {
  6. struct rb_root head; // 红黑树的根节点
  7. struct timerqueue_node *next; // 该红黑树中最早到期的节点,也就是最左下的节点
  8. };
timerqueue_head结构在红黑树的基础上,增加了一个next字段,用于保存树中最先到期的定时器节点,实际上就是树的最左下方的节点,有了next字段,当到期事件到来时,系统不必遍历整个红黑树,只要取出next字段对应的节点进行处理即可。timerqueue_node用于表示一个hrtimer节点,它在标准红黑树节点rb_node的基础上增加了expires字段,该字段和hrtimer中的_softexpires字段一起,设定了hrtimer的到期时间的一个范围,hrtimer可以在hrtimer._softexpires至timerqueue_node.expires之间的任何时刻到期,我们也称timerqueue_node.expires为硬过期时间(hard),意思很明显:到了此时刻,定时器一定会到期,有了这个范围可以选择,定时器系统可以让范围接近的多个定时器在同一时刻同时到期,这种设计可以降低进程频繁地被hrtimer进行唤醒。经过以上的讨论,我们可以得出以下的图示,它表明了每个cpu上的hrtimer是如何被组织在一起的:

                                                       图 1.1  每个cpu的hrtimer组织结构
总结一下:
 
  • 每个cpu有一个hrtimer_cpu_base结构;
  • hrtimer_cpu_base结构管理着3种不同的时间基准系统的hrtimer,分别是:实时时间,启动时间和单调时间;
  • 每种时间基准系统通过它的active字段(timerqueue_head结构指针),指向它们各自的红黑树;
  • 红黑树上,按到期时间进行排序,最先到期的hrtimer位于最左下的节点,并被记录在active.next字段中;
  • 3中时间基准的最先到期时间可能不同,所以,它们之中最先到期的时间被记录在hrtimer_cpu_base的expires_next字段中。
 

2. hrtimer如何运转

hrtimer的实现需要一定的硬件基础,它的实现依赖于我们前几章介绍的timekeeper和clock_event_device,如果你对timekeeper和clock_event_device不了解请参考以下文章:Linux时间子系统之三:时间的维护者:timekeeperLinux时间子系统之四:定时器的引擎:clock_event_device。hrtimer系统需要通过timekeeper获取当前的时间,计算与到期时间的差值,并根据该差值,设定该cpu的tick_device(clock_event_device)的下一次的到期时间,时间一到,在clock_event_device的事件回调函数中处理到期的hrtimer。现在你或许有疑问:前面在介绍clock_event_device时,我们知道,每个cpu有自己的tick_device,通常用于周期性地产生进程调度和时间统计的tick事件,这里又说要用tick_device调度hrtimer系统,通常cpu只有一个tick_device,那他们如何协调工作?这个问题也一度困扰着我,如果再加上NO_HZ配置带来tickless特性,你可能会更晕。这里我们先把这个疑问放下,我将在后面的章节中来讨论这个问题,现在我们只要先知道,一旦开启了hrtimer,tick_device所关联的clock_event_device的事件回调函数会被修改为:hrtimer_interrupt,并且会被设置成工作于CLOCK_EVT_MODE_ONESHOT单触发模式。
2.1 添加一个hrtimer
要添加一个hrtimer,系统提供了一些api供我们使用,首先我们需要定义一个hrtimer结构的实例,然后用hrtimer_init函数对它进行初始化,它的原型如下:
 
  1. void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,
  2. enum hrtimer_mode mode);
which_clock可以是CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_BOOTTIME中的一种,mode则可以是相对时间HRTIMER_MODE_REL,也可以是绝对时间HRTIMER_MODE_ABS。设定回调函数:
  1. timer.function = hr_callback;
如果定时器无需指定一个到期范围,可以在设定回调函数后直接使用hrtimer_start激活该定时器:
 
  1. int hrtimer_start(struct hrtimer *timer, ktime_t tim,
  2. const enum hrtimer_mode mode);
如果需要指定到期范围,则可以使用hrtimer_start_range_ns激活定时器:
 
 
  1. hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
  2. unsigned long range_ns, const enum hrtimer_mode mode);
要取消一个hrtimer,使用hrtimer_cancel:
 
 
  1. int hrtimer_cancel(struct hrtimer *timer);
以下两个函数用于推后定时器的到期时间:
 
 
  1. extern u64
  2. hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval);
  3. /* Forward a hrtimer so it expires after the hrtimer's current now */
  4. static inline u64 hrtimer_forward_now(struct hrtimer *timer,
  5. ktime_t interval)
  6. {
  7. return hrtimer_forward(timer, timer->base->get_time(), interval);
  8. }
以下几个函数用于获取定时器的当前状态:
 
 
  1. static inline int hrtimer_active(const struct hrtimer *timer)
  2. {
  3. return timer->state != HRTIMER_STATE_INACTIVE;
  4. }
  5. static inline int hrtimer_is_queued(struct hrtimer *timer)
  6. {
  7. return timer->state & HRTIMER_STATE_ENQUEUED;
  8. }
  9. static inline int hrtimer_callback_running(struct hrtimer *timer)
  10. {
  11. return timer->state & HRTIMER_STATE_CALLBACK;
  12. }
hrtimer_init最终会进入__hrtimer_init函数,该函数的主要目的是初始化hrtimer的base字段,同时初始化作为红黑树的节点的node字段:
 
 
  1. static void __hrtimer_init(struct hrtimer *timer, clockid_t clock_id,
  2. enum hrtimer_mode mode)
  3. {
  4. struct hrtimer_cpu_base *cpu_base;
  5. int base;
  6. memset(timer, 0, sizeof(struct hrtimer));
  7. cpu_base = &__raw_get_cpu_var(hrtimer_bases);
  8. if (clock_id == CLOCK_REALTIME && mode != HRTIMER_MODE_ABS)
  9. clock_id = CLOCK_MONOTONIC;
  10. base = hrtimer_clockid_to_base(clock_id);
  11. timer->base = &cpu_base->clock_base[base];
  12. timerqueue_init(&timer->node);
  13. ......
  14. }
 
hrtimer_start和hrtimer_start_range_ns最终会把实际的工作交由__hrtimer_start_range_ns来完成:
  1. int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
  2. unsigned long delta_ns, const enum hrtimer_mode mode,
  3. int wakeup)
  4. {
  5. ......
  6. /* 取得hrtimer_clock_base指针 */
  7. base = lock_hrtimer_base(timer, &flags);
  8. /* 如果已经在红黑树中,先移除它: */
  9. ret = remove_hrtimer(timer, base); ......
  10. /* 如果是相对时间,则需要加上当前时间,因为内部是使用绝对时间 */
  11. if (mode & HRTIMER_MODE_REL) {
  12. tim = ktime_add_safe(tim, new_base->get_time());
  13. ......
  14. }
  15. /* 设置到期的时间范围 */
  16. hrtimer_set_expires_range_ns(timer, tim, delta_ns);
  17. ......
  18. /* 把hrtime按到期时间排序,加入到对应时间基准系统的红黑树中 */
  19. /* 如果该定时器的是最早到期的,将会返回true */
  20. leftmost = enqueue_hrtimer(timer, new_base);
  21. /*
  22. * Only allow reprogramming if the new base is on this CPU.
  23. * (it might still be on another CPU if the timer was pending)
  24. *
  25. * XXX send_remote_softirq() ?
  26. * 定时器比之前的到期时间要早,所以需要重新对tick_device进行编程,重新设定的的到期时间
  27. */
  28. if (leftmost && new_base->cpu_base == &__get_cpu_var(hrtimer_bases))
  29. hrtimer_enqueue_reprogram(timer, new_base, wakeup);
  30. unlock_hrtimer_base(timer, &flags);
  31. return ret;
  32. }
  33. <p>
  34. </p>
2.2 hrtimer的到期处理
高精度定时器系统有3个入口可以对到期定时器进行处理,它们分别是:
 
  • 没有切换到高精度模式时,在每个jiffie的tick事件中断中进行查询和处理;
  • 在HRTIMER_SOFTIRQ软中断中进行查询和处理;
  • 切换到高精度模式后,在每个clock_event_device的到期事件中断中进行查询和处理;
 
低精度模式  因为系统并不是一开始就会支持高精度模式,而是在系统启动后的某个阶段,等待所有的条件都满足后,才会切换到高精度模式,当系统还没有切换到高精度模式时,所有的高精度定时器运行在低精度模式下,在每个jiffie的tick事件中断中进行到期定时器的查询和处理,显然这时候的精度和低分辨率定时器是一样的(HZ级别)。低精度模式下,每个tick事件中断中,hrtimer_run_queues函数会被调用,由它完成定时器的到期处理。hrtimer_run_queues首先判断目前高精度模式是否已经启用,如果已经切换到了高精度模式,什么也不做,直接返回:
 
  1. void hrtimer_run_queues(void)
  2. {
  3. if (hrtimer_hres_active())
  4. return;
如果hrtimer_hres_active返回false,说明目前处于低精度模式下,则继续处理,它用一个for循环遍历各个时间基准系统,查询每个hrtimer_clock_base对应红黑树的左下节点,判断它的时间是否到期,如果到期,通过__run_hrtimer函数,对到期定时器进行处理,包括:调用定时器的回调函数、从红黑树中移除该定时器、根据回调函数的返回值决定是否重新启动该定时器等等:
 
 
  1. for (index = 0; index < HRTIMER_MAX_CLOCK_BASES; index++) {
  2. base = &cpu_base->clock_base[index];
  3. if (!timerqueue_getnext(&base->active))
  4. continue;
  5. if (gettime) {
  6. hrtimer_get_softirq_time(cpu_base);
  7. gettime = 0;
  8. }
  9. raw_spin_lock(&cpu_base->lock);
  10. while ((node = timerqueue_getnext(&base->active))) {
  11. struct hrtimer *timer;
  12. timer = container_of(node, struct hrtimer, node);
  13. if (base->softirq_time.tv64 <=
  14. hrtimer_get_expires_tv64(timer))
  15. break;
  16. __run_hrtimer(timer, &base->softirq_time);
  17. }
  18. raw_spin_unlock(&cpu_base->lock);
  19. }
上面的timerqueue_getnext函数返回红黑树中的左下节点,之所以可以在while循环中使用该函数,是因为__run_hrtimer会在移除旧的左下节点时,新的左下节点会被更新到base->active->next字段中,使得循环可以继续执行,直到没有新的到期定时器为止。

高精度模式  切换到高精度模式后,原来给cpu提供tick事件的tick_device(clock_event_device)会被高精度定时器系统接管,它的中断事件回调函数被设置为hrtimer_interrupt,红黑树中最左下的节点的定时器的到期时间被编程到该clock_event_device中,这样每次clock_event_device的中断意味着至少有一个高精度定时器到期。另外,当timekeeper系统中的时间需要修正,或者clock_event_device的到期事件时间被重新编程时,系统会发出HRTIMER_SOFTIRQ软中断,软中断的处理函数run_hrtimer_softirq最终也会调用hrtimer_interrupt函数对到期定时器进行处理,所以在这里我们只要讨论hrtimer_interrupt函数的实现即可。
 
hrtimer_interrupt函数的前半部分和低精度模式下的hrtimer_run_queues函数完成相同的事情:它用一个for循环遍历各个时间基准系统,查询每个hrtimer_clock_base对应红黑树的左下节点,判断它的时间是否到期,如果到期,通过__run_hrtimer函数,对到期定时器进行处理,所以我们只讨论后半部分,在处理完所有到期定时器后,下一个到期定时器的到期时间保存在变量expires_next中,接下来的工作就是把这个到期时间编程到tick_device中:
 
  1. void hrtimer_interrupt(struct clock_event_device *dev)
  2. {
  3. ......
  4. for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {
  5. ......
  6. while ((node = timerqueue_getnext(&base->active))) {
  7. ......
  8. if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer)) {
  9. ktime_t expires;
  10. expires = ktime_sub(hrtimer_get_expires(timer),
  11. base->offset);
  12. if (expires.tv64 < expires_next.tv64)
  13. expires_next = expires;
  14. break;
  15. }
  16. __run_hrtimer(timer, &basenow);
  17. }
  18. }
  19. /*
  20. * Store the new expiry value so the migration code can verify
  21. * against it.
  22. */
  23. cpu_base->expires_next = expires_next;
  24. raw_spin_unlock(&cpu_base->lock);
  25. /* Reprogramming necessary ? */
  26. if (expires_next.tv64 == KTIME_MAX ||
  27. !tick_program_event(expires_next, 0)) {
  28. cpu_base->hang_detected = 0;
  29. return;
  30. }
如果这时的tick_program_event返回了非0值,表示过期时间已经在当前时间的前面,这通常由以下原因造成:
 
 
  • 系统正在被调试跟踪,导致时间在走,程序不走;
  • 定时器的回调函数花了太长的时间;
  • 系统运行在虚拟机中,而虚拟机被调度导致停止运行;
为了避免这些情况的发生,接下来系统提供3次机会,重新执行前面的循环,处理到期的定时器:
 
 
  1. raw_spin_lock(&cpu_base->lock);
  2. now = hrtimer_update_base(cpu_base);
  3. cpu_base->nr_retries++;
  4. if (++retries < 3)
  5. goto retry;
如果3次循环后还无法完成到期处理,系统不再循环,转为计算本次总循环的时间,然后把tick_device的到期时间强制设置为当前时间加上本次的总循环时间,不过推后的时间被限制在100ms以内:
 
 
  1. delta = ktime_sub(now, entry_time);
  2. if (delta.tv64 > cpu_base->max_hang_time.tv64)
  3. cpu_base->max_hang_time = delta;
  4. /*
  5. * Limit it to a sensible value as we enforce a longer
  6. * delay. Give the CPU at least 100ms to catch up.
  7. */
  8. if (delta.tv64 > 100 * NSEC_PER_MSEC)
  9. expires_next = ktime_add_ns(now, 100 * NSEC_PER_MSEC);
  10. else
  11. expires_next = ktime_add(now, delta);
  12. tick_program_event(expires_next, 1);
  13. printk_once(KERN_WARNING "hrtimer: interrupt took %llu ns\n",
  14. ktime_to_ns(delta));
  15. }
 

3. 切换到高精度模式

上面提到,尽管内核配置成支持高精度定时器,但并不是一开始就工作于高精度模式,系统在启动的开始阶段,还是按照传统的模式在运行:tick_device按HZ频率定期地产生tick事件,这时的hrtimer工作在低分辨率模式,到期事件在每个tick事件中断中由hrtimer_run_queues函数处理,同时,在低分辨率定时器(时间轮)的软件中断TIMER_SOFTIRQ中,hrtimer_run_pending会被调用,系统在这个函数中判断系统的条件是否满足切换到高精度模式,如果条件满足,则会切换至高分辨率模式,另外提一下,NO_HZ模式也是在该函数中判断并切换。
 
  1. void hrtimer_run_pending(void)
  2. {
  3. if (hrtimer_hres_active())
  4. return;
  5. ......
  6. if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))
  7. hrtimer_switch_to_hres();
  8. }
因为不管系统是否工作于高精度模式,每个TIMER_SOFTIRQ期间,该函数都会被调用,所以函数一开始先用hrtimer_hres_active判断目前高精度模式是否已经激活,如果已经激活,则说明之前的调用中已经切换了工作模式,不必再次切换,直接返回。hrtimer_hres_active很简单:
 
 
  1. DEFINE_PER_CPU(struct hrtimer_cpu_base, hrtimer_bases) = {
  2. ......
  3. }
  4. static inline int hrtimer_hres_active(void)
  5. {
  6. return __this_cpu_read(hrtimer_bases.hres_active);
  7. }
hrtimer_run_pending函数接着通过tick_check_oneshot_change判断系统是否可以切换到高精度模式,
 
 
  1. int tick_check_oneshot_change(int allow_nohz)
  2. {
  3. struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
  4. if (!test_and_clear_bit(0, &ts->check_clocks))
  5. return 0;
  6. if (ts->nohz_mode != NOHZ_MODE_INACTIVE)
  7. return 0;
  8. if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
  9. return 0;
  10. if (!allow_nohz)
  11. return 1;
  12. tick_nohz_switch_to_nohz();
  13. return 0;
  14. }
函数的一开始先判断check_clock标志的第0位是否被置位,如果没有置位,说明系统中没有注册符合要求的时钟事件设备,函数直接返回,check_clock标志由clocksource和clock_event_device系统的notify系统置位,当系统中有更高精度的clocksource被注册和选择后,或者有更精确的支持CLOCK_EVT_MODE_ONESHOT模式的clock_event_device被注册时,通过它们的notify函数,check_clock标志的第0为会置位。
 
如果tick_sched结构中的nohz_mode字段不是NOHZ_MODE_INACTIVE,表明系统已经切换到其它模式,直接返回。nohz_mode的取值有3种:
 
  • NOHZ_MODE_INACTIVE // 未启用NO_HZ模式
  • NOHZ_MODE_LOWRES // 启用NO_HZ模式,hrtimer工作于低精度模式下
  • NOHZ_MODE_HIGHRES // 启用NO_HZ模式,hrtimer工作于高精度模式下
接下来的timerkeeping_valid_for_hres判断timekeeper系统是否支持高精度模式,tick_is_oneshot_available判断tick_device是否支持CLOCK_EVT_MODE_ONESHOT模式。如果都满足要求,则继续往下判断。allow_nohz是函数的参数,为true表明可以切换到NOHZ_MODE_LOWRES 模式,函数将进入tick_nohz_switch_to_nohz,切换至NOHZ_MODE_LOWRES 模式,这里我们传入的allow_nohz是表达式:
 

(!hrtimer_is_hres_enabled())

所以当系统不允许高精度模式时,将会在tick_check_oneshot_change函数内,通过tick_nohz_switch_to_nohz切换至NOHZ_MODE_LOWRES 模式,如果系统允许高精度模式,传入的allow_nohz参数为false,tick_check_oneshot_change函数返回1,回到上面的hrtimer_run_pending函数,hrtimer_switch_to_hres函数将会被调用,已完成切换到NOHZ_MODE_HIGHRES高精度模式。好啦,真正的切换函数找到了,我们看一看它如何切换:
首先,它通过hrtimer_cpu_base中的hres_active字段判断该cpu是否已经切换至高精度模式,如果是则直接返回:
 
  1. static int hrtimer_switch_to_hres(void)
  2. {
  3. int i, cpu = smp_processor_id();
  4. struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu);
  5. unsigned long flags;
  6. if (base->hres_active)
  7. return 1;
接着,通过tick_init_highres函数接管tick_device关联的clock_event_device:
 
 
  1. local_irq_save(flags);
  2. if (tick_init_highres()) {
  3. local_irq_restore(flags);
  4. printk(KERN_WARNING "Could not switch to high resolution "
  5. "mode on CPU %d\n", cpu);
  6. return 0;
  7. }
tick_init_highres函数把tick_device切换到CLOCK_EVT_FEAT_ONESHOT模式,同时把clock_event_device的回调handler设置为hrtimer_interrupt,这样设置以后,tick_device的中断回调将由hrtimer_interrupt接管,hrtimer_interrupt在上面已经讨论过,它将完成高精度定时器的调度和到期处理。
 
接着,设置hres_active标志,以表明高精度模式已经切换,然后把3个时间基准系统的resolution字段设为KTIME_HIGH_RES:
 
  1. base->hres_active = 1;
  2. for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++)
  3. base->clock_base[i].resolution = KTIME_HIGH_RES;
最后,因为tick_device被高精度定时器接管,它将不会再提供原有的tick事件机制,所以需要由高精度定时器系统模拟一个tick事件设备,继续为系统提供tick事件能力,这个工作由tick_setup_sched_timer函数完成。因为刚刚完成切换,tick_device的到期时间并没有被正确地设置为下一个到期定时器的时间,这里使用retrigger_next_event函数,传入参数NULL,使得tick_device立刻产生到期中断,hrtimer_interrupt被调用一次,然后下一个到期的定时器的时间会编程到tick_device中,从而完成了到高精度模式的切换:
  1. tick_setup_sched_timer();
  2. /* "Retrigger" the interrupt to get things going */
  3. retrigger_next_event(NULL);
  4. local_irq_restore(flags);
  5. return 1;
 
整个切换过程可以用下图表示:

                                                                             图3.1  低精度模式切换至高精度模式

4. 模拟tick事件

根据上一节的讨论,当系统切换到高精度模式后,tick_device被高精度定时器系统接管,不再定期地产生tick事件,我们知道,到目前的版本为止(V3.4),内核还没有彻底废除jiffies机制,系统还是依赖定期到来的tick事件,供进程调度系统和时间更新等操作,大量存在的低精度定时器也仍然依赖于jiffies的计数,所以,尽管tick_device被接管,高精度定时器系统还是要想办法继续提供定期的tick事件。为了达到这一目的,内核使用了一个取巧的办法:既然高精度模式已经启用,可以定义一个hrtimer,把它的到期时间设定为一个jiffy的时间,当这个hrtimer到期时,在这个hrtimer的到期回调函数中,进行和原来的tick_device同样的操作,然后把该hrtimer的到期时间顺延一个jiffy周期,如此反复循环,完美地模拟了原有tick_device的功能。下面我们看看具体点代码是如何实现的。
在kernel/time/tick-sched.c中,内核定义了一个per_cpu全局变量:tick_cpu_sched,从而为每个cpu提供了一个tick_sched结构, 该结构主要用于管理NO_HZ配置下的tickless处理,因为模拟tick事件与tickless有很强的相关性,所以高精度定时器系统也利用了该结构的以下字段,用于完成模拟tick事件的操作:
 
  1. struct tick_sched {
  2. struct hrtimer sched_timer;
  3. unsigned long check_clocks;
  4. enum tick_nohz_mode nohz_mode;
  5. ......
  6. };
sched_timer就是要用于模拟tick事件的hrtimer,check_clock上面几节已经讨论过,用于notify系统通知hrtimer系统需要检查是否切换到高精度模式,nohz_mode则用于表示当前的工作模式。
 
上一节提到,用于切换至高精度模式的函数是hrtimer_switch_to_hres,在它的最后,调用了函数tick_setup_sched_timer,该函数的作用就是设置一个用于模拟tick事件的hrtimer:
 
  1. void tick_setup_sched_timer(void)
  2. {
  3. struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
  4. ktime_t now = ktime_get();
  5. /*
  6. * Emulate tick processing via per-CPU hrtimers:
  7. */
  8. hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
  9. ts->sched_timer.function = tick_sched_timer;
  10. /* Get the next period (per cpu) */
  11. hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());
  12. for (;;) {
  13. hrtimer_forward(&ts->sched_timer, now, tick_period);
  14. hrtimer_start_expires(&ts->sched_timer,
  15. HRTIMER_MODE_ABS_PINNED);
  16. /* Check, if the timer was already in the past */
  17. if (hrtimer_active(&ts->sched_timer))
  18. break;
  19. now = ktime_get();
  20. }
  21. #ifdef CONFIG_NO_HZ
  22. if (tick_nohz_enabled)
  23. ts->nohz_mode = NOHZ_MODE_HIGHRES;
  24. #endif
  25. }
该函数首先初始化该cpu所属的tick_sched结构中sched_timer字段,把该hrtimer的回调函数设置为tick_sched_timer,然后把它的到期时间设定为下一个jiffy时刻,返回前把工作模式设置为NOHZ_MODE_HIGHRES,表明是利用高精度模式实现NO_HZ。
 
接着我们关注一下hrtimer的回调函数tick_sched_timer,我们知道,系统中的jiffies计数,时间更新等是全局操作,在smp系统中,只有一个cpu负责该工作,所以在tick_sched_timer的一开始,先判断当前cpu是否负责更新jiffies和时间,如果是,则执行更新操作:
 
  1. static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer)
  2. {
  3. ......
  4. #ifdef CONFIG_NO_HZ
  5. if (unlikely(tick_do_timer_cpu == TICK_DO_TIMER_NONE))
  6. tick_do_timer_cpu = cpu;
  7. #endif
  8. /* Check, if the jiffies need an update */
  9. if (tick_do_timer_cpu == cpu)
  10. tick_do_update_jiffies64(now);
然后,利用regs指针确保当前是在中断上下文中,然后调用update_process_timer:
 
 
  1. if (regs) {
  2. ......
  3. update_process_times(user_mode(regs));
  4. ......
  5. }
最后,把hrtimer的到期时间推进一个tick周期,返回HRTIMER_RESTART表明该hrtimer需要再次启动,以便产生下一个tick事件。
 
 
  1. hrtimer_forward(timer, now, tick_period);
  2. return HRTIMER_RESTART;
  3. }
关于update_process_times,如果你你感兴趣,回看一下本系列关于clock_event_device的那一章:Linux时间子系统之四:定时器的引擎:clock_event_device中的第5小节,对比一下模拟tick事件的hrtimer的回调函数tick_sched_timer和切换前tick_device的回调函数tick_handle_periodic,它们是如此地相像,实际上,它们几乎完成了一样的工作。
来源:http://blog.csdn.net/droidphone/article/details/8074892

hrtimer的简单使用 + 原理和实现【转】的更多相关文章

  1. .NET程序的简单编译原理

    1.不管是什么程序,最终的执行官是CPU,而CPU只认识1和0的机器码. 2.我们现在写的一般是高级语言写的程序.CPU是不认识我们用高级语言写的源代码的,那应该怎么办才能让CPU执行我们写好的程序尼 ...

  2. Support Vector Machine (1) : 简单SVM原理

    目录 Support Vector Machine (1) : 简单SVM原理 Support Vector Machine (2) : Sequential Minimal Optimization ...

  3. 【转】MyBatis接口的简单实现原理

    MyBatis接口的简单实现原理 用过MyBatis3的人可能会觉得为什么MyBatis的Mapper接口没有实现类,但是可以直接用? 那是因为MyBatis使用Java动态代理实现的接口. 这里仅仅 ...

  4. MyBatis接口的简单实现原理

    MyBatis接口的简单实现原理 用过MyBatis3的人可能会觉得为什么MyBatis的Mapper接口没有实现类,但是可以直接用? 那是因为MyBatis使用Java动态代理实现的接口. 这里仅仅 ...

  5. vuex中mapGetters的使用及简单实现原理

    一.项目中的mapGetters在Vue项目的开发过程中必然会使用到vuex,对vue项目公用数据进行管理,从而解决组件之间数据相互通信的问题,如果不使用vuex,那么一些非父子组件之间的数据通信将会 ...

  6. JS简单回弹原理

    /* *JS简单回弹原理 */ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "ht ...

  7. super函数没有那么简单-super原理剖析

    开始之前,先出一道题: #super函数探讨 class A(object): def __init__(self): print 'A.__init__' class B(A): def __ini ...

  8. Redux其实很简单(原理篇)

    在这一篇文章中,笔者将带大家编写一个完整的Redux,深度剖析Redux的方方面面,读完本篇文章后,大家对Redux会有一个深刻的认识. 核心API 这套代码是笔者阅读完Redux源码,理解其设计思路 ...

  9. Servlet的生命周期以及简单工作原理的讲解

    Servlet生命周期分为三个阶段: 1,初始化阶段              调用init()方法 2,响应客户请求阶段 调用service()方法 3,终止阶段           调用destr ...

随机推荐

  1. jmeter如何连接数据库

    大家都知道jmeter是java编写的,java/jmeter如果想连接数据库就要通过java database connector(JDBC)去连接,首先需先下载一个驱动 (mysql-connec ...

  2. 学习人工智能的第五个月[字典学习[Dictionary Learning,DL]]

    摘要: 大白话解释字典学习,分享第五个月的学习过程,人生感悟,最后是自问自答. 目录: 1.字典学习(Dictionary Learning,DL) 2.学习过程 3.自问自答 内容: 1.字典学习( ...

  3. [ecmagnet][django] 如何使用django的signal

    """ 在web开发中, 你可能会遇到下面这种场景: 在用户完成某个操作后, 自动去执行一些后续的操作. 譬如用户完成修改密码后,你要发送一份确认邮件 观察者模式:观察者 ...

  4. BZOJ 3670 NOI2014 动物园 KMP+dp

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3670 题意概述:令num[i]表示字符串由1~i的字符形成的前缀中不相重叠的相同前后缀的数 ...

  5. 第1讲——用C++写一个程序

    一.学习新知识 在学习C++之前学过C语言了,一些基础的就不bb了,进入正题. 来几个小程序练练手: [程序1] #include <iostream> //头文件 using names ...

  6. 【历史】- Unix时代的开创者Ken Thompson

    自图灵奖诞生以来,其获得者一直都是计算机领域的科学家与学者,而在所有这些界的图灵奖中只有唯一的一届有个例外,那就是Ken Thompson与Dennis M. Ritchie,他们都是计算机软件工程师 ...

  7. Delphi 之 编辑框控件(TEdit)

    TEdit 组件主要用于数据的输入和显示和编辑等操作. AutoSelect 获取组件焦点.该属性只能在单行文本组件使用.值为True为选中.false则不选中. BorderStyle 设置编辑框控 ...

  8. ARC075 E.Meaningful Mean(树状数组)

    题目大意:给定n和k,问an中有多少子区间的平均值大于等于k 很巧妙的一个式子,就是如果一个区间[l, r]满足条件 那么则有 sum[r] - sum[l-1] >= (r-l+1)*k 整理 ...

  9. [洛谷P2568]GCD

    题目大意:给你$n(1\leqslant n\leqslant 10^7)$,求$\displaystyle\sum\limits_{x=1}^n\displaystyle\sum\limits_{y ...

  10. BZOJ3573 [Hnoi2014]米特运输 【贪心】

    题目链接 BZOJ3573 题解 题目又臭又长系列 题意:修改尽量少的点权,使得: ①同个节点的所有儿子点权相同 ②任意非叶节点权值等于其儿子权值之和 容易发现一旦任意一个点权值确定,整棵树权值就确定 ...