时间管理——高精度时钟、动态时钟——实现(二)

参考:1、《Professional Linux Kernel Architecture》1ed_CN p714~p760
        2、http://blog.csdn.net/droidphone/article/details/7975694
        3、2.6.34

说明下,如果单纯以unicore架构的sep611为例,没有必要(没有高精度时钟源、现行的unicore内核本身也不支持),龙芯没有继续向下做,所以此处以omap44xx为参考做记录。没有记录timer wheel,相关部分看下代码就行了。

从系统初始化开始记录:

start_kernel()
    |---->tick_init()
          |---->clockevents_register_notifier(&tick_notifier);
          |    将tick_notifier订阅者挂入clockevents_chain
          |    中,通过clockevents_chain发布有时钟事件发生,
          |    进而通知订阅者。
    |......
    |---->init_timers()
          |---->timer_cpu_notify(&timers_nb, 
          |          (unsigned long)CPU_UP_PREPARE,
          |          (void *)(long)smp_processor_id());
                     |---->init_timers_cpu(cpu)
                     |     初始化各个CPU的tvec_base(timer wheel)
                     |
          |----register_cpu_notifier(&timers_nb);
          |----open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
    |
    |---->hrtimers_init()
    |......
    |---->timekeeping_init()
    |     该函数中初始化了大量的时钟相关全局变量
    |     该函数中调用的函数有部分实际上没有真正的实现
    |     关于RTC时间同步,可以参考rtc_hctosys函数
          |---->ntp_init()
          |---->clock = clocksource_default_clock();
          |     系统在启动期间,如果计算机确实没有提供更好的选择
          |     (在启动后,决不会如此),内核提供了一个基于
          |     jiffies的时钟 clocksource_jiffies
          |---->timekeeper_setup_internals(clock);
          |---->set_normalized_timespec(&wall_to_monotonic,
          |                             -boot.tv_sec, -boot.tv_nsec);
    |---->time_init()
         |---->system_timer->init()
         |     system_timer和平台相关,在setup_arch函数中被设置
         |     
         |     以omap4430为例(尽管不熟习这个平台)
    |----.......
    |---->rest_init()
         |---->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
| kernel_init中会调用smp_prepare_cpus(setup_max_cpus), | 完成各个核的时钟事件设备注册(有点不准确,实际上应该说是boot核
| 但是给函数中调用了percpu_timer_setup,其它核都会执行该函数
        

void __init smp_prepare_cpus(unsigned int max_cpus)
void __init smp_prepare_cpus(unsigned int max_cpus)                                                                
   |----......
   |---->percpu_timer_setup()
        |---->unsigned int cpu = smp_processor_id();
        |     struct clock_event_device *evt = 
        |            &per_cpu(percpu_clockevent, cpu);
        |     evt->cpumask = cpumask_of(cpu);
        |---->local_timer_setup(evt);
        |     即使是boot核在初始化阶段使用的clock_event_device
        |     也将被替换因为此处使用的rating值高达400

 

(记录的最后有个ARM SMP的启动简略图)

主核何时通知其它核启动

static int __init kernel_init(void *unused)
     |----->smp_perpare_cpus(setup_max_cpus)
              |-----......
              |----->wakeup_secondary()
     |-----......
     |----->smp_init()
              |-----......
              |----->cpu_up(cpu)
                       |---->_cpu_up(cpu, 0)
                               |---->__cpu_up(cpu);
                                      |----->boot_secondary(cpu, idle)

 

关于其它核的初始化流程:

arch/arm/mach-omp2/omap-headsmp.S

ENTRY(omap_secondary_startup)
   hold:......
            bne hold
            b secondary_startup
END(omap_secondary_startup)


arch/arm/kernel/head.S

ENTRY(secondary_startup)
......
    mov r13, r12 @__secondary_switched address
......
ENDPROC(secondary_startup)

ENTRY(__secondary_switched)
......
    b secondary_start_kernel
ENDPROC(__secondary_switched)

asmlinkage void __cpuinit secondary_start_kernel(void)
{
    ........
    percpu_timer_setup();   //其它核上的clock_event_device设置
    .......
}

 

 

基于以上信息,我们可以得到系统初始化后每个核都有自己的时钟事件设备,但是这个时候仍然采用低分辨率周期时钟,我们自然会问:什么时候切换成了高精度时钟如下:
由于开始时周期性中断处理函数仍是tick_handle_periodic,那么第一次触发时钟中断时:

void tick_handle_periodic(struct clock_event_device *dev)
  |----tick_periodic()
       |----......
       |---->update_process_times(user_mode(get_irq_regs()));
             |----......
             |---->run_local_timers()
                  |---->hrtimer_run_queues();
                  |     处理高精度时钟,实际上由于还没有激活高精度
                  |     时钟功能,因此无效。但是一旦激活高精度时钟
                  |     其职责将变得很大。
|---->raise_softirq(TIMER_SOFTIRQ); | 第一次唤醒TIMER_SOFTIRQ软中断时将激活高 | 精度时钟。
static void run_timer_softirq(struct softirq_action *h)
   |---->hrtimer_run_pending()
  |    系统在hrtimer_run_pending函数中判断系统的条
  |    件是否满足切换到高精度模式,NO_HZ模式也在该函数中
  |    判断并切换。
  |---->__run_timers(base);
  |    timer wheel(更适用于timer out)
void hrtimer_run_pending(void)
  |---->if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))
   |         hrtimer_switch_to_hres();
   |     1、如果hrtimer_is_hres_enabled()返回0,说明没有使能高精度
   |        时钟功能。
   |       (1)如果使能动态时钟则,则设置clock_event_device的
   |          event_handler为tick_nohz_handler;里面仍然用高精
   |          度时钟来做tick的周期定时。
   |          也许你会很奇怪,为什么在没有使能高精度时钟的情况
   |          下,仍用高精度时钟来管理tick_sched.sched_timer,
   |          其实这里仅是为了代码复用,毕竟高精度时钟中的
   |          hrtimer_forward可以便于我们周期性地不停的触发tick
   | 
   |       (2)如果没有使能动态时钟,则仍保持event_handler为
   |          tick_handle_periodic
   |    2、如果hrtimer_is_hres_enabled()返回1,说明可以激活高精
   |       度时钟功能。通过hrtimer_switch_to_hres()激活高精度时钟。

