Linux时间子系统浅析


贴一篇之前读代码的存货笔记。。。


Linux中的时间子系统和调度子系统关联比较大,所以需要结合起来分析。

在时钟子系统部分,主要关注时间子系统的初始化;periodic模式中断处理函数的实现;如何从periodic模式切换到oneshot模式;如何切换到高精度定时器;基于hrtimer的周期性时钟sched_timer的实现;hres模式中断处理函数的实现。

在调度部分,主要关注调度的流程;调度策略触发的时间和条件;以及cpu_idle的行为。关于具体的调度策略会涉及到具体算法留着以后深入研究再分析吧。

1. 时间子系统初始化

根据时间子系统的初始化过程,按照初始化先后顺序依次分析。

1.1. tick相关的通知链注册tick_init)

tick_init()向通知链clockevents_chain注册了回调函数nb

416 void __init tick_init(void)

417 {

418     clockevents_register_notifier(&tick_notifier);

419 }  

notice block—tick_notifier的回调函数为:

407 static struct notifier_block tick_notifier = {

408     .notifier_call = tick_notify,

409 };

先查看下回调函数tick_notify的实现:

362 static int tick_notify(struct notifier_block *nb, unsigned long reason, void *dev)

364 {

365     switch (reason) { 

367     case CLOCK_EVT_NOTIFY_ADD:

368         return tick_check_new_device(dev);

… … 

404     return NOTIFY_OK;

405 }

先关注CLOCK_EVT_NOTIFY_ADD条件的处理函数tick_check_new_device,从条件名称可推断出,当系统添加新的tick设备时会通过通知来告诉tick_notify,进而再去执行tick_check_new_device函数。tick_check_new_device主要功能就是对这个new device进行一些初始化(后续分析),这里先关注触发条件,触发通知的API有两个,分别是:

1. clockevents_notify 

2. clockevents_register_device

先看clockevents_notifyclockevents_notify最终通过调用clockevents_do_notify(reason, arg)来触发通知,但参数reasonarg由调用clockevents_notify传进来。所以clockevents_notify应该是一个公用的接口。除新clock设备添加外,其它reason也可以由这个接口来触发通知。搜索代码后得知,当前工程不是用这个接口来通知系统有新clock设备添加。

再看clockevents_register_device,此函数也是通过clockevents_do_notify来触发通知的,但参数已固定为CLOCK_EVT_NOTIFY_ADD和dev,所以调用clockevents_register_device必定会导致CLOCK_EVT_NOTIFY_ADD通知的触发。当前工程用的也是这个接口。

那哪些地方会调用这个接口呢?调用这个接口的地方就说明需要通知系统有新的clock device已经添加到系统了。搜索可知,当前有两处:

1. via_clockevent_init

2. clockevents_config_and_register

via_clockevent_init从字面就能看出是个init函数。调用时间则是在machinetimer初始化过程中,这需要我们稍后仔细分析。

clockevents_config_and_register其实是percpu_timer_setup()函数(通过lt_ops->setup实现),在主核初始化或者开启从核的过程中都有机会调用到。具体如下:

1. secondary_start_kernel

2. smp_prepare_cpus

secondary_start_kernel是在开启从核的过程中调用的,开启从核后需要针对从核的tick相关子系统做些初始化,所以这里需要调用是可以理解的。

再看smp_prepare_cpus,它是在kernel_init内核线程中调用的,此时从核还未开启,所以这里smp_prepare_cpus应该还是操作了core0。根据这个推论,看上去core0会两次会执行到tick_notify。第一次是在time_init(machine_desc->timer)过程中,第二次是在kernel_init线程的(smp_prepare_cpus)那这两次有什么区别呢?后面分析的时候再留意吧。

1.2. timers(普通精度)hrtimers(高精度)相关初始化

这一步主要通过两个函数来实现,init_timers()和hrtimers_init()。两个函数的实现分别如下:

1783 void __init init_timers(void)                                                                                                            

1784 {

1785     int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,

1786                 (void *)(long)smp_processor_id());

1787 

1788     init_timer_stats();

1790     BUG_ON(err != NOTIFY_OK);

1791     register_cpu_notifier(&timers_nb);

1792     open_softirq(TIMER_SOFTIRQ, run_timer_softirq); 

1793 }

timers_nb定义如下:

1778 static struct notifier_block __cpuinitdata timers_nb = {

1779     .notifier_call  = timer_cpu_notify,

1780 };

通知回调函数timer_cpu_notify的实现如下:

1753 static int __cpuinit timer_cpu_notify(struct notifier_block *self, usigned long action, void *hcpu)

1755 {

1756     long cpu = (long)hcpu;

1757     int err; 

1759     switch(action) {

1760     case CPU_UP_PREPARE:

1761     case CPU_UP_PREPARE_FROZEN:

1762         err = init_timers_cpu(cpu);

1763         if (err < 0)

1764             return notifier_from_errno(err);

1765         break;

1766 #ifdef CONFIG_HOTPLUG_CPU

1767     case CPU_DEAD:

1768     case CPU_DEAD_FROZEN:

1769         migrate_timers(cpu);

1770         break;

1771 #endif

1772     default:

1773         break;

1774     }

1775     return NOTIFY_OK;

1776 }

init_timers主要实现两个功能,一个是注册与timers系统相关的通知;另一个是注册软中断的处理。

timers系统相关通知的实现如上所示,通知回调函数timer_cpu_notify用于CPU online/offlinetimers系统所需要完成的对应工作。如在init_timers执行中所示,此时只有cpu0在工作,整个系统处于bring up过程,此时函数便显示调用了timer_cpu_notify,且参数是CPU_UP_PREPARE,所以此后就会去调用init_timers_cpu,timer系统就会进行后续初始化。如果整个系统smp准备好了,second core准备工作了,那么在second core初始化过程中就会通过通知链通知timers系统,告诉timers当前有新的core添加进来了可以执行init_timers_cpu去初始化这个coretimer系统了。

再看软中断handler的注册,open_softirq(TIMER_SOFTIRQ, run_timer_softirq)。先关注哪些地方会调用这个handler,有如下两处:

1. tick_handle_periodic-> tick_periodic-> update_process_times-> run_local_timers

2. hrtimer_interrupt-> __run_hrtimer->(调用timerfunc  …… tick_sched_timer.func = tick_sched_timer) tick_sched_timer-> update_process_times->run_local_timers

这里先补充下tick_handle_periodichrtimer_interrupt知识,这两个都是clock eventhandler,具有当前系统用的是哪个handler由系统tick运行模式决定,具体是根据periodic还是oneshot模式来决定的,且同一个clock event在同一时间只能启用一种模式,也就是对应只有一个handler生效。

hrtimers_init的流程和init_timers的类似,也是先注册了通知然后又注册了软中断的handler。具体实现如下:

1759 void __init hrtimers_init(void)

1760 {

1761     hrtimer_cpu_notify(&hrtimers_nb, (unsigned long)CPU_UP_PREPARE,

1762               (void *)(long)smp_processor_id());

1763     register_cpu_notifier(&hrtimers_nb);

1764 #ifdef CONFIG_HIGH_RES_TIMERS

1765     open_softirq(HRTIMER_SOFTIRQ, run_hrtimer_softirq);

1766 #endif

1767 }

