linux2 6定時器的時間輪演算法分析

2021-06-07 06:27:24 字數 4022 閱讀 3129

常用的定時器實現演算法有兩種:紅黑樹和時間輪(timing wheel)。

在linux2.6的**中,kernel/timer.c檔案實現了乙個通用定時器機制,使用的是時間輪演算法。

每乙個cpu都有乙個struct tvec_base結構,代表這個cpu使用的時間輪。

struct tvec_base

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類似。

增加timer分兩步,先是呼叫init_timer初始化乙個struct timer_list結構,然後呼叫add_timer把timer掛到5個wheel中包含的某乙個定時器佇列中。

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

static void __init_timer(struct timer_list *timer)

add_timer是__mod_timer函式的乙個封裝,__mod_timer最終會呼叫internal_add_timer。

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

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

else if (idx < 1 << (tvr_bits + tvn_bits))

// 類推,加入tv3

else if (idx < 1 << (tvr_bits + 2 * tvn_bits))

// 類推,加入tv4

else if (idx < 1 << (tvr_bits + 3 * tvn_bits))

// 如果idx < 0,說明定時器已經超時,應該馬上被排程,把它加入base->timer_jiffies,                                         // 即base的當前jiffies對應的佇列。這裡要注意的是,這個函式在spin_lock_irq被調           // 用之後呼叫,所以不會與定時器排程函式產生競態。

else if ((signed long) idx < 0)

// 加入tv5佇列。

else

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

timer的排程在軟中斷中完成,在init_timers函式中呼叫

open_softirq(timer_softirq, run_timer_softirq)

註冊定時器對應的軟中斷處理函式。

static void run_timer_softirq(struct softirq_action *h)

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                                  // 解鎖

在時間輪演算法中,隨著時間推移,定時器到期的時間越來越近,時間輪也隨著時間不停轉動。當最快的輪轉到某乙個觸發點,就將次快輪的某一佇列搬到最快輪。次塊輪轉到某一觸發點,把下一級時間輪的某一佇列再往上搬到次快輪。依此類推。所以排隊中的定時器就是這樣一級一級往上,直到在最快輪中被處理。

在這裡,這些搬移由上節描述的__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)

Linux的動態定時器 時間輪

定時器 有時也稱為動態定時器或核心定時器 是管理核心時間的基礎。定時器是一種軟體功能,即允許在將來的某個時刻,函式在給定的時間間隔用完時被呼叫。注意的是定時器並不會週期執行,它在超時後就自行銷毀,這也是定時器被稱為動態定時器的乙個原因。動態定時器不斷地建立和銷毀,而且它的執行次數也不受限制。定時器在...

linux定時器時間輪演算法詳解

linux高併發程式設計 紅黑樹實現定時器 時間輪實現定時器 linux多執行緒環境下海量定時任務的定時器設計 linux定時器分為低精度定時器和高精度定時器兩種型別,核心對其均有實現。本文討論的是我們在應用程式開發中比較常見的低精度定時器。作為常用的基礎元件,定時器常用的幾種實現方法包括 基於排序...

基於時間輪的定時器

目錄 是乙個單層時間輪,當指標走到某一格上,就獲取那一格上掛的任務將其執行。當時如果時間跨度大的時候,格仔數明顯不夠,那麼就可以做成多級時間輪。其實就是當低層的時間輪走了一圈,將它高一層的時間輪走一格,並且將掛在高層時間輪上的任務分配下來。檔案 include timewheel.h include...