static int hrtimer_switch_to_hres(void)
   |----......
   |---->tick_init_highres()
        |---->tick_switch_to_oneshot(hrtimer_interrupt) 
        |     设置clock_event_device的event_handler为
| hrtimer_interrupt |----.....
|---->tick_setup_sched_timer(); | 这个函数使用tick_cpu_sched这个per-CPU变量来模拟原来 | tick device的功能。tick_cpu_sched本身绑定了
  |    一个hrtimer
这个hrtimer的超时值为下一个tick,
| 回调函数为tick_sched_timer因此,每过一个 | tick,tick_sched_timer就会被调用一次,在这回调函数中首先 | 完成原来tick device的工作,然后设置下一次的超时值为再下一个 | tick,从而达到了模拟周期运行的tick device的功能。如果所有的 | CPU在同一时间点被唤醒,并发执行tick时可能会出现。   | | 关于tick_shced_timer自行看下 | |---->retrigger_next_event(NULL); | 此处传入的参数为NULL,使得tick_device立刻产生到期中断, | hrtimer_interrupt被调用一次,然后下一个到期的定时器的时间 | 会编程到tick_device中,从而完成高精度模式的切换。 | | 切换到高精度时钟可以干嘛?精确的定时,msleep依然基于 |  timer wheel实现, 而nanosleep则基于高精度时钟实现 |    (nanosleep->sys_nanosleep-> |     htimer_nanosleep->do_nanosleep)

 

 

 

 

激活动态时钟与没有使用动态时钟的区别主要在于当核调度IDLE进程时的区别(此处记录的较为简略,没有太深入)

void cpu_idle(void)
  |----tick_nohz_stop_sched_tick(1);
  |    不会再周期性产生中断
  |    退出的情形包括:
  |    (1)一个外部中断使某个进程变成可运行的,这要求时钟机制恢复工作
  |    (2)下一个时钟信号即将到期
  |    关于(2),因为时钟信号一定会到来(防止硬件溢出),此时仍会进入
  |    周期性中断处理函数,问题在于,如果此时没有激活新的进程,那么我们
  |    可以把tick触发时刻继续推后,这需要注意在irq_exit也有可能会调
  |    用tick_nohz_stop_sched_tick(0);
  |----...... |----tick_nohz_restart_sched_tick(); |----......

 

=====================================================================

以上明白了高精度时钟及动态时钟的设置,下面记录下如上记录中没有涉及到的部分细节。

hrtimers_init()  

