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有两个,分别是:

  • clockevents_notify
  • clockevents_register_device

先看clockevents_notify,clockevents_notify最终通过调用clockevents_do_notify(reason, arg)来触发通知,但参数reason、arg由调用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已经添加到系统了。搜索可知,当前有两处:

  • via_clockevent_init
  • clockevents_config_and_register

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

扫描二维码关注公众号,回复: 3921096 查看本文章

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

  • secondary_start_kernel
  • 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/offline时timers系统所需要完成的对应工作。如在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去初始化这个core的timer系统了。

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

  • tick_handle_periodic-> tick_periodic-> update_process_times-> run_local_timers
  • hrtimer_interrupt-> __run_hrtimer->(调用timer的func …… tick_sched_timer.func = tick_sched_timer) tick_sched_timer-> update_process_times->run_local_timers

这里先补充下tick_handle_periodic和hrtimer_interrupt知识,这两个都是clock event的handler,具有当前系统用的是哪个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.注册一个rate为200的clocksource到系统。
  • 3.初始化一个sched_clock_timer,到时候给周期性时钟sched_timer使用
  • 4.注册一个clock event devices到系统
  • 5.enable os timer
  • 6.注册 arch timer,也就是属于core的timer

其中比较重要的步骤是2、3、4和6,下面具体分析。

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_cpu、tick_next_period、tick_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被初始化成当前的ktime,tick_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 device的mode设置为CLOCK_EVT_MODE_ONESHOT
  • 3.设置下一中断间隔时间到timer的match寄存器

设置handler比较简单,通过调用tick_set_periodic_handler就可以实现。设置clock event device的mode主要是后续在tick中断处理函数中需要判断当前的mod的,后续会提到。设置timer的match寄存器就需要多一些功夫了,因为需要判断你提供的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 }   

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

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

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 event的handler设置和timer的match寄存器设置,现在就算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 timer的clock 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 timer的handler,
  • 3.注册per cpu timer的ops函数,该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 timer的handler,而这里初始化的是per cpu timer的handler,且这个handler和arch_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 };    

这里有两个成员,分别是setup和stop。这两个接口就是用于初始化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 timer的clock event做对比,rating更高了,而这里set_next_event的操作对象也由之前的os timer变成per cpu timer了。

到此整个timer系统的初始化工作就完成了,只是当前的clock event还是使用os timer期间注册的clock event。但是clock source已经切换到更高精度的了,并且core timer的clock 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 0的timer中断需要用来唤醒core2就是SGI(如果这么做可以的话)。SPI和PPI在顶层的入口是一样的都是handle_IRQ,而SGI则由handle_IP来处理。无论是os timer还是core自己的timer,最终都会调用到evt-> event_handler,也就是由clock events 的handler来具体处理。

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

  • 1.os timer:tick_handle_periodic/hrtime_interrupt
  • 2.core timer:arch_timer_handler
  • 3.inter core:ipi_timer

根据前面的分析,系统无论是periodic模式还是oneshot模式,第一次时钟中断发生时,最终总是由clock event的tick_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。那也就是说如果说当前cpu是core0那么我们就执行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_periodic是periodic 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(),从低精度timer到hres的转换就主要由这个函数来实现了,具体过程如下:

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 event的handler设置为tick_handler_periodic,而在这里我们需要把他改成oneshot模式对应的handler了,也就是hrtimer_interrupt。tick_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 device的mode重新设置为ONESHOT了(之前是PERIODIC模式),并且clock event的mode也设置为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_device和clock_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 event的handler,那看起来这两者有共同的事情要处理。

先梳理下hres模式的整个运行流程(更具体的参考网上文章)。hres模式下的最小单位是hrtimer,hrtimer中有个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 resched的flag。到这里中断处理的步骤才算全部执行完毕。

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,然后调用hrtimer的func来处理。并且下一即将到期的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_RESCHED的flag了。这也体现了周期性时钟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就是针对core0的per 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 device、clock event、clock event handler几个数据结构,这使得对象的切换变得非常灵活。例如这里看到的,tick_device是per 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 cpu的clock 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 timer的clock event。handler就是hrtimer_interrupt,这是在switch到hres模式时改的。再看函数的步骤就非常简单了:

  • 1.将per cpu timer的handler初始化为hrtimer_interrupt
  • 2.设置clock event的模式为oneshot
  • 3.将最近到期的时间值通过per cpu timer的接口写入寄存器。

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

2. 调度

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

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

  • 1.系统调用。
    返回用户空间的异常入口是vector_swi,Vector_swi->adr lr, BSYM(ret_fast_syscall)
    所以系统调用结束时是从ret_fast_syscall返回user space的。如果在系统调用的过程中TIF_NEED_RESCHED被置位,那么在ret_fast_syscall中有机会调用schedule来调度其它进程。

    1. 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
    1. 抢占重新开启
      preempt_enable()->preempt_check_resched()->preempt_schedule()->__schedule()
    1. 代码中显式调用schdule的地方
    1. 任务被阻塞
      比如wait_event、wait_event_timeout、wait_event_interruptiable、wait_event_interruptible_timeout等都会导致任务被schedule出去。

猜你喜欢

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