linux时间子系统(五)

3 定时器

  Linux中定时器分两种,一种是timeout类型,另一种是timer类型。timeout类型的定时器通常用于检测各种错误条件,例如用于检测网卡发收数据包是否会超时,IO设备的读写是否会超时的定时器等。使用timeout类型的定时器往往不关心超时处理,因此超时精确与否,并不重要。这类定时器是基于time wheel机制实现的。timer类型的定时器与timeout类型的定时器正好相反,使用timer类型的定时器往往要求在精确的时钟条件下完成特定的事件。timer类型的定时器是基于红黑树实现的。

  Linux需要进行时钟管理,离不开底层的硬件支持。在早期的Linux内核中,通过8253芯片提供的PIT来提供时钟,但是PIT的频率很低,只能提供最多1ms的时钟精度,由于PIT触发的中断速度太慢,会导致很大的时延,对于像音视频这类对时间精度要求很高的应用并不足够,会极大的影响用户体验。随着硬件平台的不断发展,陆续出现了TSC,HPET,ACPI PM Timer,CPU Local APIC Timer等精度更高的时钟,内核为了可以使用更高精度的定时器,开发出了基于rbtree的hrtimer子系统。

3.1 time wheel 

  在Linux 2.6.16之前,内核一致使用一种被称为time wheel的机制来管理定时器。这就是内核一直采用的基于HZ的定时器机制。

  为了避免竞争,内核为每个cpu定义了一个tvec_base结构指针,用来保存定时器。

3.1.1 tvec_base结构

struct tvec_base {

        spinlock_t lock;

        struct timer_list *running_timer;

        wait_queue_head_t wait_for_running_timer;

        unsigned long timer_jiffies;

        unsigned long next_timer;

        struct tvec_root tv1;

        struct tvec tv2;      

        struct tvec tv3;

        struct tvec tv4;

        struct tvec tv5;

} ____cacheline_aligned;  

  running_timer,该字段指向当前cpu正在处理的定时器所对应的timer_list结构。

  timer_jiffies,该字段表示当前CPU定时器所经历过的jiffies数。大多数情况下,该数和jiffies计数值相等,如果cpu的idle状态连续持续了多个jiffies时,当退出idle状态时,jiffies计数值就会大于该字段,在接下来的tick中断后,定时器系统会让该字段等于jiffies值。

  next_timer,该字段指向该CPU下一个即将到期的定时器。

  tv1 -- tv5,这5个字段用于对定时器进行分组。实际上,tv1-tv5都是一个链表数组,其中tv1的数组大小为TVR_SIZE,tv2-tv5的大小为TVN_SIZE,根据CONFIG_BASE_SMALL配置项不同,他们有不同的大小。默认情况下,CONFIG_BASE_SMALL未使能,TVR_SIZE=256,TVN_SIZE=64。若系统内存不足,则可以使能CONFIG_BASE_SMALL,此时TVR_SIZE=64,TVN_SIZE=16。

3.1.2 time wheel机制

  time wheel机制的工作原理类似于水表的工作原理。假定没有使能CONFIG_BASE_SMALL,此时tv1-tv5这5个链表数组的大小分别是256,64,64,64,64。由于tv1中的定时器会被最先处理而tv5中的定时器会被最后处理,我们可以认为tv1-tv5分别占据一个32位数的不同比特位,其中tv1占据最低的8位,tv2占据紧接着的6为,tv5占据最后的6位。

  当注册一个定时器时,我们可以获取定时器到期时间和所属cpu的tvec_base结构中timer_jiffies字段的差值,记为idx。之后比较idx与1<<8-1,1<<14-1, 1<<20-1, 1<<26-1, 1<<32-1的值,确定定时器应该存放的链表数组。假设idx=4,则存放到tv1数组中。假定idx=500,则存放到tv2数组中。

  当确定了链表数组后,接着要确定把该定时器放入数组的哪一个链表中。如果idx的值小于256,则要被放入tv1中,所以可以简单的使用定时器到期时间timer_list.expires的低8位作为数组下标索引,放入tv1相应的链表中即可。如果idx的值在256-16383之间,则需要把定时器放入tv2链表数组中,所以可以使用定时器到期时间timer_list.expires的8-14位作为数组的下标索引即可。tv3-tv5同理,即放入(timer_list.expires << (TVN_SIZE + n*TVR_SIZE)) & (n?TVR_MASK:TVN_MASK)作为下标索引的相应链表即可。

    static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)

{

        unsigned long expires = timer->expires;

        unsigned long idx = expires - base->timer_jiffies;

        struct list_head *vec;

 

        if (idx < TVR_SIZE) {

                int i = expires & TVR_MASK;

                vec = base->tv1.vec + i;

        } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {

                int i = (expires >> TVR_BITS) & TVN_MASK;

                vec = base->tv2.vec + i;

        } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {

                int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;

                vec = base->tv3.vec + i;

        } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {

                int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;

                vec = base->tv4.vec + i;

        } else if ((signed long) idx < 0) {

                /*

                 * Can happen if you add a timer with expires == jiffies,

                 * or you set a timer to go off in the past

                 */

                vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);

        } else {

                int i;

                /* If the timeout is larger than 0xffffffff on 64-bit

                 * architectures then we use the maximum timeout:

                 */

                if (idx > 0xffffffffUL) {

                        idx = 0xffffffffUL;

                        expires = idx + base->timer_jiffies;

                }

                i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;

                vec = base->tv5.vec + i;

        }

        /*

         * Timers are FIFO:

         */

        list_add_tail(&timer->entry, vec);

}

   定时器的添加,就是首先计算定时器与所属cpu的tvec_base->timer_jiffies的差值,再根据idx的值和定时器的到期时间将定时器放入tv1-tv5链表数组的某一链表中。

猜你喜欢

转载自blog.csdn.net/kylinos_123/article/details/80321714