hrtimers_nb的定义如下:

1755 static struct notifier_block __cpuinitdata hrtimers_nb = {

1756     .notifier_call = hrtimer_cpu_notify,

1757 };

通知回调函数hrtimer_cpu_notify的定义如下:

1722 static int __cpuinit hrtimer_cpu_notify(struct notifier_block *self,                                                                     

1723                     unsigned long action, void *hcpu)

1724 {

1725     int scpu = (long)hcpu;

1726 

1727     switch (action) {

1728 

1729     case CPU_UP_PREPARE:

1730     case CPU_UP_PREPARE_FROZEN:

1731         init_hrtimers_cpu(scpu);

1732         break;

1733 

1734 #ifdef CONFIG_HOTPLUG_CPU

1735     case CPU_DYING:

1736     case CPU_DYING_FROZEN:

1737         clockevents_notify(CLOCK_EVT_NOTIFY_CPU_DYING, &scpu);

1738         break;

1739     case CPU_DEAD:

1740     case CPU_DEAD_FROZEN:

1741     {

1742         clockevents_notify(CLOCK_EVT_NOTIFY_CPU_DEAD, &scpu); 

1743         migrate_hrtimers(scpu);

1744         break;

1745     }

1746 #endif

1747     

1748     default:

1749         break;

1750     }

1751 

1752     return NOTIFY_OK;

1753 }

init_timers中注册的通知回调函数类似,这里的回调函数是给second core等其它UP或者DEAD时使用的。

至于hrtimer的软中断handler触发点在哪里,在后续sched_timer中会详细分析

1.3. ARCH相关的timer系统初始化(OS TIMER)

在相关工作准备好后,我们就要开始初始化我们的timer硬件了。分析平台的硬件存在两种timer,一种是全局的os timer,还有一种是core自己的专属timer。先阶段初始化的是os timer。硬件初始化的入口是time_init,追踪此函数可以发现最终会调用到medsc里定义的timer init方法。在我们目前的平台上就是via_timer_init(),相关函数具体如下:

146 void __init time_init(void)

147 {   

148     system_timer = machine_desc->timer;

149     system_timer->init();

150     sched_clock_postinit();

151 }

via_timer_init的实现如下:

284 static void __init via_timer_init(void)

285 {

286     /* prepare OS timer hardware, irq disabled  */

287     via_os_timer_init();

288     /* os timer1 as clocksourece                */

289     via_clocksource_init(&via_clocksource);

290 setup_sched_clock(via_os_timer_read_counter, 32, VIA_CLOCK_TICK_RATE);

291 

292     /* os timer1 as clockevent device           */

293 #ifdef CONFIG_SECURITY_MODE 

294 via_clockevent_init(&via_clockevent, IRQ_OST1, &via_timer_irq);

 295 #else

296     via_clockevent_init(&via_clockevent, IRQ_OST1_NS, &via_timer_irq);//By Tim Guo, IRQ_OST1 should be modified according to new GIC Spec

297 #endif

298     /* this is a MUST operation */

299     via_os_timer_enable_irq();

300 

301 #if CONFIG_OF//By PeterCui, for supporting device tree

302     arch_timer_of_register();

303 #else

304     arch_timer_register();

305 #endif

306     return ;

307 }

首先分析下via_timer_init做的事情:

1. 初始化os timer寄存器相关信息

2. 注册一个rate200clocksource到系统。

3. 初始化一个sched_clock_timer,到时候给周期性时钟sched_timer使用

4. 注册一个clock event devices到系统

5. enable os timer

6. 注册 arch timer,也就是属于coretimer

其中比较重要的步骤是2346,下面具体分析。

1.3.1. 注册clocksource(os timer)

步骤2通过一层一层封装的函数最终调用到__clocksource_register_scale,在这个函数中调用clocksource_enqueue将我们定义的clock source添加到clocksource_list(所有的clock source最终都会被加到这个链表上),然后再通过clocksource_select来重新选择出当前list中最合适clock source作为系统当前的clock source。如下就是我们定义的cloaksource数据结构和__clocksource_register_scale函数的实现:

182 struct clocksource via_clocksource = {

183     .name           = "via_clocksource",

184     .rating         = 200,

185     .read           = via_timer_read_cycles,

186     .mask           = CLOCKSOURCE_MASK(32),

187     .flags          = CLOCK_SOURCE_IS_CONTINUOUS,

188     .suspend        = via_cs_suspend,

189     .resume         = via_cs_resume,

190 };

__clocksource_register_scale实现如下:

711 int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)

712 {

713     

714     /* Initialize mult/shift and max_idle_ns */

715     __clocksource_updatefreq_scale(cs, scale, freq);

716 

717     /* Add clocksource to the clcoksource list */

718     mutex_lock(&clocksource_mutex);

719     clocksource_enqueue(cs);

720     clocksource_enqueue_watchdog(cs);

721     clocksource_select();

722     mutex_unlock(&clocksource_mutex);

723     return 0;

724 }

1.3.2. 注册clock event(os timer)

接着分析步骤4 clock event devices的注册过程,在分析之前先注意下clock event设备的数据结构,具体如下:

236 struct clock_event_device via_clockevent = {

237     .name           = "via_clockevent",

238     .features       = CLOCK_EVT_FEAT_ONESHOT,

239     .rating         = 200,

240     .set_next_event = via_timer_set_next_event,

241     .set_mode       = via_timer_set_mode,

242     .shift          = 32,

243 };

重要的几个成员已经标红了,在后续函数中会使用。其中feature是用于判断当前clock event设备支持的特性,特性不一样的设备可以有不同的行为。后续函数中需要根据这个feature来判断clock event设备支持的功能,我们这里的设备只支持ONESHOT功能;set_next_event也比较重要,它用于更新硬件timer中的match寄存器值,也就是将下一个定时值设置到硬件寄存器中,这个和平台相关,目前平台实现如下:

200 via_timer_set_next_event(unsigned long cycles, struct clock_event_device *evt)

201 {

202     unsigned long next = 0;

203     unsigned long oscr = 0;

204 

205     oscr = via_os_timer_read_counter();

206     next = oscr + cycles;

207     /* set new value to os time1 match register   */

208     via_os_timer_set_match(next);

209     /* Enable match on timer 1 to cause interrupts. */

210     via_os_timer_enable_irq();

211 

212     return 0;

213 }

此函数中的输入参数cycles就是所需定时间隔,该间隔加上当前的oscr值便得到最终的value,将此值设置到match寄存器便能够在定时时间到后触发中断。

在了解了clock event数据结构后,我们继续分析clock event devices的注册过程。注册由封装函数via_clockevent_init实现。在该函数中主要完成:

1. 初始化clock event devices的部分参数

2. 注册中断irq对应的irqaction(比如qilian平台上用了os timer1,这里就是注册os timer1对应的irqaction) 

3. clock event devices添加到clockevent_devices list上。

4. 发送CLOCK_EVT_NOTIFY_ADD的通知。

