五、好大一棵树:高精度定时器的实现
在低精度下,级联方式管理的定时
器非常地高效,每一次时钟中断,只需要判断第一组下的表项(index = base->timer_jiffies & 255)是否有定时器即可。但到了高精度下,情况就不一样了,老革命遇到了新问题,高精度定时器的定时精度为纳级,而低精度下为毫秒级,
差了两个数量级,每秒钟就有1000000000个纳秒。如果还按级联方式来做的话,系统得有多少个表项啊,需要多少内存啊。而且原来的级联方式是根据jiffies和32位系统来做的,因此,最终系统采用了红黑树来管理定时器。更详细的描述可以参考源码树的hrtimes.txt文件。
由于系统jiffies为毫秒级,而高精度定时器为纳秒级,那么使用jiffies来表示已经不合适了,因此,系统引入了ktime_t来表示时间,ktime_t实际为一个64bit的值,如果系统为32位,则为两个32bit的联合体。并引入了一堆接口做时间换算。
下面看看高精度定时器的触发处理hrtimer_interrupt:
1,从红黑树取定时器节点(启动定时器时已经按照定时时间插入树中),判断是否超时,
2,处理定时器的回调,
3,所有超时定时器已经处理完毕,设置时钟芯片下一个定时时间。
高精度定时器可以基于两种时钟,一个是单调时钟,系统启动为0开始递增;一个是实际时间。
单调时钟是不会变化的,而实际时间是有可能变化的,比如调用了settimeofday接口,这时候定时器怎么处理?
系统计算当前时间与设置时间的差值,并从红黑树获取第一个定时器节点(最先超时),超时时间加上差值(可能为正也可能为负),以此结果重新设置时钟芯片的触发时间。时钟芯片触发时,需要对每一个定时器进行时间补偿。
六、tick时钟
没有启动动态时钟的情况下,系统内部有一个以固定周期触发的定时器,它就是tick时钟。
七、动态时钟(tickless)
第六节tick时钟介绍了系统的周期时钟是以固定的时间间隔触发,超时处理函数进行相应的处理。但实际上,在不是很繁忙的系统上(最直观的方法就是CPU占有率),大多数情况下,系统都是空闲的,由于该tick值较短(依赖于配置,常见的为10毫秒或者4毫秒),超时后发现没有事情需要处理,又接着运行IDLE任务或者CPU进入节能状态,这个超时处理是很浪费。考虑一个人,没有事情做的时候想打个盹,但又怕错过,不得不眼睛刚眯上,又得睁开问有没有事情做,还能睡好吗?那能不能在只有事情处理的时候才叫醒他呢?
在内核就实现了类似的机制,这就是动态时钟。所谓动态时钟,就是当系统空闲时,它不是采用周期性的tick超时,而是判断系统的下一个超时处理时间,如果超时处理时间大于1个tick值(小于则没有启动动态时钟的意义),则对时钟设备进行编程,让时钟设备以单触发的方式,在指定的超时时间触发。简单说,动态时钟就是在空闲态下,系统节拍不是固定周期触发的,是通过计算得出的,这就是动态的含义,可以认为是tick周期时钟的扩展。
当指定的超时时间到达之后或者被其他外部中断提前唤醒后,系统为下一个时钟信号编程,参见tick_nohz_restart_sched_tick->tick_nohz_restart
系统空闲时的相关处理函数参见cpu_idle->tick_nohz_stop_sched_tick。
相关数据结构参见struct tick_sched(该结构同时也是高精度定时器下的tick时钟的实现)。
特别的是,动态时钟机制是否支持是在编译时指定的,使能编译宏CONFIG_NO_HZ才会支持。