linux现代时间轮算法,linux2.6定时器的时间轮算法分析

1.Overview

常用的定时器实现算法有两种:红黑树和时间轮(timing wheel)。

在Linux2.6的代码中,kernel/timer.c文件实现了一个通用定时器机制,使用的是时间轮算法。

每一个CPU都有一个struct tvec_base结构,代表这个CPU使用的时间轮。

struct tvec_base

{

spinlock_t lock;//同步锁

struct timer_list * running_timer;//当前正在运行的定时器

unsigned long timer_jiffies;//当前运行到的jiffies

struct tvec_root tv1;

struct tvec tv2;

struct tvec tv3;

struct tvec tv4;

struct tvec tv5;

}

struct tvec_root与struct tvec都是数组,数组中的每一项都指定一个链表。struct tvec_root定义的数组大小是256(2的8次方);struct tvec_root定义的数组大小是64(2的6次方)。所以,tv1~6定义的数组总大小是2的(8 + 4*6 = 32)次方,正好对应32位处理器中jiffies的定义(unsigned long)。

因为使用的是wheel算法,tv1~5就代表5个wheel。

tv1是转速最快的wheel,所有在256个jiffies内到期的定时器都会挂在tv1的某个链表头中。

tv2是转速第二快的wheel,里面挂的定时器超时jiffies在2^8 ~ 2^(8+6)之间。

tv3是转速第三快的wheel,超时jiffies在2^(8+6) ~ 2^(8+2*6)之间。

tv4、tv5类似。

2.增加timer

增加timer分两步,先是调用init_timer初始化一个struct timer_list结构,然后调用add_timer把timer挂到5个wheel中包含的某一个定时器队列中。

init_timer函数初始化timer_list中的一些域。

static void __init_timer(struct timer_list *timer)

{

timer->entry.next = NULL;

timer->base = __raw_get_cpu_var(tvec_bases);

#ifdef CONFIG_TIMER_STATS

timer->start_site = NULL;

timer->start_pid = -1;

memset(timer->start_comm, 0, TASK_COMM_LEN);

#endif

}

add_timer是__mod_timer函数的一个封装,__mod_timer最终会调用internal_add_timer。

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;// idx为定时器距离超时还有多少jiffies

struct list_head *vec;

//如果idx少于TVR_SIZE(256),把定时器加入tv1

if (idx < TVR_SIZE) {

int i = expires & TVR_MASK;

vec = base->tv1.vec + i;

}

//如果idx少于2^(8+6)次方,加入tv2

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

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

vec = base->tv2.vec + i;

}

//类推,加入tv3

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

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

vec = base->tv3.vec + i;

}

//类推,加入tv4

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

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

vec = base->tv4.vec + i;

}

//如果idx < 0,说明定时器已经超时,应该马上被调度,把它加入base->timer_jiffies,//即base的当前jiffies对应的队列。这里要注意的是,这个函数在spin_lock_irq被调//用之后调用,所以不会与定时器调度函数产生竞态。

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);

}

//加入tv5队列。

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;

}

trace_timer_set(timer);

/*

* Timers are FIFO:

*/

list_add_tail(&timer->entry, vec);

}

3.timer的调度

timer的调度在软中断中完成,在init_timers函数中调用

open_softirq(TIMER_SOFTIRQ, run_timer_softirq)

注册定时器对应的软中断处理函数。

static void run_timer_softirq(struct softirq_action *h)

{

struct tvec_base *base = __get_cpu_var(tvec_bases);

hrtimer_run_pending();              // 高精度时钟的处理

//如果jiffies超过了base->timer_jiffies,那么调用时钟处理函数

if (time_after_eq(jiffies, base->timer_jiffies))

__run_timers(base);

}

static inline void __run_timers(struct tvec_base *base)

spin_lock_irq加锁,关中断,保证与定时器添加函数之间不会产生竞态

while (time_after_eq(jiffies, base->timer_jiffies))

// index代表这个循环需要处理的定时器在数组tv1中的下标

int index = base->timer_jiffies & TVR_MASK;

//定时器的重新排列,下文详细讨论

if (!index &&

(!cascade(base, &base->tv2, INDEX(0))) &&

(!cascade(base, &base->tv3, INDEX(1))) &&

!cascade(base, &base->tv4, INDEX(2)))

cascade(base, &base->tv5, INDEX(3));

++base->timer_jiffies;

//取出这次循环需要处理的定时器队列

list_replace_init(base->tv1.vec + index, &work_list);

//处理队列中的每一个定时器。注意在调用定时器中的timer->function之前,会先//调用spin_unlock_irq,调用完之后再重新用spin_lock_irq加锁。这是为了提高CPU的效率。另外,在处理某个定时器时,//会调用set_running_timer把当前正在运行的定时器标记到base->running_timer//中。

……

//函数退出,标记现在base中没有正在运行的定时器

set_running_timer(base, NULL);

spin_unlock_irq//解锁

4.定时器的重新排列

时间轮算法中,随着时间推移,定时器到期的时间越来越近,时间轮也随着时间不停转动。当最快的轮转到某一个触发点,就将次快轮的某一队列搬到最快轮。次块

轮转到某一触发点,把下一级时间轮的某一队列再往上搬到次快轮。依此类推。所以排队中的定时器就是这样一级一级往上,直到在最快轮中被处理。

在这里,这些搬移由上节描述的__run_timers函数的下列代码体现:

// index代表这个循环需要处理的定时器在数组tv1中的下标

int index = base->timer_jiffies & TVR_MASK;

//定时器的重新排列

if (!index &&

(!cascade(base, &base->tv2, INDEX(0))) &&

(!cascade(base, &base->tv3, INDEX(1))) &&

!cascade(base, &base->tv4, INDEX(2)))

cascade(base, &base->tv5, INDEX(3));

当jiffies的低8位为0时,tv1到了触发点,调用cascade函数把tv2的某一格定时器搬到tv1。tv2的cascade函数返回0时,代表tv2也到了触发点,于是又触发下一级时间轮tv3。依此类推。

这里INDEX的定义为:

#define INDEX(N) ((base->timer_jiffies >> (TVR_BITS+ (N) * TVN_BITS)) & TVN_MASK)

可以看出这个宏返回的是时间轮中某一格的索引:

INDEX(0)返回tv2的索引。

INDEX(1)返回tv3的索引。

INDEX(2)返回tv4的索引。

INDEX(3)返回tv5的索引。

static int cascade(struct tvec_base *base, struct tvec *tv, int index)

//将tv数组中以index为下标的定时器队列取出,准备搬移

list_replace_init(tv->vec + index, &tv_list);

//根据超时时间,将队列中的定时器重新排队。定时器往上一级时间轮排队的过程就//在这里发生。

list_for_each_entry_safe(timer, tmp, &tv_list, entry) {

BUG_ON(tbase_get_base(timer->base) != base);

internal_add_timer(base, timer);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值