kernel-2.6.22中的arm arch加入了对dynticks, clocksource/event支持. imx31的BSP在clock这里有一些改动. 找了些kernel clock及timer子系统近来的变化, 总结一下. 一般来说Soft-Timer (timer wheel / hrtimer) 都是由Hardware-Timer(时钟中断之类)以及相关的clock source(e.g GPT in Soc)驱动, 所以我打算先从clock这层开始介绍, 接着是soft-timer, kernel timekeeping, 最后来看一些应用. Clock Sourceclock source定义了一个clock device的基本属性及行为, 这些clock device一般都有计数, 定时, 产生中断能力, 比如GPT. 结构定义如下:
最重要的成员是read(), cycle_last和cycle_interval. 分别定义了读取clock device count 寄存器当前计数值接口, 保存上一次周期计数值和每个tick周期间隔值. 这个结构内的值, 无论是cycle_t, 还是u64类型(实际cycle_t就是u64)都是计数值(cycle), 而不是nsec, sec和jiffies. read()是整个kernel读取精确的单调时间计数的接口, kernel会用它来计算其他时间, 比如:jiffies, xtime. clocksource的引入, 解决了之前kernel各个arch都有自己的clock device的管理方式, 基本都隐藏在MSL层, kernel core 及driver很难访问的问题. 它导出了以下接口: 1) clocksource_register() 注册clocksource 2) clocksource_get_next() 获取当前clocksource设备 3) clocksource_read() 读取clock, 实际跑到clocksource->read() 当driver处理的时间精度比较高的时, 可以通过上面的接口, 直接拿clock device来读. 当然目前ticker时钟中断源也会以clocksource的形式存在. Clock EventClock event的主要作用是分发clock事件及设置下一次触发条件. 在没有clock event之前, 时钟中断都是周期性地产生, 也就是熟知的jiffies和HZ.Clock Event device主要的结构:
hrtimer & timer wheel首先说一下timer wheel. 它就是kernel一直采用的基于jiffies的timer机制, 接口包括init_timer(), mod_timer(), del_timer()等, 很熟悉把.hrtimer 的出现, 并没有抛弃老的timer wheel机制(也不太可能抛弃:)). hrtimer做为kernel里的timer定时器, 而timer wheel则主要用来做timeout定时器. 分工比较明确. hrtimers采用红黑树来组织timers, 而timer wheel采用链表和桶. hrtimer精度由原来的timer wheel的jiffies提高到nanosecond. 主要用于向应用层提供nanosleep, posix-timers和itimer接口, 当然驱动和其他子系统也会需要high resolution的timer. kernel 里原先每秒周期性地产生HZ个ticker(中断), 被在下一个过期的hrtimer的时间点上产生中断代替. 也就是说时钟中断不再是周期性的, 而是由timer来驱动(靠clockevent的set_next_event接口设置下一个事件中断), 只要没有hrtimer加载, 就没有中断. 但是为了保证系统时间(进程时间统计, jiffies的维护)更新, 每个tick_period(NSEC_PER_SEC/HZ, 再次强调hrtimer精度是nsec)都会有一个叫做tick_sched_timer的hrtimer加载. ![]() 1)no hrtimerkernel 起来, setup_arch()之后的time_init()会去初始化相应machine结构下的timer. 初始化timer函数都在各个machine的体系结构代码中, 初始化完硬件时钟, 注册中断服务函数, 使能时钟中断. 中断服务程序会清中断, 调用timer_tick(), 它执行:1. profile_tick(); /* kernel profile, 不是很了解 */ 2. do_timer(1); /* 更新jiffies */ 3. update_process_times(); /* 计算进程耗时, 唤起TIMER_SOFTIRQ(timer wheel), 重新计算调度时间片等等 */ 最后中断服务程序设置定时器, 使其在下一个tick产生中断. 这样的框架, 使得high-res的timer很难加入. 所有中断处理code都在体系结构代码里被写死, 并且代码重用率很低, 毕竟大多的arch都会写同样的中断处理函数. 2)hrtimerkernel 里有了clockevent/source的引入, 就把clocksource的中断以一种事件的方式被抽象出来. 事件本身的处理交给event handler. handler可以在kernel里做替换从而改变时钟中断的行为. 时钟中断ISR会看上去象这样:
event_handler 在注册clockevent device时, 会被默认设置成tick_handle_periodic(). 所以kernel刚起来的时候, 时钟处理机制仍然是periodic的, ticker中断周期性的产生. tick_handle_periodic()会做和timer_tick差不多的事情, 然后调用clockevents_program_event() => arch_clockevent.set_next_event()去设置下一个周期的定时器. tick-common.c里把原来kernel时钟的处理方式在clockevent框架下实现了, 这就是periodic tick的时钟机制. hres tick机制在第一个TIMER SOFTIRQ里会替换掉periodic tick, 当然要符合一定条件, 比如command line里没有把hres(highres=off)禁止掉, clocksource/event支持hres和oneshot的能力. 这里的切换做的比较ugly, 作者的comments也提到了, 每次timer softirq被调度, 都要调用hrtimer_run_queues()检查一遍hres是否active, 如果能在timer_init()里就把clocksource/event的条件check过, 直接切换到hres就最好了, 不知道是不是有什么限制条件. TIMER SOFTIRQ代码如下:
切换的过程比较简单, 用hrtimer_interrupt()替换当前clockevent hander, 加载一个hrtimer: tick_sched_timer在下一个tick_period过期, retrigger下一次事件. hrtimer_interrupt() 将过期的hrtimers从红黑树上摘下来, 放到相应clock_base->cpu_base->cb_pending列表里, 这些过期timers会在HRTIMER_SOFTIRQ里执行. 然后根据剩余的最早过期的timer来retrigger下一个event, 再调度HRTIMER_SOFTIRQ. hrtimer softirq执行那些再cb_pending上的过期定时器函数. tick_sched_timer这个hrtimer在每个tick_period都会过期, 执行过程和timer_tick()差不多, 只是在最后调用hrtimer_forward将自己加载到下一个周期里去, 保证每个tick_period都能正确更新kernel内部时间统计. TimekeepingTimekeeping子系统负责更新xtime, 调整误差, 及提供get/settimeofday接口. 为了便于理解, 首先介绍一些概念: Times in Kernelkernel的time基本类型:1) system time A monotonically increasing value that represents the amount of time the system has been running. 单调增长的系统运行时间, 可以通过time source, xtime及wall_to_monotonic计算出来. 2) wall time A value representing the the human time of day, as seen on a wrist-watch. Realtime时间: xtime. 3) time source A representation of a free running counter running at a known frequency, usually in hardware, e.g GPT. 可以通过clocksource->read()得到counter值 4) tick A periodic interrupt generated by a hardware-timer, typically with a fixed interval defined by HZ: jiffies 这些time之间互相关联, 互相可以转换. system_time = xtime + cyc2ns(clock->read() - clock->cycle_last) + wall_to_monotonic; real_time = xtime + cyc2ns(clock->read() - clock->cycle_last) 也就是说real time是从1970年开始到现在的nanosecond, 而system time是系统启动到现在的nanosecond. 这两个是最重要的时间, 由此hrtimer可以基于这两个time来设置过期时间. 所以引入两个clock base. Clock BaseCLOCK_REALTIME: base在实际的wall timeCLOCK_MONOTONIC: base在系统运行system time hrtimer可以选择其中之一, 来设置expire time, 可以是实际的时间, 也可以是相对系统的时间. 他们提供get_time()接口: CLOCK_REALTIME 调用ktime_get_real()来获得真实时间, 该函数用上面提到的等式计算出realtime. CLOCK_MONOTONIC 调用ktime_get(), 用system_time的等式获得monotonic time. timekeeping提供两个接口do_gettimeofday()/do_settimeofday(), 都是针对realtime操作. 用户空间对gettimeofday的syscall也会最终跑到这里来. do_gettimeofday()会调用__get_realtime_clock_ts()获得时间, 然后转成timeval. do_settimeofday(), 将用户设置的时间更新到xtime, 重新计算xtime到monotonic的转换值, 最后通知hrtimers子系统时间变更.
Userspace Applicationhrtimer的引入, 对用户最有用的接口如下:Clock API clock_gettime(clockid_t, struct timespec *) 获取对应clock的时间 clock_settime(clockid_t, const struct timespec *) 设置对应clock时间 clock_nanosleep(clockid_t, int, const struct timespec *, struct timespec *) 进程nano sleep clock_getres(clockid_t, struct timespec *) 获取时间精度, 一般是nanosec clockid_t 定义了四种clock:
Timer API Timer 可以建立进程定时器,单次或者周期性定时。 int timer_create(clockid_t clockid, struct sigevent *restrict evp, timer_t *restrict timerid); 创建定时器。 clockid 指定在哪个clock base下创建定时器。 evp (sigevent) 可以指定定时器到期后内核发送哪个信号给进程,以及信号所带参数;默认为SIGALRM。 timerid 返回所建timer的id号。 在 signal处理函数里,可以通过siginfo_t.si_timerid 获得当前的信号是由哪个timer过期触发的。 试验了一下,最多可创建的timer数目和ulimit里的pending signals的有关系,不能超过pending signals的数量。 int timer_gettime(timer_t timerid, struct itimerspec *value); 获得timer的下次过期的时间。 int timer_settime(timer_t timerid, int flags, const struct itimerspec *restrict value, struct itimerspec *restrict ovalue); 设置定时器的过期时间及间隔周期。 int timer_delete(timer_t timerid); 删除定时器。 这些系统调用都会建立一个posix_timer的hrtimer,在过期的时候发送信号给进程。 总结hrtimer 及clockevent/source的引入对于kernel的实时性的提高有很大贡献,也将clock的处理从体系结构的代码中抽象了出来,增强了代码 的可重用性。并且对于posix的time/timer标准有了强有力的支持,提高了用户空间的应用程序的时间处理精度及灵活性。如果应用层在使用这些 syscall时有任何不解之处,直接看看hrtimer的code,对于处理问题,理解OS的行为都有很大帮助。 |
联系客服