本文共 5170 字,大约阅读时间需要 17 分钟。
摘抄并整理 http://www.wowotech.net/timer_subsystem/time_subsystem_index.html
时间子系统1.0 1. 生产者 在每个architecture相关的代码中要有实现clock event和clock source模块。 clock event 就是通过timer硬件的中断处理函数完成的,在此基础上可以构建tick模块,tick模块维护了系统的tick 例如系统存在10ms的tick,每次tick到来的时候,timekeeping模块就增加系统时间 如果timekeeping完全是tick驱动,那么它精度只能是10ms clock source 为了更高精度,clock source模块就是一个提供tick之间的offset时间信息的接口函数。 但是实际上并没有提供高精度 2.内核消费者 最初的linux kernel中只支持低精度的timer,也就是基于tick的timer。 如果内核采用10ms的tick,那么低精度的timer的最高的精度也只有10ms,想要取得us甚至ns级别的精度是不可能的。 各个内核的驱动模块都可以使用低精度timer实现自己的定时功能。 各个进程的时间统计也是基于tick的,内核的调度器根据这些信息进行调度。 System Load和Kernel Profiling模块也是基于tick的,用于计算系统负荷和进行内核性能剖析。 3.用户消费者 从用户空间空间来看,有两种需求 一种是获取或者设定当前的系统时间的接口函数:例如time,stime,gettimeofday等。 另外一种是timer相关的接口函数:例如setitimer、alarm等,当timer到期后会向该进程发送signal。 问题: 1. 嵌入式设备需要较好的电源管理策略 , 但是当前系统 周期性时钟会导致从低功耗(idle)状态进入高功耗,不符合电源管理的需求 2. 多媒体的应用程序需要非常精确的timer(ns),但是当前系统 不能提供足够精度的timer(ms)时间子系统2.0 改变: A. 新增内核配置 CONFIG_NO_HZ_IDLE // 在系统idle的时候,停掉周期性tick B. 新增 高精度 timer ,同时 保留 低精度 timer C. 其实 A B 的更改都是在 生产者 这块改动的,内核消费者与用户消费者都没变化 1.时间子系统2.0的选择 CONFIG_GENERIC_CLOCKEVENTS 2.时间子系统2.0下,针对A的配置,三种情况 a. 非idle, 周期性tick idle, 周期性tick : CONFIG_HZ_PERIODIC b. 非idle, 周期性tick idle,停掉周期性tick : CONFIG_NO_HZ_IDLE c. 非idle,停掉周期性tick idle,停掉周期性tick : CONFIG_NO_HZ_FULL 3.时间子系统2.0下,针对B的配置,两种情况 a. 无高精度timer : CONFIG_HIGH_RES_TIMERS=n b. 有高精度timer : CONFIG_HIGH_RES_TIMERS CONFIG_TICK_ONESHOT// 在 时间子系统上封装出了// 1. tick device : 负责调度(周期性调度和非周期性调用)// 2. timer : 负责定时器(高精度定时器和低精度定时器)
每一个cpu 有一个 tick device (clock event device) // SMP下 ,多个cpu对应的 tick device 中有一个被选择做global tick device,该device负责维护整个系统的jiffies以及更新哪些基于jiffies进行的全系统统计信息。tick device 根据 类型 分为两种 periodic tick device 可以按照固定的时间间隔产生tick event event handler是 tick_handle_periodic (没有配置高精度timer)或 hrtimer_interrupt(配置了高精度timer) one-shot tick device 设定后只能产生一次tick event,如果要连续产生tick event,那么需要每次都进行设定 event handler是 tick_nohz_handler(没有配置高精度timer)或 hrtimer_interrupt(配置了高精度timer)
linux-5.11 ok6410a 采用 了 时间子系统2.0 无高精度timer 非idle, 周期性tick idle, 周期性tick : CONFIG_HZ_PERIODIC
每当底层有新的 clockevent device 加入到系统中的时候 1.会调用 clockevents_register_device 或者clockevents_config_and_register 向通用clockevent layer注册一个新的clockevent设备 2.会调用 tick_check_new_device 通知 tick device layer有新货到来。 3.如果tick device和clockevent device你情我愿,那么就会调用tick_setup_device函数setup这个tick device了。 a.一般而言,刚系统初始化的时候,所有cpu的tick device都没有匹配clock event device b.因此,该cpu的local tick device也就是global tick device了。 c.而且,如果tick device是新婚(匹配之前,tick device的clock event device等于NULL),那么tick device的模式将被设定为 TICKDEV_MODE_PERIODIC ,即便clock event有one shot能力,即便系统配置了NO HZ。 4. 对应 TICKDEV_MODE_PERIODIC , setup函数 是 tick_setup_periodic // tick_setup_periodic 函数用来设定一个periodic tick device。 5. setup第一部分:设定 event handler 为 tick_handle_periodic // 对于周期性tick device,其clock event device的handler被设定为 tick_handle_periodic。 6. setup第二部分:调用 clockevent device layer 的接口函数 kick off底层的硬件,让其周期性的产生clock event,
time_init machine_desc->init_time/s3c64xx_timer_init samsung_pwm_clocksource_init(S3C_VA_TIMER, timer_irqs, &s3c64xx_pwm_variant); pwm.base = base; ... pwm.timerclk = clk_get(NULL, "timers"); _samsung_pwm_clocksource_init samsung_timer_resources samsung_clockevent_init clockevents_config_and_register clockevents_register_device tick_check_new_device tick_setup_device void (*handler)(struct clock_event_device *) = NULL; tick_setup_periodic(newdev, 0); tick_set_periodic_handler dev->event_handler = tick_handle_periodic; clockevents_program_event tick_setup_oneshot(newdev, handler, next_event); request_irq(irq_number, samsung_clock_event_isr, IRQF_TIMER | IRQF_IRQPOLL, "samsung_time_irq", &time_event_device) samsung_clocksource_init sched_clock_register
time_init boot cpu ,在其初始化过程中会调用 time_init 这里会启动clocksource的初始化过程。这时候,周期性的tick就会开始了。 smp_init 其他的processor会启动,然后会注册其自己的local timer,这样,各个cpu上的tick就都启动了。
// tick 发生时,走的是中断// 这里面主要设置的 是 need_resched irq_usr ... ... samsung_clock_event_isr evt->event_handler/tick_handle_periodic tick_periodic(cpu); update_process_times // 根据task_struct 里面时间片相关的成员 做 set_tsk_need_resched scheduler_tick sched_clock_tick curr->sched_class->task_tick(rq, curr, 0); if (!clockevent_state_oneshot(dev)) return; next = ktime_add_ns(next, TICK_NSEC);
调用 调度 的 途径有很多 1. 直接调用 进程主动放弃cpu执行调度 2. 系统调用(通过检查 need_resched ) 中断返回 系统调用返回用户空间 信号处理完成后返回内核空间设置 need_resched 的途径还有很多// tick 是 设置 need_resched 的 一个途径 1. 2.
返回用户空间时调度 need_reschedtick 属于 第一种__irq_usr irq_handler b ret_to_user_from_irq // 从任务的TI_FLAGS标志判断是否需要处理抢占或者信号 ldr r1, [tsk, #TI_FLAGS] movs r1, r1, lsl #16 // 处理抢占或者信号 bne slow_work_pending mov r0, sp @ 'regs' mov r2, why @ 'syscall' bl do_work_pending // 该函数内会调用 schedule // 此时 表示 没有抢占或者信号要处理 // 或者 抢占或者信号 已经处理完毕 no_work_pending: ... restore_user_regs fast = 0, offset = 0__irq_svc irq_handler #ifdef CONFIG_PREEMPTION blne svc_preempt bl preempt_schedule_irq __schedule #endif svc_exit r5, irq = 1
转载地址:http://knigi.baihongyu.com/