Linux时间子系统之四:定时器的引擎:clock_event_device
Posted 请给我倒杯茶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux时间子系统之四:定时器的引擎:clock_event_device相关的知识,希望对你有一定的参考价值。
本文转载自:http://blog.csdn.net/droidphone/article/details/8017604
版权声明:本文为博主原创文章,未经博主允许不得转载。
早期的内核版本中,进程的调度基于一个称之为tick的时钟滴答,通常使用时钟中断来定时地产生tick信号,每次tick定时中断都会进行进程的统计和调度,并对tick进行计数,记录在一个jiffies变量中,定时器的设计也是基于jiffies。这时候的内核代码中,几乎所有关于时钟的操作都是在machine级的代码中实现,很多公共的代码要在每个平台上重复实现。随后,随着通用时钟框架的引入,内核需要支持高精度的定时器,为此,通用时间框架为定时器硬件定义了一个标准的接口:clock_event_device,machine级的代码只要按这个标准接口实现相应的硬件控制功能,剩下的与平台无关的特性则统一由通用时间框架层来实现。
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!/*****************************************************************************************************/
1. 时钟事件软件架构
本系列文章的第一节中,我们曾经讨论了时钟源设备:clocksource,现在又来一个时钟事件设备:clock_event_device,它们有何区别?看名字,好像都是给系统提供时钟的设备,实际上,clocksource不能被编程,没有产生事件的能力,它主要被用于timekeeper来实现对真实时间进行精确的统计,而clock_event_device则是可编程的,它可以工作在周期触发或单次触发模式,系统可以对它进行编程,以确定下一次事件触发的时间,clock_event_device主要用于实现普通定时器和高精度定时器,同时也用于产生tick事件,供给进程调度子系统使用。时钟事件设备与通用时间框架中的其他模块的关系如下图所示:
图1.1 clock_event_device软件架构
- 与clocksource一样,系统中可以存在多个clock_event_device,系统会根据它们的精度和能力,选择合适的clock_event_device对系统提供时钟事件服务。在smp系统中,为了减少处理器间的通信开销,基本上每个cpu都会具备一个属于自己的本地clock_event_device,独立地为该cpu提供时钟事件服务,smp中的每个cpu基于本地的clock_event_device,建立自己的tick_device,普通定时器和高精度定时器。
- 在软件架构上看,clock_event_device被分为了两层,与硬件相关的被放在了machine层,而与硬件无关的通用代码则被集中到了通用时间框架层,这符合内核对软件的设计需求,平台的开发者只需实现平台相关的接口即可,无需关注复杂的上层时间框架。
- tick_device是基于clock_event_device的进一步封装,用于代替原有的时钟滴答中断,给内核提供tick事件,以完成进程的调度和进程信息统计,负载平衡和时间更新等操作。
2. 时钟事件设备相关数据结构
2.1 struct clock_event_device
时钟事件设备的核心数据结构是clock_event_device结构,它代表着一个时钟硬件设备,该设备就好像是一个具有事件触发能力(通常就是指中断)的clocksource,它不停地计数,当计数值达到预先编程设定的数值那一刻,会引发一个时钟事件中断,继而触发该设备的事件处理回调函数,以完成对时钟事件的处理。clock_event_device结构的定义如下:
- struct clock_event_device {
- void (*event_handler)(struct clock_event_device *);
- int (*set_next_event)(unsigned long evt,
- struct clock_event_device *);
- int (*set_next_ktime)(ktime_t expires,
- struct clock_event_device *);
- ktime_t next_event;
- u64 max_delta_ns;
- u64 min_delta_ns;
- u32 mult;
- u32 shift;
- enum clock_event_mode mode;
- unsigned int features;
- unsigned long retries;
- void (*broadcast)(const struct cpumask *mask);
- void (*set_mode)(enum clock_event_mode mode,
- struct clock_event_device *);
- unsigned long min_delta_ticks;
- unsigned long max_delta_ticks;
- const char *name;
- int rating;
- int irq;
- const struct cpumask *cpumask;
- struct list_head list;
- } ____cacheline_aligned;
event_handler 该字段是一个回调函数指针,通常由通用框架层设置,在时间中断到来时,machine底层的的中断服务程序会调用该回调,框架层利用该回调实现对时钟事件的处理。
set_next_event 设置下一次时间触发的时间,使用类似于clocksource的cycle计数值(离现在的cycle差值)作为参数。
set_next_ktime 设置下一次时间触发的时间,直接使用ktime时间作为参数。
max_delta_ns 可设置的最大时间差,单位是纳秒。
min_delta_ns 可设置的最小时间差,单位是纳秒。
mult shift 与clocksource中的类似,只不过是用于把纳秒转换为cycle。
mode 该时钟事件设备的工作模式,两种主要的工作模式分别是:
- CLOCK_EVT_MODE_PERIODIC 周期触发模式,设置后按给定的周期不停地触发事件;
- CLOCK_EVT_MODE_ONESHOT 单次触发模式,只在设置好的触发时刻触发一次;
set_mode 函数指针,用于设置时钟事件设备的工作模式。
rating 表示该设备的精度等级。
list 系统中注册的时钟事件设备用该字段挂在全局链表变量clockevent_devices上。
2.2 全局变量clockevent_devices
- /* Notification for clock events */
- static RAW_NOTIFIER_HEAD(clockevents_chain);
3. clock_event_device的初始化和注册
- struct sys_timer {
- void (*init)(void);
- void (*suspend)(void);
- void (*resume)(void);
- #ifdef CONFIG_ARCH_USES_GETTIMEOFFSET
- unsigned long (*offset)(void);
- #endif
- };
- MACHINE_START(SMDK4412, "SMDK4412")
- /* Maintainer: Kukjin Kim <[email protected]> */
- /* Maintainer: Changhwan Youn <[email protected]> */
- .atag_offset = 0x100,
- .init_irq = exynos4_init_irq,
- .map_io = smdk4x12_map_io,
- .handle_irq = gic_handle_irq,
- .init_machine = smdk4x12_machine_init,
- .timer = &exynos4_timer,
- .restart = exynos4_restart,
- MACHINE_END
- static void __init exynos4_timer_init(void)
- {
- if (soc_is_exynos4210())
- mct_int_type = MCT_INT_SPI;
- else
- mct_int_type = MCT_INT_PPI;
- exynos4_timer_resources();
- exynos4_clocksource_init();
- exynos4_clockevent_init();
- }
- struct sys_timer exynos4_timer = {
- .init = exynos4_timer_init,
- };
- static struct clock_event_device mct_comp_device = {
- .name = "mct-comp",
- .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
- .rating = 250,
- .set_next_event = exynos4_comp_set_next_event,
- .set_mode = exynos4_comp_set_mode,
- };
- ......
- static void exynos4_clockevent_init(void)
- {
- clockevents_calc_mult_shift(&mct_comp_device, clk_rate, 5);
- ......
- mct_comp_device.cpumask = cpumask_of(0);
- clockevents_register_device(&mct_comp_device);
- setup_irq(EXYNOS4_IRQ_MCT_G0, &mct_comp_event_irq);
- }
- /*
- * Timer (local or broadcast) support
- */
- static DEFINE_PER_CPU(struct clock_event_device, percpu_clockevent);
- static int __cpuinit exynos4_local_timer_setup(struct clock_event_device *evt)
- {
- ......
- evt->name = mevt->name;
- evt->cpumask = cpumask_of(cpu);
- evt->set_next_event = exynos4_tick_set_next_event;
- evt->set_mode = exynos4_tick_set_mode;
- evt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
- evt->rating = 450;
- clockevents_calc_mult_shift(evt, clk_rate / (TICK_BASE_CNT + 1), 5);
- ......
- clockevents_register_device(evt);
- ......
- enable_percpu_irq(EXYNOS_IRQ_MCT_LOCALTIMER, 0);
- ......
- return 0;
- }
图3.1 clock_event_device的系统初始化
由上面的图示可以看出,框架层的初始化步骤很简单,又start_kernel开始,调用tick_init,它位于kernel/time/tick-common.c中,也只是简单地调用clockevents_register_notifier,同时把类型为notifier_block的tick_notifier作为参数传入,回看2.3节,clockevents_register_notifier注册了一个通知链,这样,当系统中的clock_event_device状态发生变化时(新增,删除,挂起,唤醒等等),tick_notifier中的notifier_call字段中设定的回调函数tick_notify就会被调用。接下来start_kernel调用了time_init函数,该函数通常定义在体系相关的代码中,正如前面所讨论的一样,它主要完成machine级别对时钟系统的初始化工作,最终通过clockevents_register_device注册系统中的时钟事件设备,把每个时钟时间设备挂在clockevent_device全局链表上,最后通过clockevent_do_notify触发框架层事先注册好的通知链,其实就是调用了tick_notify函数,我们主要关注CLOCK_EVT_NOTIFY_ADD通知,其它通知请自行参考代码,下面是tick_notify的简化版本:
- static int tick_notify(struct notifier_block *nb, unsigned long reason,
- void *dev)
- {
- switch (reason) {
- case CLOCK_EVT_NOTIFY_ADD:
- return tick_check_new_device(dev);
- case CLOCK_EVT_NOTIFY_BROADCAST_ON:
- case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
- case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
- ......
- case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
- case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
- ......
- case CLOCK_EVT_NOTIFY_CPU_DYING:
- ......
- case CLOCK_EVT_NOTIFY_CPU_DEAD:
- ......
- case CLOCK_EVT_NOTIFY_SUSPEND:
- ......
- case CLOCK_EVT_NOTIFY_RESUME:
- ......
- }
- return NOTIFY_OK;
- }
4. tick_device
- struct tick_device {
- struct clock_event_device *evtdev;
- enum tick_device_mode mode;
- };
- /*
- * Tick devices
- */
- DEFINE_PER_CPU(struct tick_device, tick_cpu_device);
- static int tick_check_new_device(struct clock_event_device *newdev)
- {
- ......
- cpu = smp_processor_id();
- if (!cpumask_test_cpu(cpu, newdev->cpumask))
- goto out_bc;
- td = &per_cpu(tick_cpu_device, cpu);
- curdev = td->evtdev;
- if (!cpumask_equal(newdev->cpumask, cpumask_of(cpu))) {
- ......
- if (!irq_can_set_affinity(newdev->irq))
- goto out_bc;
- ......
- if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))
- goto out_bc;
- }
- if (curdev) {
- if ((curdev->features & CLOCK_EVT_FEAT_ONESHOT) &&
- !(newdev->features & CLOCK_EVT_FEAT_ONESHOT))
- goto out_bc; // 新的不支持单触发,但旧的支持,所以不能替换
- if (curdev->rating >= newdev->rating)
- goto out_bc; // 旧的比新的精度高,不能替换
- }
- if (tick_is_broadcast_device(curdev)) {
- clockevents_shutdown(curdev);
- curdev = NULL;
- }
- clockevents_exchange_device(curdev, newdev);
- tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
5. tick事件的处理--最简单的情况
- CONFIG_NO_HZ == 0;
- CONFIG_HIGH_RES_TIMERS == 0;
- if (td->mode == TICKDEV_MODE_PERIODIC)
- tick_setup_periodic(newdev, 0);
- else
- tick_setup_oneshot(newdev, handler, next_event);
- void tick_handle_periodic(struct clock_event_device *dev)
- {
- int cpu = smp_processor_id();
- ktime_t next;
- tick_periodic(cpu);
- if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
- return;
- next = ktime_add(dev->next_event, tick_period);
- for (;;) {
- if (!clockevents_program_event(dev, next, false))
- return;
- if (timekeeping_valid_for_hres())
- tick_periodic(cpu);
- next = ktime_add(next, tick_period);
- }
- }
- static void tick_periodic(int cpu)
- {
- if (tick_do_timer_cpu == cpu) {
- write_seqlock(&xtime_lock);
- /* Keep track of the next tick event */
- tick_next_period = ktime_add(tick_next_period, tick_period);
- do_timer(1);
- write_sequnlock(&xtime_lock);
- }
- update_process_times(user_mode(get_irq_regs()));
- profile_tick(CPU_PROFILING);
- }
- 更新jiffies_64变量;
- 更新墙上时钟;
- 每10个tick,更新一次cpu的负载信息;
- 更新进程的时间统计信息;
- 触发TIMER_SOFTIRQ软件中断,以便系统处理传统的低分辨率定时器;
- 检查rcu的callback;
- 通过scheduler_tick触发调度系统进行进程统计和调度工作;
以上是关于Linux时间子系统之四:定时器的引擎:clock_event_device的主要内容,如果未能解决你的问题,请参考以下文章