这里提一下步骤2,这里注册的中断irqaction数据结构如下:

256 struct irqaction via_timer_irq = {

257     .name    = "via_timer",

258     .flags   = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,

259     .handler = via_timer_interrupt,

260     .dev_id  = &via_clockevent,

261 };

这个action中的handler比较重要,将来os timer1硬件产生中断后通过一层一层的API最终就会调用到这个handler来进行具体的处理。

clock event设备添加到list上之后便发送CLOCK_EVT_NOTIFY_ADD通知了,这个通知就触发第1.1节中提到的通知回调函数tick_notify的执行了。

在具体分析tick_notify的行为,tick_notify中通过调用tick_check_new_device(),参数是在前面已经部分初始化好的clock event device。这里需要注意一个数据结构tick devices,这个数据是percpu类型,并且它将clock event device数据结构包含在内了,输入参数clock event device最后会被初始化到这个tick device数据结构中,后续只要通过td->evt便可取出clock event设备。从本质上说一个tick_device就对应一个clock event,只是它比clock_event包含了额外的信息,并且从定义上看,tick_device支持的模式有两种periodic或者oneshot模式。

再看tick_check_new_device()函数最后调用的是tick_setup_device来实现具体初始化任务,如下所示:

208 static int tick_check_new_device(struct clock_event_device *newdev)

209 {

210     struct clock_event_device *curdev;

211     struct tick_device *td;

212     int cpu, ret = NOTIFY_OK;

213     unsigned long flags;

214 

215     raw_spin_lock_irqsave(&tick_device_lock, flags);

216 

217     cpu = smp_processor_id();

218     if (!cpumask_test_cpu(cpu, newdev->cpumask))

219         goto out_bc;

220 

221     td = &per_cpu(tick_cpu_device, cpu);

222     curdev = td->evtdev;

… … 

269     clockevents_exchange_device(curdev, newdev);

270     tick_setup_device(td, newdev, cpu, cpumask_of(cpu));

271     if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)

272         tick_oneshot_notify();

         … …

287 }

接着看tick_setup_device是如何处理的。

150 static void tick_setup_device(struct tick_device *td,

151                   struct clock_event_device *newdev, int cpu,

152                   const struct cpumask *cpumask)

153 {

154     ktime_t next_event;

155     void (*handler)(struct clock_event_device *) = NULL;

156         

157     /*  

158      * First device setup ?

159      */

160     if (!td->evtdev) {

161         /*

162          * If no cpu took the do_timer update, assign it to

163          * this cpu:

164          */

165         if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) {

166             tick_do_timer_cpu = cpu;

167             tick_next_period = ktime_get();

168             tick_period = ktime_set(0, NSEC_PER_SEC / HZ);

169         }

170 

171         /*

172          * Startup in periodic mode first.

173          */

174         td->mode = TICKDEV_MODE_PERIODIC;

175     } else {

176         handler = td->evtdev->event_handler;

177         next_event = td->evtdev->next_event;

178         td->evtdev->event_handler = clockevents_handle_noop;

179     }

180 

181     td->evtdev = newdev;

182 

183     /*

184      * When the device is not per cpu, pin the interrupt to the

185      * current cpu:

186      */

187     if (!cpumask_equal(newdev->cpumask, cpumask))

188         irq_set_affinity(newdev->irq, cpumask);

189 

190     /*

191      * When global broadcasting is active, check if the current

192      * device is registered as a placeholder for broadcast mode.

193      * This allows us to handle this x86 misfeature in a generic

194      * way.

195      */

196     if (tick_device_uses_broadcast(newdev, cpu))

197         return;

198 

199     if (td->mode == TICKDEV_MODE_PERIODIC)

200         tick_setup_periodic(newdev, 0);

201     else

202         tick_setup_oneshot(newdev, handler, next_event);

203 }

函数的主要部分已经着色,初次执行时td->evtdev是空的,所以会按如下逻辑执行:

1. 将变量tick_do_timer_cputick_next_periodtick_period初始化,后面有机会用到其中的变量。

2. td->mode赋值为TICKDEV_MODE_PERIODIC

这个td->mode比较重要,第199行就用到了这个条件。可以看出,时间子系统在初始化过程中遵循一个原则,即无论将来tick device设置为哪种mode它最初都是TICK_MODE_PERIODIC模式,就算以后切换成onehot模式也是由TICK_MODE_PERIODIC切换过去的,这个切换动作是在tick_switch_to_oneshot()中完成的,也就是当系统将tick切换到hres时。

此外,这里初始化的变量后续也会用到,比如tick_next_period被初始化成当前的ktimetick_period就是一个tick对应的ns数,还有这个tick_do_timer_cpu是用来指定负责更新jiffies等全局变量的cpu。后续可以发现在调用do_timer(1)之前会有个判断,用于判断当前cpu是不是tick_do_timer_cpu,如果是才能调用do_timer去更新jiffies等全局变量。

接着向下分析,因为td->mode被设置成了TICK_MOD_PERIODIC,所以接下去就要执行tick_setup_periodic()了。

117 void tick_setup_periodic(struct clock_event_device *dev, int broadcast)

118 {

119     tick_set_periodic_handler(dev, broadcast);

120         

121     /* Broadcast setup ? */

122     if (!tick_device_is_functional(dev))

123         return;

124     

125     if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) &&

126         !tick_broadcast_oneshot_active()) {

127         clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);

128     } else {

129         unsigned long seq;

130         ktime_t next;

131 

132         do {

133             seq = read_seqbegin(&xtime_lock);

134             next = tick_next_period;

135         } while (read_seqretry(&xtime_lock, seq));

136     

137         clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);

138     

139         for (;;) {

140             if (!clockevents_program_event(dev, next, false))

141                 return;

142             next = ktime_add(next, tick_period);

143         }

144     }

145 }

tick_setup_periodic()中重要的函数调用已经着色,次函数主要做了三件事情:

1. 设置clock event设备的handler

2. clock event devicemode设置为CLOCK_EVT_MODE_ONESHOT

3. 设置下一中断间隔时间到timermatch寄存器

设置handler比较简单,通过调用tick_set_periodic_handler就可以实现。设置clock event devicemode主要是后续在tick中断处理函数中需要判断当前的mod的,后续会提到。设置timermatch寄存器就需要多一些功夫了,因为需要判断你提供的value是否合适,如果不合适还需要重新计算。从这里的for循环就可以看出,如果提供的next值不合适我们会在next的基础上再增加tick_period,直到找到相对合适的数值。这里为啥是add而不是sub呢?这是因为这里的next就是tick_next_periodic,而tick_next_periodic我们前面是通过ktime_get()来获取的,所以到这里这个next很有可能就落后于当前的ktime,所以我们要在next的基础上增加tick_periodic

简单介绍流程后通过代码来详细分析,先看设置handler代码的流程:

285 void tick_set_periodic_handler(struct clock_event_device *dev, int broadcast)

286 {

287     if (!broadcast)

288         dev->event_handler = tick_handle_periodic;

289     else

290         dev->event_handler = tick_handle_periodic_broadcast;

291 }   