hrtimers_init()
  |---->hrtimer_cpu_notify(&hrtimers_nb, 
  |       (unsigned long)CPU_UP_PREPARE,
  |       (void *)(long)smp_processor_id());
       |---->init_hrtimers_cpu(cpu);
       |     初始化各个CPU的hrtimer_bases(高精度定时器,也可用于仿真周期性
       |     时钟中断) hrtimer_base是实现hrtimer的核心数据结构,通过
       |     hrtimer_bases,
hrtimer可以管理挂在每一个CPU上的所有timer。
       |     每个CPU上的timer list不再使用timer wheel中多级链表的实现方式,
       |     而是采用红黑树(Red-Black Tree)来进行管理。每个hrtimer_bases
       |     都包含两个clock_base,一个是CLOCK_REALTIME类型的,另一个是
       |     CLOCK_MONOTONIC类型的(即采用的时间基准不一样)。hrtimer可以选
       |     择其中之一来设置timer的expire time,可以是实际的时间,也可以是相 |  对系统运行的时间。注意hrtimer_base定义时的初始值。 |  hrtimer_base类型为hrtimer_cpu_base,其中包含
       |       struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES],
       |     注意hrtimer_clock_base中的index:
       |              用于区分CLOCK_MONOTONIC和CLOCK_REALTIME.
|---->hrtimer_init_hres(cpu_base); |----base->expires_next.tv64 = KTIME_MAX; | 将要到期的下一个事件的绝对时间 |----base->hres_active = 0; | 表示高分辨率模式是否已经启用,还是只提供低分辨率模式 |----register_cpu_notifier(&hrtimers_nb); |----open_softirq(HRTIMER_SOFTIRQ, run_hrtimer_softirq);

 

ntp_init()

ntp_init()
  |---->ntp_clear()
  |---->hrtimer_init(&leap_timer, 
  |                CLOCK_REALTIMER, 
  |                HRTIMER_MODE_ABS);
  |----leap_timer.function = ntp_leap_second

 

omap2_gp_timer_init()

 omap2_gp_timer_init()
  |---->omap_dm_timer_init()
       |----dm_timers = omap4_dm_timers
       |----dm_timer_count = omap4_dm_timer_count
       |----dm_source_names = omap4_dm_source_names
       |----dm_source_clocks = omap4_dm_source_clocks
       |----for(i = 0; dm_source_names[i] != NULL; i++)
       |      dm_source_clocks[i] = 
       |           clk_get(NULL, dm_source_names[i]);
       |----I/O空间映射
  |---->omap2_gp_clockevent_init()
       |----gptimer = 
       |           omap_dm_timer_request_specific(gptimer_id);
       |    获取一个时钟
       |----tick_rate = 
       |           clk_get_rate(omap_dm_timer_get_fclk(gptimer));
       |    获取时钟的频率
       |----omap2_gp_timer_irq.dev_id = (void *)gptimer;
       |----setup_irq(omap_dm_timer_get_irq(gptimer), 
       |              &omap2_gp_timer_irq);
       |    设置中断
       |
       |    以下设置全局时钟事件设备
       |----clockevent_gpt.mult = 
       |           div_sc(tick_rate, 
       |                  NSEC_PER_SEC,
       |                  clockevent_gpt.shift);
       |----clockevent_gpt.max_delta_ns =
       |           clockevent_delta2ns(0xffffffff, &clockevent_gpt);
       |----clockevent_gpt.min_delta_ns =
       |           clockevent_delta2ns(3, &clockevent_gpt);
       |----clockevent_gpt.cpumask = cpumask_of(0);
       |    将该clock_event_device即clockevent_gpt制定给CPU0
       |    SO,其它的核呢?请留意start_kernel->rest_init->kernel_init线程中的
       |    smp_prepare_cpus(setup_max_cpus);
       |
       |----clockevents_register_device(&clockevent_gpt);
       |    非常重要
  |---->omap2_gp_clocksource_init()
       |----gpt_clocksource =
       |    omap_dm_timer_request();
       |    获取一个时钟源
       |    omap_dm_timer_set_source(gpt_clocksource,
       |           OMAP_TIMER_SRC_SYS_CLK);
       |
       |    tick_rate = clk_get_rate(
       |         omap_dm_timer_get_fclk(gpt_clocksource));
       |
       |    tick_period = (tick_rate / HZ) - 1;
       |    omap_dm_timer_set_load_start(gpt_clocksource, 1, 0);
       |
       |----clocksource_gpt.mult =
       |----    clocksource_khz2mult(tick_rate/1000,
       |                 clocksource_gpt.shift);
       |----clocksource_register(&clocksource_gpt)


