Chinaunix首页 | 论坛 | 博客
  • 博客访问: 321655
  • 博文数量: 100
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 665
  • 用 户 组: 普通用户
  • 注册时间: 2015-02-02 12:43
文章分类

全部博文(100)

文章存档

2015年(100)

我的朋友

分类: LINUX

2015-05-17 07:19:40

http://blog.chinaunix.net/uid-25942458-id-3412140.html
linux2.6.22之前没有引入高精度定时器的框架,系统中的各个架构都有一套自己的时间和定时器管理框架,这样就带来几个麻烦:1、重复代码太多 2、原来的框架不适于高精度定时器的加入,精度不够等。所以,linux后面通过引入clocksource和clockevent对时钟源和定时器进行了抽象,有了一个统一的抽象框架,而各个架构只需要适配这个框架即可,同时,又可以引入高精度定时器。
一、介绍几个基本概念
时钟源:定义如下:

点击(此处)折叠或打开

  1. struct clocksource {
  2.     /*
  3.      * First part of structure is read mostly
  4.      */
  5.     char *name; //时钟源的名字
  6.     struct list_head list;//时钟源链表,系统中的时钟源都是挂在一个链表上的
  7.     int rating;//时钟源的质量或者精度,数字越大越好,通常300~399是精度比较高的
  8.     cycle_t (*read)(struct clocksource *cs);//对时钟源操作的函数
  9.     int (*enable)(struct clocksource *cs);
  10.     void (*disable)(struct clocksource *cs);
  11.     cycle_t mask;
  12.     u32 mult;
  13.     u32 shift;
  14.     u64 max_idle_ns;
  15.     unsigned long flags;
  16.     cycle_t (*vread)(void);
  17.     void (*suspend)(struct clocksource *cs);
  18.     void (*resume)(struct clocksource *cs);
  19. #ifdef CONFIG_IA64
  20.     void *fsys_mmio; /* used by fsyscall asm code */
  21. #define CLKSRC_FSYS_MMIO_SET(mmio, addr) ((mmio) = (addr))
  22. #else
  23. #define CLKSRC_FSYS_MMIO_SET(mmio, addr) do { } while (0)
  24. #endif

  25.     /*
  26.      * Second part is written at each timer interrupt
  27.      * Keep it in a different cache line to dirty no
  28.      * more than one cache line.
  29.      */
  30.     cycle_t cycle_last ____cacheline_aligned_in_smp;

  31. #ifdef CONFIG_CLOCKSOURCE_WATCHDOG
  32.     /* Watchdog related data, used by the framework */
  33.     struct list_head wd_list;
  34.     cycle_t wd_last;
  35. #endif
  36. }
时钟时间设备:定义如下:

点击(此处)折叠或打开

  1. struct clock_event_device {
  2.     const char        *name;//时钟时间设备的名称
  3.     unsigned int        features;
  4.     u64            max_delta_ns;//这个和下一个域指定了当前时间和下一次时间的触发时间之间的差值,分别为最小值和最大值
  5.     u64            min_delta_ns;
  6.     u32            mult;//和下面的域分别是乘数和位移数,用于在时钟周期数和纳秒值之间转换。
  7.     u32            shift;
  8.     int            rating;
  9.     int            irq;//指定时间设备使用的irq号,只有全局设备才使用这个号
  10.     const struct cpumask    *cpumask;
  11.     int            (*set_next_event)(unsigned long evt,
  12.                          struct clock_event_device *);
  13.     void            (*set_mode)(enum clock_event_mode mode,
  14.                      struct clock_event_device *);
  15.     void            (*event_handler)(struct clock_event_device *);//时钟时间设备的处理函数
  16.     void            (*broadcast)(const struct cpumask *mask);
  17.     struct list_head    list;
  18.     enum clock_event_mode    mode;
  19.     ktime_t            next_event;
  20.     unsigned long        retries;
  21. }
时钟设备:定义如下:

点击(此处)折叠或打开

  1. enum tick_device_mode {
  2.     TICKDEV_MODE_PERIODIC,
  3.     TICKDEV_MODE_ONESHOT,
  4. };

  5. struct tick_device {
  6.     struct clock_event_device *evtdev;//从这里可以看出时钟设备就是时钟事件设备的一个封装
  7.     enum tick_device_mode mode;//时钟设备的模式,分为一次性模式和周期性模式,如上面枚举定义所示
  8. };
二、低精度定时器的运作
系统启动之初运作的就是低精度定时器,后面到一定的时机就会切换到高精度定时器。低精度定时器主要相关的步骤为:tick_init --> time_init -->每个时钟周期(HZ)调用timer_interrupt
1、tick_init定义如下,主要完成的功能就是注册一个添加设备的时间通知链,当注册一个新设备时:
发送CLOCK_EVT_NOTIFY_ADD事件 --》调用tick_check_new_device --》调用tick_setup_device安装设备(当然,先判断是否比当前现有的设备好) --》判断,若是首次安装设备,设置TICKDEV_MODE_PERIODIC标志 --》若上一步设置了周期模式,则调用tick_setup_periodic --》tick_set_periodic_handler --》安装tick_handle_periodic函数

点击(此处)折叠或打开

  1. static int tick_notify(struct notifier_block *nb, unsigned long reason,
  2.              void *dev)
  3. {
  4.     switch (reason) {

  5.     case CLOCK_EVT_NOTIFY_ADD://最关键的就是这一步,添加一个事件设备就会进入这一步
  6.         return tick_check_new_device(dev);

  7.     case CLOCK_EVT_NOTIFY_BROADCAST_ON:
  8.     case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
  9.     case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
  10.         tick_broadcast_on_off(reason, dev);
  11.         break;

  12.     case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
  13.     case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
  14.         tick_broadcast_oneshot_control(reason);
  15.         break;

  16.     case CLOCK_EVT_NOTIFY_CPU_DYING:
  17.         tick_handover_do_timer(dev);
  18.         break;

  19.     case CLOCK_EVT_NOTIFY_CPU_DEAD:
  20.         tick_shutdown_broadcast_oneshot(dev);
  21.         tick_shutdown_broadcast(dev);
  22.         tick_shutdown(dev);
  23.         break;

  24.     case CLOCK_EVT_NOTIFY_SUSPEND:
  25.         tick_suspend();
  26.         tick_suspend_broadcast();
  27.         break;

  28.     case CLOCK_EVT_NOTIFY_RESUME:
  29.         tick_resume();
  30.         break;

  31.     default:
  32.         break;
  33.     }

  34.     return NOTIFY_OK;
  35. }

  36. static struct notifier_block tick_notifier = {
  37.     .notifier_call = tick_notify,
  38. };

  39. /**
  40.  * tick_init - initialize the tick control
  41.  *
  42.  * Register the notifier with the clockevents framework
  43.  */
  44. void __init tick_init(void)
  45. {
  46.     clockevents_register_notifier(&tick_notifier);
  47. }
2、time_init定义如下,从下面的注释大意应当可以理解,在这一步就利用上了第一步的时钟时间设备添加时间,最终注册了高精度定时器设备,同时,把timer_interrupt要用的全局事件处理函数tick_handle_periodic也注册上去后,最后调用中断注册,将timer_interrupt与0号中断绑定,一切都是那么顺利成章。