因为broadcast0,所以当前clock eventhandler被设置为tick_handle_periodic()。设置这个handler很重要,因为当os timer产生硬件中断时,最终是调用到clock eventhandler去处理具体事情的!初始化到这步,在紧接着的这次则中断必然由tick_handle_periodic来处理了。也就是说,就算系统正常运行时采用的是oneshot模式,但在初始化时第一次的handlertick_handle_periodic,之后的handler才会被修改为oneshothandler

接着再看设置定时时间间隔的函数实现:

201 int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,

202                   bool force)

203 {

204     unsigned long long clc;

205     int64_t delta;

206     int rc;     

207             

208     if (unlikely(expires.tv64 < 0)) {

209         WARN_ON_ONCE(1);

210         return -ETIME;

211     }

212 

213     dev->next_event = expires;

214 

215     if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)

216         return 0; 

217                   

218     /* Shortcut for clockevent devices that can deal with ktime. */

219     if (dev->features & CLOCK_EVT_FEAT_KTIME)

220         return dev->set_next_ktime(expires, dev);

221 

222     delta = ktime_to_ns(ktime_sub(expires, ktime_get()));

223     if (delta <= 0)

224         return force ? clockevents_program_min_delta(dev) : -ETIME;

225     

226     delta = min(delta, (int64_t) dev->max_delta_ns);

227     delta = max(delta, (int64_t) dev->min_delta_ns);

228 

229     clc = ((unsigned long long) delta * dev->mult) >> dev->shift;

230     rc = dev->set_next_event((unsigned long) clc, dev);

231             

232     return (rc && force) ? clockevents_program_min_delta(dev) : rc;

233 }  

可见第一次执行时候delta<0的,这时直接退出函数,但退出后将expires增加tick_periodic后会再此调用该函数,如此循环直到delta>0,之后便通过dev->set_next_event函数将定时值设置到match寄存器中去了。此后,只要enable这个timer在运行完设定值后便能得到timer中断。set_next_event的具体实现如下:

199 static int

200 via_timer_set_next_event(unsigned long cycles, struct clock_event_device *evt)

201 {

202     unsigned long next = 0;

203     unsigned long oscr = 0;

204 

205     oscr = via_os_timer_read_counter();

206     next = oscr + cycles;

207     /* set new value to os time1 match register   */

208     via_os_timer_set_match(next);

209     /* Enable match on timer 1 to cause interrupts. */

210     via_os_timer_enable_irq();

211 

212     return 0;

213 }

从上可知,在设置了match之后,我们立即enable了这个timer,所以系统在不久的将来便会产生timer中断了。

到此,通过tick_setup_device已经完成clock eventhandler设置和timermatch寄存器设置,现在就算timer中断到来也有能力处理它了。但是我们的初始化工作还没完成,我们在回头看tick_check_new_device。因为我们的clock event设备支持ONESHOT功能,所以在tick_setup_device之后还有机会调用tick_oneshot_notify函数。如下:

270     tick_setup_device(td, newdev, cpu, cpumask_of(cpu));

271     if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)

272         tick_oneshot_notify();

所以继续看看tick_oneshot_notify的行为。

887 void tick_oneshot_notify(void)

888 {

889     struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);

890 

891     set_bit(0, &ts->check_clocks);

892 }

这个函数做的事情很少,只是设置了一个bit位。那这个bit位有什么用呢?要得到答案便需要知道在哪些地方会使用这个bit。答案是tick_check_oneshot_change()中。在该函数中会判断此bit是否被置位,后续再具体分析这个函数的功能。这里透露设置这个bit后,系统就知道当前是支持oneshot模式的,后续在periodic模式切到oneshot模式时就是以此为判断条件。

1.3.3 为切换到core timer做准备

到此os timerclock event都注册好了,中断也开了。假设此时os timer的中断还没来,那返回via_timer_init还会继续执行后续arch_timer_of_register,arch_timer_of_register主要调用的是arch_timer_register。因为系统boot阶段使用的是os timer,但毕竟还有个精度更高的core timer存在,因此将来必然会使用更高精度的timer。现在先通过arch_timer_register函数来做一些准备工作,等时机成熟的时候,我们就可以切到core timer了,函数实现如下:

273 int  __init  arch_timer_register(void)

274 {

275     int err;

276 

277     err = arch_timer_available();

278     if (err)

279         return err;

280 

281     arch_timer_evt = alloc_percpu(struct clock_event_device *);

282     if (!arch_timer_evt)

283         return -ENOMEM;

284 

285   clocksource_register_hz(&clocksource_counter, arch_timer_rate);

286 

287     err = request_percpu_irq(arch_timer_ppi, arch_timer_handler,

288                  "arch_timer", arch_timer_evt);

289     if (err) {

290         pr_err("arch_timer: can't register interrupt %d (%d)\n",

291                arch_timer_ppi, err);

292         goto out_free;

293     }

294 

295     err = local_timer_register(&arch_timer_ops);

296     if (err) {

297         /*

298          * We couldn't register as a local timer (could be

299          * because we're on a UP platform, or because some

300          * other local timer is already present...). Try as a

301          * global timer instead.

302          */

303         arch_timer_global_evt.cpumask = cpumask_of(0);              

304         err = arch_timer_setup(&arch_timer_global_evt);

305     }

306 

307     if (err)

308         goto out_free_irq;

309 

310     return 0;

311 

312 out_free_irq:

313     free_percpu_irq(arch_timer_ppi, arch_timer_evt);

314 out_free:

315     free_percpu(arch_timer_evt);

316 

317     return err;

318 }

这里主要做了3件事情:

1. 注册rate更高的clock source

2. 注册per cpu timerhandler

3. 注册per cpu timerops函数,该ops包含setup/stop per cpu timer

对于这里注册的clock source对应实体如下:

250 static struct clocksource clocksource_counter = {                                                                                         

251     .name   = "arch_sys_counter",

252     .rating = 400,

253     .read   = arch_counter_read,

254     .mask   = CLOCKSOURCE_MASK(56),

255     .flags  = CLOCK_SOURCE_IS_CONTINUOUS,

256 };  

可见其rateing更高了,所以系统应该会选这个clock souce来使用。

接着看irq的注册。前面在via_timer_init中注册的是os timerhandler,而这里初始化的是per cpu timerhandler,且这个handlerarch_timer_evt是绑定的,而从代码看这个arch_timer_evt是一个per cpu类型的clock event数据,这就清楚的表明每个core都对应有一个属于自己的clock event device

再看注册的ops,这个ops的定义如下:

266 static struct local_timer_ops arch_timer_ops __cpuinitdata = {                                                                            

267     .setup  = arch_timer_setup,

268     .stop   = arch_timer_stop,

269 };    

这里有两个成员,分别是setupstop。这两个接口就是用于初始化per cpu对应的clock event数据结构或者stop clock event的。Stop函数暂时不分析,将来有机会再研究。先列出setup函数的实现:

155 static int __cpuinit arch_timer_setup(struct clock_event_device *clk)