void clockevents_register_device(struct clock_event_device *dev)
void clockevents_register_device(struct clock_event_device *dev)
  |---->list_add(&dev->list, &clockevents_device)
  |---->clockevents_do_notify(CLOCK_EVT_NOTIFIY_ADD, dev);
  |     注意tick_init中,在clockevents_chain中加入的订阅者
       |---->return tick_check_new_device(dev); 


static int tick_check_new_device(struct clock_event_device *newdev)
static int tick_check_new_device(struct clock_event_device *newdev)
  |----strcut tick_device *td;
  |    td = &per_cpu(tick_cpu_device, cpu)
  |    curdev = td->evtdev
  |----clockevents_exchange_device(curdev, newdev)
  |    关闭了newdev(后面会再开启)
  |----tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
       |----注意这个函数,因为在其中会在首次运行该函数时,
      |    将指定一个CPU负责全局时钟相关事宜,(该CPU放弃该职责时,
| 应该如何处理,可以参考前一篇记录)
| 此处略过,自行查看; | | 关注共同点: | 每个个cpu 的 tick_device首次运行该函数,还会将其 | 工作模式设置为TICKDEV_MODE_PERIODIC |----td->evtdev = newdev |----if(td->mode = TICKDEV_MODE_PERIODIC) | 首次一定成立,因此: | tick_setup_periodic(newdev, 0) |---->if(newdev->features & CLOCK_EVT_FEAT_ONESHOT) tick_oneshot_notify() |--->struct_sched *ts = &_get_cpu_var(tick_cpu_sched) | tick_sched是一个专门的数据结构,用于管理周期时钟相关的所有信息, | 由全局变量tick_cpu_sched为每个CPU分别提供一个该结构的实例 |--->set_bit(0, &ts->check_clocks)

void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
   |----假设传入上文所示参数
   |----tick_set_periodic_handler(dev, broadcast)
        |当broadcast为0时,则:
        |---->dev->event_handler = tick_handle_periodic

 



int clocksource_register(struct clocksource *cs)
int clocksource_register(struct clocksource *cs)                                                                   
  |---->cs->max_idle_ns = clocksource_max_deferment(cs);
  |    计算在该时钟源上可睡眠的最长时间(防止硬件溢出)
  |---->clocksource_enqueue(cs);
  |    将时钟源加入clocksource_list链表
  |---->clocksource_select();
       |----struct clocksource *best, *cs;
       |----......
       |    选取当前clocksource_list上最好时钟源(rating)
       |----if (curr_clocksource != best) {
       |        curr_clocksource = best;
       |        timekeeping_notify(curr_clocksource);}
  |---->clocksource_enqueue_watchdog(cs);

 

 

 

static void local_timer_setup(struct clock_event_device *evt)  (percpu_timer_setup中使用)

static void local_timer_setup(struct clock_event_device *evt)
   |初始化每个CPU的clock_event_device
   |boot核的tick_device在初始化阶段将被替换
   |----evt->name       = "dummy_timer";
   |    evt->features   = CLOCK_EVT_FEAT_ONESHOT |
   |                      CLOCK_EVT_FEAT_PERIODIC |
   |                      CLOCK_EVT_FEAT_DUMMY;
   |    evt->rating     = 400;  
   |    evt->mult       = 1;    
   |    evt->set_mode   = broadcast_timer_set_mode;
   |    evt->broadcast  = smp_timer_broadcast;
   |---->clockevents_register_device(evt);
         |---->如果是首次进入该该函数,则与boot核情形相同,
         |     但是要注意boot核,因为boot核此时将二次进入,
         |     首次进入被设置成了tick_handle_periodic,
         |     即使是boot核在二次进入的情形下,仍被设置成
         |     tick_handle_perodic;
         |
         |     如果在进入tick_setup_device后tick_device
         |     的模式为TICKDEV_MODE_ONESHOT,此时的处理函数
         |     仍然是tick_handle_periodic,关键在于由于此时
         |     单次触发模式,因此tick_handle_periodic的行为
         |     将会做适当的改变(主动设置下次tick的触发时间)

 

 ARM SMP启动简略图:

引述自:http://www.linux-arm.org/LinuxBootLoader/SMPBoot

posted on 2013-09-17 21:12  阿加  阅读(4124)  评论(0编辑  收藏  举报

导航