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初始化过程中,这需要我们稍后仔细分析。
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来调度其它进程。 -
- Irq_user
在user space中产生中断后会进入irq_user处理,处理完中断后通过ret_to_user_from_irq返回user space。和系统调用一样,如果TIF_NEED_RESCHED在中断处理过程中被置位,那么ret_to_user_from_irq中就有机会调用schedule。
- Irq_user
内核抢占(内核线程触发的schedule)。
-
- 中断退出时(svc模式)
中断退出时irq_svc->svc_preempt-> preempt_schedule_irq
- 中断退出时(svc模式)
-
- 抢占重新开启
preempt_enable()->preempt_check_resched()->preempt_schedule()->__schedule()
- 抢占重新开启
-
- 代码中显式调用schdule的地方
-
- 任务被阻塞
比如wait_event、wait_event_timeout、wait_event_interruptiable、wait_event_interruptible_timeout等都会导致任务被schedule出去。
- 任务被阻塞