156 {

157     /* Be safe... */

158     arch_timer_disable();

159 

160     clk->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_C3STOP;

161     clk->name = "arch_sys_timer";

162     clk->rating = 450;

163     clk->set_mode = arch_timer_set_mode;

164     clk->set_next_event = arch_timer_set_next_event;

165     clk->irq = arch_timer_ppi;

166 

167     clockevents_config_and_register(clk, arch_timer_rate,

168                     0xf, 0x7fffffff);

169 

170     *__this_cpu_ptr(arch_timer_evt) = clk;

171 

172     enable_percpu_irq(clk->irq, 0);

173 

174     return 0;

175 }

如上所示,已经per cpu timer对应的clock event中重要的成员已经着色了。可以与os timerclock event对比rating更高了,而这里set_next_event的操作对象也由之前的os timer变成per cpu timer了。

到此整个timer系统的初始化工作就完成了,只是当前的clock event还是使用os timer期间注册的clock event。但是clock source已经切换到更高精度的了,并且core timerclock event已经初始化好了,只等时机成熟后调用ops->setup函数便完成切换。这个时机是什么时候呢?大约是boot的最后阶段,系统准备初始smp时切换的。或者通过second_start_kernel来打开从核时,也会注册从核自己的clock event。这个切换过程时机比较靠后,所以先分析首次中断发生时的处理过程。

1.4. 首次中断的处理(tick_handle_periodic流程)

结合前面的分析,系统当前的clock event还是在os timer初始化期间注册的那个,当时的handler被初始化为tick_handle_periodic()。下面就可以分析系统在初始化后,第一次产生时钟中断时的处理流程了。首先梳理下回下内核中断的处理流程。

1. machine_desc中会有一个handle_irq的实现,这个handler就是平台所有中断的入口。

2. 中断可分为SPI(os timer)PPI(core timer)SGI(inter core), os timer属于SPI,因为这个timer是挂在PMIC下,属于所有core共享的;而每个core还有自己的timer,这个timer属于PPI;而core之间的中断就属于SGI了,比如core 0timer中断需要用来唤醒core2就是SGI(如果这么做可以的话)SPIPPI在顶层的入口是一样的都是handle_IRQ,而SGI则由handle_IP来处理。无论是os timer还是core自己的timer,最终都会调用到evt-> event_handler,也就是由clock events handler来具体处理。

鉴于第2点提到的中断比较多,这里先罗列出各handler和硬件的对应关系:

1. os timertick_handle_periodic/hrtime_interrupt

2. core timer:arch_timer_handler

3. inter core:ipi_timer

根据前面的分析,系统无论是periodic模式还是oneshot模式,第一次时钟中断发生时,最终总是由clock eventtick_handle_periodic来处理,现在就假设系统已经产生中断了,tick_handle_periodic的处理过程就如下:

82 void tick_handle_periodic(struct clock_event_device *dev)

 83 {

 84     int cpu = smp_processor_id();

 85     ktime_t next;

 86 

 87     tick_periodic(cpu);

 88 

 89     if (dev->mode != CLOCK_EVT_MODE_ONESHOT)

 90         return;

 91     /*

 92      * Setup the next period for devices, which do not have

 93      * periodic mode:

 94      */

 95     next = ktime_add(dev->next_event, tick_period);

 96     for (;;) {

 97         if (!clockevents_program_event(dev, next, false))

 98             return;

 99         /*

100          * Have to be careful here. If we're in oneshot mode,

101          * before we call tick_periodic() in a loop, we need

102          * to be sure we're using a real hardware clocksource.

103          * Otherwise we could get trapped in an infinite

104          * loop, as the tick_periodic() increments jiffies,

105          * when then will increment time, posibly causing

106          * the loop to trigger again and again.

107          */

108         if (timekeeping_valid_for_hres())

109             tick_periodic(cpu);

110         next = ktime_add(next, tick_period);

111     }

112 }

先整体看下tick_handle_periodic的功能,主要有两个。

1. 通过tick_periodic来完成中断事务的处理。

2. 如果系统是oneshot模式,那在处理完中断后还需要program timer寄存器,设定下次中断到期的时间值。

这里的模式,系统在tick_device_setup中就已经设置为ONESHOT了,所以步骤2我们一定会执行。下面需要详细分析下tick_periodic的工作

63 static void tick_periodic(int cpu)                                                                                                        

 64 {           

 65     if (tick_do_timer_cpu == cpu) {

 66         write_seqlock(&xtime_lock);

 67 

 68         /* Keep track of the next tick event */

 69         tick_next_period = ktime_add(tick_next_period, tick_period);

 70 

 71         do_timer(1);

 72         write_sequnlock(&xtime_lock);

 73     }

 74         

 75     update_process_times(user_mode(get_irq_regs()));

 76     profile_tick(CPU_PROFILING);

 77 }   

可以发现,函数首先判断当前的cpu是不是tick_do_timer_cpu,从而决定当前是否有能力去更新jiffies等全局变量。这个tick_do_timer_cpu也是在tick_setup_device过程中就初始化好了的,一般情况下它就是core0。那也就是说如果说当前cpucore0那么我们就执行do_timer(1),系统在初始化的时候只有core0在运行,所以这里可以执行do_timer(1)do_timer(1)的实现如下:

1275 void do_timer(unsigned long ticks)                                                                                                       

1276 {

1277     jiffies_64 += ticks;

1278     update_wall_time(); 

1279     calc_global_load(ticks);

1280 }

这个do_timer主要就是用于更新计时相关的全局变量,比如jiffies_64,更新wall_time,以及计算全局负载等任务。

看到这里应该可以想到,每个core最后都会开启属于自己的timer,而不是使用os timer。所以jiffies只能由一个core来更新,否则smp架构的jiffies计算要乱套了。所以这里就只认准了core0,只有此core0发生中断才去更新一些全局相关的信息,其它core产生中断则干与自己core相关的事情。

在更新完jiffies后,需要去执行update_process_time了。

1338 void update_process_times(int user_tick)                                                                                                 