点击(此处)折叠或打开

  1. static irqreturn_t timer_interrupt(int irq, void *dev_id)
    {
     /* Keep nmi watchdog up to date */
     inc_irq_stat(irq0_irqs);
  2.  /* Optimized out for !IO_APIC and x86_64 */
     if (timer_ack) {
      /*
       * Subtle, when I/O APICs are used we have to ack timer IRQ
       * manually to deassert NMI lines for the watchdog if run
       * on an 82489DX-based system.
       */
      raw_spin_lock(&i8259A_lock);
      outb(0x0c, PIC_MASTER_OCW3);
      /* Ack the IRQ; AEOI will end it automatically. */
      inb(PIC_MASTER_POLL);
      raw_spin_unlock(&i8259A_lock);
     }
  3.  global_clock_event->event_handler(global_clock_event);//最终注册了tick_handle_periodic函数
  4.  /* MCA bus quirk: Acknowledge irq0 by setting bit 7 in port 0x61 */
     if (MCA_bus)
      outb_p(inb_p(0x61)| 0x80, 0x61);
  5.  return IRQ_HANDLED;
    }
  6. static struct irqaction irq0 = {
  7.     .handler = timer_interrupt,
  8.     .flags = IRQF_DISABLED | IRQF_NOBALANCING | IRQF_IRQPOLL | IRQF_TIMER,
  9.     .name = "timer"
  10. };

  11. void __init setup_default_timer_irq(void)
  12. {
  13.     setup_irq(0, &irq0);
  14. }

  15. /* Default timer init function */
  16. void __init hpet_time_init(void)
  17. {
  18.     if (!hpet_enable())//注册一个高精度定时器事件设备,根据第一步的分析,也就是把tick_handle_periodic函数注册到全局事件处理函数上去,便于timer_interrupt的最终调用
  19.         setup_pit_timer();
  20.     setup_default_timer_irq();//将timer_interrupt注册到0号中断上去
  21. }

  22. static __init void x86_late_time_init(void)
  23. {
  24.     x86_init.timers.timer_init();//最终调用hpet_time_init
  25.     tsc_init();
  26. }

  27. /*
  28.  * Initialize TSC and delay the periodic timer init to
  29.  * late x86_late_time_init() so ioremap works.
  30.  */
  31. void __init time_init(void)
  32. {
  33.     late_time_init = x86_late_time_init;
  34. }
3、之后,在每个时钟周期,timer_interrupt就会被调用一次,也就是tick_handle_periodic被调用一次,最终,也就是jiffy值不断的增加,进程统计及低精度定时器和部分高精度定时器链表上的定时器被处理。
三、高精度定时器
高精度定时器基于红黑树来实现,且基于两种时钟:单调时钟和实际时钟。每个cpu都提供两种时钟基础,每个时钟基础都有一颗红黑树,用来排序待决的高精度定时器。
时钟基础hrtimer_clock_base,定义如下:

点击(此处)折叠或打开

  1. struct hrtimer_clock_base {
  2.     struct hrtimer_cpu_base    *cpu_base;//指向该时钟基础所属的各cpu的时候基础结构
  3.     clockid_t        index;//用来区分是哪种时钟基础
  4.     struct rb_root        active;//红黑树结构
  5.     struct rb_node        *first;//指向第一个到期的定时器
  6.     ktime_t            resolution;//定时器分辨率纳秒
  7.     ktime_t            (*get_time)(void);
  8.     ktime_t            softirq_time;
  9. #ifdef CONFIG_HIGH_RES_TIMERS
  10.     ktime_t            offset;
  11. #endif
  12. }
每个cpu建立两个时钟基础的结构,定义如下

点击(此处)折叠或打开

  1. struct hrtimer_cpu_base {
  2.     raw_spinlock_t            lock;
  3.     struct hrtimer_clock_base    clock_base[HRTIMER_MAX_CLOCK_BASES];//包含两个时钟基础结构
  4. #ifdef CONFIG_HIGH_RES_TIMERS
  5.     ktime_t                expires_next;//将要到期的下一个时间的绝对时间
  6.     int                hres_active;//高分辨率模式是否已经使用
  7.     int                hang_detected;
  8.     unsigned long            nr_events;
  9.     unsigned long            nr_retries;
  10.     unsigned long            nr_hangs;
  11.     ktime_t                max_hang_time;
  12. #endif
  13. }
从上面可以看到,每个cpu有一个base,这个base包含两个clockbase,两个clockbase又各自包含一个红黑树用来管理高精度定时器。那么,每个红黑树上的高精度定时器是怎么定义的呢?如下:

点击(此处)折叠或打开

  1. struct hrtimer {
  2.     struct rb_node            node;//将高精度定时器维持在红黑树上
  3.     ktime_t                _expires;
  4.     ktime_t                _softexpires;
  5.     enum hrtimer_restart        (*function)(struct hrtimer *);//高精度定时器回调函数
  6.     struct hrtimer_clock_base    *base;//指向时钟基础
  7.     unsigned long            state;
  8. #ifdef CONFIG_TIMER_STATS
  9.     int                start_pid;
  10.     void                *start_site;
  11.     char                start_comm[16];
  12. #endif
  13. }
那么高精度定时器如何被处理的呢?分两种情况:一个是高分辨率模式下高精度定时器的处理;二个是低分辨率模式下高精度定时器的处理。为什么会有低分辨率模式下高精度定时器处理呢?主要是因为避免在不启动高精度定时器时候,高精度定时器由一个低分辨率时钟驱动,而不用提供额外版本支持。
1、高分辨率模式下高精度定时器的处理:主要是通过高精度时钟中断中回调hrtimer_interrupt来实现,而其大概原理如下:

点击(此处)折叠或打开

  1. void hrtimer_interrupt(struct clock_event_device *dev)
  2. {
  3.     struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);//获取所在cpu的时钟基础
  4. 。。。。。。

  5.     base = cpu_base->clock_base;//获取cpu上的时钟基础

  6.     for (= 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {//遍历两个时钟基础
  7.         ktime_t basenow;
  8.         struct rb_node *node;

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

  10.         while ((node = base->first)) {//获取到期的定时器
  11.             struct hrtimer *timer;

  12.             timer = rb_entry(node, struct hrtimer, node);//获取相应的节点


  13.             if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer)) {
  14.                 ktime_t expires;
  15.                //判断是否过期
  16.                 expires = ktime_sub(hrtimer_get_expires(timer),
  17.                          base->offset);
  18.                 if (expires.tv64 < expires_next.tv64)
  19.                     expires_next = expires;
  20.                 break;//没过期直接退出
  21.             }

  22.             __run_hrtimer(timer, &basenow);//处理过期的定时器
  23.         }
  24.         base++;
  25.     }
  26. 。。。。。。
  27. //重新编程时钟时间,便于下一次到期
  28.     /* Reprogramming necessary ? */
  29.     if (expires_next.tv64 == KTIME_MAX ||
  30.      !tick_program_event(expires_next, 0)) {
  31.         cpu_base->hang_detected = 0;
  32.         return;
  33.     }
  34. 。。。。。。
  35.     if (delta.tv64 > 100 * NSEC_PER_MSEC)
  36.         expires_next = ktime_add_ns(now, 100 * NSEC_PER_MSEC);
  37.     else
  38.         expires_next = ktime_add(now, delta);
  39.     tick_program_event(expires_next, 1);//重新编程下一次超时事件
  40.     printk_once(KERN_WARNING "hrtimer: interrupt took %llu ns\n",
  41.          ktime_to_ns(delta));
  42. }
处理超期的定时器:

点击(此处)折叠或打开

  1. static void __run_hrtimer(struct hrtimer *timer, ktime_t *now)
  2. {
  3. 。。。。。。

  4.     debug_deactivate(timer);
  5.     __remove_hrtimer(timer, base, HRTIMER_STATE_CALLBACK, 0);
  6.     timer_stats_account_hrtimer(timer);
  7.     fn = timer->function;
  8. //具体回调超期的定时器
  9.     trace_hrtimer_expire_entry(timer, now);
  10.     restart = fn(timer);
  11.     trace_hrtimer_expire_exit(timer);
  12.     raw_spin_lock(&cpu_base->lock);
  13. //取消回调标志
  14.     timer->state &= ~HRTIMER_STATE_CALLBACK;
  15. }
低分辨率下的高分辨率定时器hrtimer_run_queues,其主要被调用两个时机:一个是低分辨率时钟中断处理函数tick_handle_periodic中会调用,二是在周期时钟仿真中tick_sched_timer调用。