1339 {

1340     struct task_struct *p = current;

1341     int cpu = smp_processor_id();

1342    

1343     /* Note: this timer irq context must be accounted for as well. */

1344     account_process_tick(p, user_tick); ①

1345     run_local_timers(); ②

1346     rcu_check_callbacks(cpu, user_tick); ③

1347     printk_tick(); ④

1348 #ifdef CONFIG_IRQ_WORK 

1349     if (in_irq())

1350         irq_work_run(); ⑤

1351 #endif

1352     scheduler_tick(); ⑥

1353  run_posix_cpu_timers(p);

这函数比较大,调用的函数也比较多,主要分析以下几个函数:

1. run_local_timers()

2. scheduler_tick()

先看run_local_timers()的实现,如下所示:

1372 void run_local_timers(void)                                                                                                              

1373 {

1374     hrtimer_run_queues();

1375     raise_softirq(TIMER_SOFTIRQ);

1376 }

此函数调用了两个函数hrtimer_run_queue和raise_softirq(TIMER_SOFTIRQ)。

在hrtimer_run_queues()中,首先判断hres_active是否被置1,如果置1证明我们的系统已经是hres的了,在hres模式下系统有专门来处理hrtimer queue的地方,在这里就直接返回了。如果没有置1,那就先把已经注册到系统中的hrtimer处理掉。

其实可以发现,tick_handle_periodicperiodic mode的中断处理函数,也就是说如果系统是periodic mode,那么系统也是有使用hrtime的能力,只是这时的hrtime是基于传统periodic低精度的timer来驱动的。而这种情况下的hrtimer精度应该较低,因为这种情况下的hrtimer只能在tick中断到来时才能被执行,所以精度应该由系统具体采用的tick来决定。由于在在初始化过程中,系统还未切换到hres,所以这次会去扫一遍hrtimer queue

接着看raise_softirq,对应的软中断处理函数是run_timer_softirq,如下所示。

1359 static void run_timer_softirq(struct softirq_action *h)

1360 {

1361     struct tvec_base *base = __this_cpu_read(tvec_bases);

1362 

1363     hrtimer_run_pending();                                                                                                               

1364 

1365     if (time_after_eq(jiffies, base->timer_jiffies))

1366         __run_timers(base);

1367 }  

run_timer_softirq是调用hrtimer_run_pending来干活的,再看hrtimer_run_pending的实现:

1436 void hrtimer_run_pending(void)

1437 {

1438     if (hrtimer_hres_active())

1439         return;                                                                                                                          

1440 

1441     /*

1442      * This _is_ ugly: We have to check in the softirq context,

1443      * whether we can switch to highres and / or nohz mode. The

1444      * clocksource switch happens in the timer interrupt with

1445      * xtime_lock held. Notification from there only sets the

1446      * check bit in the tick_oneshot code, otherwise we might

1447      * deadlock vs. xtime_lock.

1448      */

1449     if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))

1450         hrtimer_switch_to_hres();

1451 }

函数首先判断hres是否激活,如果激活那后续操作也没必要进行了,直接返回就可以,如果没激活就需要执行后续操作来初始化hres模式了。因为系统目前还处于初始化过程中,所以当前没开启hres,因此if的条件必然是true,于是就执行hrtimer_switch_to_hres(),从低精度timerhres的转换就主要由这个函数来实现了,具体过程如下:

678 static int hrtimer_switch_to_hres(void)

 679 {

 680     int i, cpu = smp_processor_id();

 681     struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu);

 682     unsigned long flags;

 683 

 684     if (base->hres_active)

 685         return 1;

 686 

 687     local_irq_save(flags);

 688 

 689     if (tick_init_highres()) {

 690         local_irq_restore(flags);

 691         printk(KERN_WARNING "Could not switch to high resolution "

 692                     "mode on CPU %d\n", cpu);

 693         return 0;

 694     }

 695     base->hres_active = 1;

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

 697         base->clock_base[i].resolution = KTIME_HIGH_RES;

 698 

 699     tick_setup_sched_timer();

 700     /* "Retrigger" the interrupt to get things going */

 701     retrigger_next_event(NULL);

 702     local_irq_restore(flags);                                                                                                            

 703     return 1;

 704 }                                                                                             

hrtimer_switch_to_hres()主要做了如下几件事:

1. 将系统切换到hres

2. 设置周期性时钟sched_timer

1.4.1. 将系统从低精度切换到hres

切换到hres的过程主要由函数tick_init_highres来实现,具体如下:

112 int tick_init_highres(void)

113 {

114     return tick_switch_to_oneshot(hrtimer_interrupt);

115 }

可见该函数也只是对tick_switch_to_oneshot的封装而已,这里需要注意输入参数hrtimer_interrupt,在初始化的时候我们将clock eventhandler设置为tick_handler_periodic,而在这里我们需要把他改成oneshot模式对应的handler了,也就是hrtimer_interrupttick_switch_to_oneshot的具体实现如下:

60 int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *))

 61 {

 62     struct tick_device *td = &__get_cpu_var(tick_cpu_device);

 63     struct clock_event_device *dev = td->evtdev;

 64 

 65     if (!dev || !(dev->features & CLOCK_EVT_FEAT_ONESHOT) ||

 66             !tick_device_is_functional(dev)) {

 67 

 68         printk(KERN_INFO "Clockevents: "

 69                "could not switch to one-shot mode:");

 70         if (!dev) {

 71             printk(" no tick device\n");

 72         } else {

 73             if (!tick_device_is_functional(dev))

 74                 printk(" %s is not functional.\n", dev->name);

 75             else

 76                 printk(" %s does not support one-shot mode.\n",

 77                        dev->name);

 78         }

 79         return -EINVAL;

 80     }

 81 

 82     td->mode = TICKDEV_MODE_ONESHOT;

 83     dev->event_handler = handler;

 84     clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);

 85     tick_broadcast_switch_to_oneshot();

 86     return 0;

 87 }

这里系统将tick devicemode重新设置为ONESHOT(之前是PERIODIC模式),并且clock eventmode也设置为ONESHOT,并且在83行处将handler更新为hrtime_interrupt了,也就是说切换到hres后,系统产生中断,我们将转由hrtimer_interrupt来处理,而不是之前的handle_tick_periodic了,系统到这一步就彻底切换成hres了。

1.4.2. 初始化周期时钟sched_timer

接着再往下看,系统在切换到hres后又去设置了sched_timer。因为切换到hres后系统不再有周期性的tick,而系统运行需要有周期时钟,比如周期性更新jiffies等。所以我们还需要模拟出一个周期性时钟。所以系统就基于hrtimer构建了一个叫做sched_timer的周期性时钟。这个sched_timer是嵌在tick_sched中的,这种关系有点像前面分析到的tick_deviceclock_event之间的关系。周期性时钟sched_timer的初始化过程如下:

828 void tick_setup_sched_timer(void)

829 {

830     struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);

831     ktime_t now = ktime_get();

832 

833     /*

834      * Emulate tick processing via per-CPU hrtimers:

835      */

836     hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);

837     ts->sched_timer.function = tick_sched_timer;

838 

839     /* Get the next period (per HRcpu) */

840     hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());

841 

842     for (;;) {

843         hrtimer_forward(&ts->sched_timer, now, tick_period);

844         hrtimer_start_expires(&ts->sched_timer,

845                       HRTIMER_MODE_ABS_PINNED);

846         /* Check, if the timer was already in the past */

847         if (hrtimer_active(&ts->sched_timer))

848             break;

849         now = ktime_get();

850     }

851 

852 #ifdef CONFIG_NO_HZ

853     if (tick_nohz_enabled)

854         ts->nohz_mode = NOHZ_MODE_HIGHRES;

855 #endif

856 }

初始化的过程很简单,主要就是注册了一个定时器到期回调函数tick_sched_timer,然后将sched_timer的时间间隔设置为tick_period,最后将这个hrtimer enqueue到某个base(关于hrtimer的组织形式可以网上搜索文章)

这里再重点分析下hrtimer_start_expires(),这个函数就是用来enqueue hrtimer到系统中的,此外这个函数还有一个重要的作用触发hrtimer软中断。为什么在添加完hrtimer之后一定要触发软中断呢?我们先看下软中断处理函数的实现:

1411 static void run_hrtimer_softirq(struct softirq_action *h)

1412 {

1413     struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);

1414 

1415     if (cpu_base->clock_was_set) {

1416         cpu_base->clock_was_set = 0;

1417         clock_was_set();

1418     }

1419                                                                                                                                          

1420     hrtimer_peek_ahead_timers();

1421 }

这个函数也没有做什么实质性的操作,继续跟踪后得到如下函数栈:hrtimer_peek_ahead_timers->__hrtimer_peek_ahead_timers->hrtimer_interrupt,可见这个软中断最后居然调用了oneshot模式下clock eventhandler,那看起来这两者有共同的事情要处理。

先梳理下hres模式的整个运行流程(更具体的参考网上文章)hres模式下的最小单位是hrtimerhrtimer中有个expires,通过这个expires来描述本hrtimer到期的时间。系统所有的hrtimer通过链表连接起来,并且系统通过某种方式可以记录下最早要到期的那个expires,以便中断处理完毕后将这个值设置到硬件寄存器中来触发下次中断。

根据上述流程再看当前情景。这里enqueue了一个hrtimer,那这个hrtimer就有可能成为系统当前最早要到期的hrtimer,所以我们需要判断一下,否则这个hrtimer要丢了。此外,如果是最早到期的hrtimer,还需要把对应的expires设到硬件寄存器中去。可以发现这个流程其实和中断处理完后的流程是类似的。在处理完后都需要把最近要到期的expires设到硬件寄存器中。所以这里enqueue 一个hrtimer之后马上会触发hrtimer软中断,在软中断处理函数hrtimer_interrupt中再重新将最近要到期的expires设到寄存器中。实现这一步,这里用到的是hrtimer_start_expires(),此外还有一个hrtimer_start(),这两个api功能都是一样的,先enqueue hrtimer到系统然后触发hrtimer软中断。

1.4.3. scheduler_tick

前面的分析是针对update_process_time()中的run_local_timer的流程,接着再分析update_process_time中另一个重要功能调度。Scheduler_tick的实现如下所示:

3157 void scheduler_tick(void)

3158 {

3159     int cpu = smp_processor_id();

3160     struct rq *rq = cpu_rq(cpu);

3161     struct task_struct *curr = rq->curr;

3162 

3163     sched_clock_tick();

3164 

3165     raw_spin_lock(&rq->lock);

3166     update_rq_clock(rq);

3167     update_cpu_load_active(rq);

3168     curr->sched_class->task_tick(rq, curr, 0);

3169     raw_spin_unlock(&rq->lock);

3170 

3171     perf_event_task_tick();

3172 

3173 #ifdef CONFIG_SMP

3174     rq->idle_balance = idle_cpu(cpu);

3175     trigger_load_balance(rq, cpu);

3176 #endif

3177 }

函数中通过3168行调用了当前使用的调度策略,将合适的task先打上need reschedflag。到这里中断处理的步骤才算全部执行完毕。

1.5. HRES中断的处理(hrtimer_interrupt的流程)

前面提到了hrtimer_interrupt,那就首先具体来看下这个函数的执行流程:

1254 void hrtimer_interrupt(struct clock_event_device *dev)

1255 {

1256     struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);

1257     ktime_t expires_next, now, entry_time, delta;

1258     int i, retries = 0;

1259 

1260     BUG_ON(!cpu_base->hres_active);

1261     cpu_base->nr_events++;

1262     dev->next_event.tv64 = KTIME_MAX;

1263 

1264     raw_spin_lock(&cpu_base->lock);

1265     entry_time = now = hrtimer_update_base(cpu_base);

1266 retry:

1267     expires_next.tv64 = KTIME_MAX;

1268     /*

1269      * We set expires_next to KTIME_MAX here with cpu_base->lock

1270      * held to prevent that a timer is enqueued in our queue via

1271      * the migration code. This does not affect enqueueing of

1272      * timers which run their callback and need to be requeued on

1273      * this CPU.

1274      */

1275     cpu_base->expires_next.tv64 = KTIME_MAX;

1276 

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

1278         struct hrtimer_clock_base *base;  

1279         struct timerqueue_node *node;

1280         ktime_t basenow;

1281 

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

1283             continue;

1284 

1285         base = cpu_base->clock_base + i;

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

1287 

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

1289             struct hrtimer *timer;

1290 

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

1292 

1293             /*

1294              * The immediate goal for using the softexpires is

1295              * minimizing wakeups, not running timers at the

1296              * earliest interrupt after their soft expiration.

1297              * This allows us to avoid using a Priority Search

1298              * Tree, which can answer a stabbing querry for

1299              * overlapping intervals and instead use the simple

1300              * BST we already have.

1301              * We don't add extra wakeups by delaying timers that

1302              * are right-of a not yet expired timer, because that

1303              * timer will have to trigger a wakeup anyway.

1304              */

1305 

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

1307                 ktime_t expires;

1308 

1309                 expires = ktime_sub(hrtimer_get_expires(timer),

1310                             base->offset);

1311                 if (expires.tv64 < expires_next.tv64)

1312                     expires_next = expires;

1313                 break;

1314             }  

1315 

1316             __run_hrtimer(timer, &basenow);

1317         }

1318     }

1319 

1320     /*

1321      * Store the new expiry value so the migration code can verify

1322      * against it.

1323      */

1324     cpu_base->expires_next = expires_next;

1325     raw_spin_unlock(&cpu_base->lock);

1326 

1327     /* Reprogramming necessary ? */

1328     if (expires_next.tv64 == KTIME_MAX ||

1329         !tick_program_event(expires_next, 0)) {

1330         cpu_base->hang_detected = 0;

1331         return;

1332     }

1333 

1334     /*

1335      * The next timer was already expired due to:

1336      * - tracing

1337      * - long lasting callbacks

1338      * - being scheduled away when running in a VM

1339      *

1340      * We need to prevent that we loop forever in the hrtimer

1341      * interrupt routine. We give it 3 attempts to avoid

1342      * overreacting on some spurious event.

1343      *

1344      * Acquire base lock for updating the offsets and retrieving

1345      * the current time.

1346      */

1347     raw_spin_lock(&cpu_base->lock);

1348     now = hrtimer_update_base(cpu_base);

1349     cpu_base->nr_retries++;  

1350     if (++retries < 3)

1351         goto retry;

1352     /*

1353      * Give the system a chance to do something else than looping

1354      * here. We stored the entry time, so we know exactly how long

1355      * we spent here. We schedule the next event this amount of

1356      * time away.

1357      */

1358     cpu_base->nr_hangs++;

1359     cpu_base->hang_detected = 1;

1360     raw_spin_unlock(&cpu_base->lock);

1361     delta = ktime_sub(now, entry_time);

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

1363         cpu_base->max_hang_time = delta;

1364     /*

1365      * Limit it to a sensible value as we enforce a longer

1366      * delay. Give the CPU at least 100ms to catch up.

1367      */

1368     if (delta.tv64 > 100 * NSEC_PER_MSEC)

1369         expires_next = ktime_add_ns(now, 100 * NSEC_PER_MSEC);

1370     else

1371         expires_next = ktime_add(now, delta);

1372     tick_program_event(expires_next, 1);

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