点击(此处)折叠或打开

  1. void hrtimer_run_queues(void)
  2. {
  3.     。。。。。。
  4. //判断高精度定时器是否已经开启,若是开启,则不执行,直接返回。
  5.     if (hrtimer_hres_active())
  6.         return;
  7. //遍历两个时钟基础
  8.     for (index = 0; index < HRTIMER_MAX_CLOCK_BASES; index++) {
  9.         base = &cpu_base->clock_base[index];

  10.         if (!base->first)
  11.             continue;

  12.         if (gettime) {
  13.             hrtimer_get_softirq_time(cpu_base);
  14.             gettime = 0;
  15.         }

  16.         raw_spin_lock(&cpu_base->lock);
  17. //找到到期的高精度定时器
  18.         while ((node = base->first)) {
  19.             struct hrtimer *timer;

  20.             timer = rb_entry(node, struct hrtimer, node);
  21.             if (base->softirq_time.tv64 <=
  22.                     hrtimer_get_expires_tv64(timer))
  23.                 break;
  24. //处理高精度定时器
  25.             __run_hrtimer(timer, &base->softirq_time);
  26.         }
  27.         raw_spin_unlock(&cpu_base->lock);
  28.     }
  29. }
这里我们会发现一个问题,就是到了高精度定时器后,tick_handle_periodic就不再提供周期时钟信号,而高精度定时器也要提供一个同样功能的函数,这个时候就要提到了周期时钟仿真的概念。
周期时钟仿真:当切换到高精度定时器时,tick_setup_sched_timer来激活时钟仿真层,为每个cpu安装一个高分辨率定时器。

点击(此处)折叠或打开

  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.     u64 offset;

  6.     /*
  7.      * Emulate tick processing via per-CPU hrtimers:
  8.      */
  9.     hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
  10.     ts->sched_timer.function = tick_sched_timer;//该定时器的回调函数选择了tick_sched_timer
  11. 。。。。。。

  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. }

每个时钟周期仿真最终调用函数为:

点击(此处)折叠或打开

  1. static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer)
  2. {
  3.     。。。。。
  4.     /* Check, if the jiffies need an update */
  5.     if (tick_do_timer_cpu == cpu)
  6.         tick_do_update_jiffies64(now);//更新jiffy

  7.     /*
  8.      * Do not call, when we are not in irq context and have
  9.      * no valid regs pointer
  10.      */
  11.     if (regs) {
  12.         。。。。。。
  13.         update_process_times(user_mode(regs));//更新进程相关信息
  14.         profile_tick(CPU_PROFILING);
  15.     }

  16.     hrtimer_forward(timer, now, tick_period);//重新定时

  17.     return HRTIMER_RESTART;
  18. }

三、低分辨定时器切换到高分辨率定时器
主要是通过下面的内容来实现切换的:

点击(此处)折叠或打开

  1. int tick_init_highres(void)
  2. {
  3.     return tick_switch_to_oneshot(hrtimer_interrupt);
  4. }

点击(此处)折叠或打开

  1. int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *))
  2. {
  3.     struct tick_device *td = &__get_cpu_var(tick_cpu_device);//每cpu的时钟设备
  4.     struct clock_event_device *dev = td->evtdev;

  5. 。。。。。。

  6.     td->mode = TICKDEV_MODE_ONESHOT;
  7.     dev->event_handler = handler;//切换处理函数,此时就切换为hrtimer_interrupt
  8.     clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);//设置为一次性模式
  9.     tick_broadcast_switch_to_oneshot();
  10.     return 0;
  11. }

每个软中断中会检查是否有高精度定时器可以切换,若是有,直接切换高精度定时器。其调用路径如下:
软中断发生 --》 run_timer_softirq--》hrtimer_run_pending --》hrtimer_switch_to_hres--》tick_init_highres(切换高精端定时器) + tick_setup_sched_timer(启动周期仿真层)
阅读(1433) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~