1374             ktime_to_ns(delta));

1375 }                                                                          

虽然这个函数很长,但是主要做的事情也没多少,主要是一个for循环,在这个for循环中搜找出到期的hrtimer,然后调用hrtimerfunc来处理。并且下一即将到期的next_expires也会更新一下,因为hrtimer的添加会导致这一结果的变化。最后就将next_expires设置到硬件寄存器中。从1347行开始的后续代码都是异常的处理,正常情况下不会进入。异常分支等将来有机会再分析。

1.6. sched_timer的中断处理tick_sched_timer

在前面hrtimer_interrupt中可以看到一个for循环,这个for循环中处理的就是到期的hrtimer。前面注册了sched_timer,如果到期来,func就是在这里通过__run_hrtimer(timer, &basenow)被调用的。下面看下sched_timer的回调函数tick_sched_timer

775 static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer)

776 {

777     struct tick_sched *ts =

778         container_of(timer, struct tick_sched, sched_timer);

779     struct pt_regs *regs = get_irq_regs();

780     ktime_t now = ktime_get();

781     int cpu = smp_processor_id();

782 

783 #ifdef CONFIG_NO_HZ

784     /*

785      * Check if the do_timer duty was dropped. We don't care about

786      * concurrency: This happens only when the cpu in charge went

787      * into a long sleep. If two cpus happen to assign themself to

788      * this duty, then the jiffies update is still serialized by

789      * xtime_lock.

790      */

791     if (unlikely(tick_do_timer_cpu == TICK_DO_TIMER_NONE))

792         tick_do_timer_cpu = cpu;

793 #endif

794 

795     /* Check, if the jiffies need an update */

796     if (tick_do_timer_cpu == cpu)

797         tick_do_update_jiffies64(now);

798 

799     /*

800      * Do not call, when we are not in irq context and have

801      * no valid regs pointer

802      */

803     if (regs) {

804         /*

805          * When we are idle and the tick is stopped, we have to touch

806          * the watchdog as we might not schedule for a really long

807          * time. This happens on complete idle SMP systems while

808          * waiting on the login prompt. We also increment the "start of

809          * idle" jiffy stamp so the idle accounting adjustment we do

810          * when we go busy again does not account too much ticks.

811          */

812         if (ts->tick_stopped) {

813             touch_softlockup_watchdog();

814             ts->idle_jiffies++;

815         }

816         update_process_times(user_mode(regs));

817         profile_tick(CPU_PROFILING);

818     }

819 

820     hrtimer_forward(timer, now, tick_period);

821 

822     return HRTIMER_RESTART;

823 }

这个函数中最重要的就是调用update_process_times去处理一些事务,update_process_times的实现在前面已经分析了,调用这个函数之后,系统需要被调度的task便被打上NEED_RESCHEDflag了。这也体现了周期性时钟sched_timer的价值,周期性的调度任务,再结合schedule()便实现完整的调度了。

1.7. os timer切换到core timer

1.1的分析可知,setup函数调用clockevents_config_and_register()后会触发CLOCK_EVT_NOTIFY_ADD通知,也就是会注册一个clock event到系统。从1.1的分析可知,总共有两处会调用到这个setup函数,分别是在运行secondary_start_kernel()smp_prepare_cpus()过程中。secondary_start_kernel()执行setup操作可以理解,因为此时需要初始化从核,所以从核的per cpu timer就应该初始化。再看smp_prepare_cpus()函数,调用此函数时SMP应该还没准备好,所以只有core0在运行,所以这里的setup就是针对core0per cpu timer(1.3.2注册过程针对的是os timer)

先看1.3.2中提到的tick_check_new_device(),当再次进入函数时,某些条件已经改变,所以函数的的执行流程有变化。重点看下tick_setup_device(),再次进入此函数时候tick_device中已经有clock event了,并且td->mode在切换到hres时已经被设置为ONESHOT了,具体如下所示:

175     } else {

176         handler = td->evtdev->event_handler;

177         next_event = td->evtdev->next_event;

178         td->evtdev->event_handler = clockevents_handle_noop;

179     }

    … …

201     else

202         tick_setup_oneshot(newdev, handler, next_event);

203 }

Linux针对时间系统设置了tick deviceclock eventclock event handler几个数据结构,这使得对象的切换变得非常灵活。例如这里看到的,tick_deviceper cpu类型,它包含一个clock event的成员。如果tick device要切换不同的clock event只要把这个指针修改到不同clock event即可。同样的,clock event通过clock event handler来具体处理中断事务。但是相同的handler却可以复给不同的clock event。例如我们这里的场景,系统从os timer切换到per cpu timer。我们只需要把tick device中的clock event指向arch_timer_evt(per cpuclock event),再把运来的handler取出来赋给arch_timer_evt即可,只替换了clock event,其它都可以保持不变,相当灵活。

再看tick_setup_oneshot(),因为此时td->mode已经不是PERIODIC模式了,所以需要执行tick_setup_oneshot(),具体如下:

48 void tick_setup_oneshot(struct clock_event_device *newdev,

 49             void (*handler)(struct clock_event_device *),

 50             ktime_t next_event)

 51 {

 52     newdev->event_handler = handler;

 53     clockevents_set_mode(newdev, CLOCK_EVT_MODE_ONESHOT);

 54     clockevents_program_event(newdev, next_event, true);

 55 }

先看输入的两个参数。newdev对应per cpu timerclock eventhandler就是hrtimer_interrupt,这是在switchhres模式时改的。再看函数的步骤就非常简单了:

1. per cpu timerhandler初始化为hrtimer_interrupt

2. 设置clock event的模式为oneshot

3. 将最近到期的时间值通过per cpu timer的接口写入寄存器。

然后就等着per cpu timer下次中断的到来了!

2. 调度

调度的flagTIF_NEED_RESCHED,TIF_NEED_RESCHED的置位会导致TIF_WORK_MASK的置位。

用户抢占(运行用户空间程序时触发的schedule)

1. 系统调用

返回用户空间的异常入口是vector_swiVector_swi->adr lr, BSYM(ret_fast_syscall)

所以系统调用结束时是从ret_fast_syscall返回user space的。如果在系统调用的过程中TIF_NEED_RESCHED被置位,那么在ret_fast_syscall中有机会调用schedule来调度其它进程。

2. Irq_user

user space中产生中断后会进入irq_user处理,处理完中断后通过ret_to_user_from_irq返回user space。和系统调用一样,如果TIF_NEED_RESCHED在中断处理过程中被置位,那么ret_to_user_from_irq中就有机会调用schedule。  

内核抢占(内核线程触发的schedule)

1. 中断退出时(svc模式)

中断退出时irq_svc->svc_preempt-> preempt_schedule_irq

2. 抢占重新开启

preempt_enable()->preempt_check_resched()->preempt_schedule()->__schedule()

3. 代码中显式调用schdule的地方

4. 任务被阻塞

比如wait_eventwait_event_timeoutwait_event_interruptiablewait_event_interruptible_timeout等都会导致任务被schedule出去。

猜你喜欢

转载自blog.csdn.net/rockrockwu/article/details/79593014