上一篇文章,我介紹了傳統(tǒng)的低分辨率定時(shí)器的實(shí)現(xiàn)原理。而隨著內(nèi)核的不斷演進(jìn),大牛們已經(jīng)對(duì)這種低分辨率定時(shí)器的精度不再滿足,而且,硬件也在不斷地發(fā)展,系統(tǒng)中的定時(shí)器硬件的精度也越來(lái)越高,這也給高分辨率定時(shí)器的出現(xiàn)創(chuàng)造了條件。內(nèi)核從2.6.16開始加入了高精度定時(shí)器架構(gòu)。在實(shí)現(xiàn)方式上,內(nèi)核的高分辨率定時(shí)器的實(shí)現(xiàn)代碼幾乎沒(méi)有借用低分辨率定時(shí)器的數(shù)據(jù)結(jié)構(gòu)和代碼,內(nèi)核文檔給出的解釋主要有以下幾點(diǎn):
低分辨率定時(shí)器的代碼和jiffies的關(guān)系太過(guò)緊密,并且默認(rèn)按32位進(jìn)行設(shè)計(jì),并且它的代碼已經(jīng)經(jīng)過(guò)長(zhǎng)時(shí)間的優(yōu)化,目前的使用也是沒(méi)有任何錯(cuò)誤,如果硬要基于它來(lái)實(shí)現(xiàn)高分辨率定時(shí)器,勢(shì)必會(huì)打破原有的時(shí)間輪概念,并且會(huì)引入一大堆#if--#else判斷;
雖然大部分時(shí)間里,時(shí)間輪可以實(shí)現(xiàn)O(1)時(shí)間復(fù)雜度,但是當(dāng)有進(jìn)位發(fā)生時(shí),不可預(yù)測(cè)的O(N)定時(shí)器級(jí)聯(lián)遷移時(shí)間,這對(duì)于低分辨率定時(shí)器來(lái)說(shuō)問(wèn)題不大,可是它大大地影響了定時(shí)器的精度;
低分辨率定時(shí)器幾乎是為“超時(shí)”而設(shè)計(jì)的,并為此對(duì)它進(jìn)行了大量的優(yōu)化,對(duì)于這些以“超時(shí)”未目的而使用定時(shí)器,它們大多數(shù)期望在超時(shí)到來(lái)之前獲得正確的結(jié)果,然后刪除定時(shí)器,精確時(shí)間并不是它們主要的目的,例如網(wǎng)絡(luò)通信、設(shè)備IO等等。
為此,內(nèi)核為高精度定時(shí)器重新設(shè)計(jì)了一套軟件架構(gòu),它可以為我們提供納秒級(jí)的定時(shí)精度,以滿足對(duì)精確時(shí)間有迫切需求的應(yīng)用程序或內(nèi)核驅(qū)動(dòng),例如多媒體應(yīng)用,音頻設(shè)備的驅(qū)動(dòng)程序等等。以下的討論用hrtimer(high resolution timer)表示高精度定時(shí)器。
?
1. ?如何組織hrtimer?
我們知道,低分辨率定時(shí)器使用5個(gè)鏈表數(shù)組來(lái)組織timer_list結(jié)構(gòu),形成了著名的時(shí)間輪概念,對(duì)于高分辨率定時(shí)器,我們期望組織它們的數(shù)據(jù)結(jié)構(gòu)至少具備以下條件:
穩(wěn)定而且快速的查找能力;
快速地插入和刪除定時(shí)器的能力;
排序功能;
內(nèi)核的開發(fā)者考察了多種數(shù)據(jù)結(jié)構(gòu),例如基數(shù)樹、哈希表等等,最終他們選擇了紅黑樹(rbtree)來(lái)組織hrtimer,紅黑樹已經(jīng)以庫(kù)的形式存在于內(nèi)核中,并被成功地使用在內(nèi)存管理子系統(tǒng)和文件系統(tǒng)中,隨著系統(tǒng)的運(yùn)行,hrtimer不停地被創(chuàng)建和銷毀,新的hrtimer按順序被插入到紅黑樹中,樹的最左邊的節(jié)點(diǎn)就是最快到期的定時(shí)器,內(nèi)核用一個(gè)hrtimer結(jié)構(gòu)來(lái)表示一個(gè)高精度定時(shí)器:
[cpp]?view plain?copy
struct?hrtimer?{??
struct?timerqueue_node??????node;??
ktime_t?????????????_softexpires;??
enum?hrtimer_restart????????(*function)(struct?hrtimer?*);??
struct?hrtimer_clock_base???*base;??
unsigned?long???????????state;??
......??
};??
定時(shí)器的到期時(shí)間用ktime_t來(lái)表示,_softexpires字段記錄了時(shí)間,定時(shí)器一旦到期,function字段指定的回調(diào)函數(shù)會(huì)被調(diào)用,該函數(shù)的返回值為一個(gè)枚舉值,它決定了該hrtimer是否需要被重新激活:
[cpp]?view plain?copy
enum?hrtimer_restart?{??
HRTIMER_NORESTART,??/*?Timer?is?not?restarted?*/??
HRTIMER_RESTART,????/*?Timer?must?be?restarted?*/??
};??
state字段用于表示hrtimer當(dāng)前的狀態(tài),有幾下幾種位組合:
[cpp]?view plain?copy
#define?HRTIMER_STATE_INACTIVE??0x00??//?定時(shí)器未激活??
#define?HRTIMER_STATE_ENQUEUED??0x01??//?定時(shí)器已經(jīng)被排入紅黑樹中??
#define?HRTIMER_STATE_CALLBACK??0x02??//?定時(shí)器的回調(diào)函數(shù)正在被調(diào)用??
#define?HRTIMER_STATE_MIGRATE???0x04??//?定時(shí)器正在CPU之間做遷移??
hrtimer的到期時(shí)間可以基于以下幾種時(shí)間基準(zhǔn)系統(tǒng):
[cpp]?view plain?copy
enum??hrtimer_base_type?{??
HRTIMER_BASE_MONOTONIC,??//?單調(diào)遞增的monotonic時(shí)間,不包含休眠時(shí)間??
HRTIMER_BASE_REALTIME,???//?平常使用的墻上真實(shí)時(shí)間??
HRTIMER_BASE_BOOTTIME,???//?單調(diào)遞增的boottime,包含休眠時(shí)間??
HRTIMER_MAX_CLOCK_BASES,?//?用于后續(xù)數(shù)組的定義??
};??
和低分辨率定時(shí)器一樣,處于效率和上鎖的考慮,每個(gè)cpu單獨(dú)管理屬于自己的hrtimer,為此,專門定義了一個(gè)結(jié)構(gòu)hrtimer_cpu_base:
[cpp]?view plain?copy
struct?hrtimer_cpu_base?{??
......??
struct?hrtimer_clock_base???clock_base[HRTIMER_MAX_CLOCK_BASES];??
};??
其中,clock_base數(shù)組為每種時(shí)間基準(zhǔn)系統(tǒng)都定義了一個(gè)hrtimer_clock_base結(jié)構(gòu),它的定義如下:
[cpp]?view plain?copy
struct?hrtimer_clock_base?{??
struct?hrtimer_cpu_base?*cpu_base;??//?指向所屬cpu的hrtimer_cpu_base結(jié)構(gòu)??
......??
struct?timerqueue_head??active;?????//?紅黑樹,包含了所有使用該時(shí)間基準(zhǔn)系統(tǒng)的hrtimer??
ktime_t?????????resolution;?//?時(shí)間基準(zhǔn)系統(tǒng)的分辨率??
ktime_t?????????(*get_time)(void);?//?獲取該基準(zhǔn)系統(tǒng)的時(shí)間函數(shù)??
ktime_t?????????softirq_time;//?當(dāng)用jiffies??
ktime_t?????????offset;??????//???
};??
active字段是一個(gè)timerqueue_head結(jié)構(gòu),它實(shí)際上是對(duì)rbtree的進(jìn)一步封裝:
[cpp]?view plain?copy
struct?timerqueue_node?{??
struct?rb_node?node;??//?紅黑樹的節(jié)點(diǎn)??
ktime_t?expires;??????//?該節(jié)點(diǎn)代表隊(duì)hrtimer的到期時(shí)間,與hrtimer結(jié)構(gòu)中的_softexpires稍有不同??
};??
struct?timerqueue_head?{??
struct?rb_root?head;??????????//?紅黑樹的根節(jié)點(diǎn)??
struct?timerqueue_node?*next;?//?該紅黑樹中最早到期的節(jié)點(diǎn),也就是最左下的節(jié)點(diǎn)??
};??
timerqueue_head結(jié)構(gòu)在紅黑樹的基礎(chǔ)上,增加了一個(gè)next字段,用于保存樹中最先到期的定時(shí)器節(jié)點(diǎn),實(shí)際上就是樹的最左下方的節(jié)點(diǎn),有了next字段,當(dāng)?shù)狡谑录絹?lái)時(shí),系統(tǒng)不必遍歷整個(gè)紅黑樹,只要取出next字段對(duì)應(yīng)的節(jié)點(diǎn)進(jìn)行處理即可。timerqueue_node用于表示一個(gè)hrtimer節(jié)點(diǎn),它在標(biāo)準(zhǔn)紅黑樹節(jié)點(diǎn)rb_node的基礎(chǔ)上增加了expires字段,該字段和hrtimer中的_softexpires字段一起,設(shè)定了hrtimer的到期時(shí)間的一個(gè)范圍,hrtimer可以在hrtimer._softexpires至timerqueue_node.expires之間的任何時(shí)刻到期,我們也稱timerqueue_node.expires為硬過(guò)期時(shí)間(hard),意思很明顯:到了此時(shí)刻,定時(shí)器一定會(huì)到期,有了這個(gè)范圍可以選擇,定時(shí)器系統(tǒng)可以讓范圍接近的多個(gè)定時(shí)器在同一時(shí)刻同時(shí)到期,這種設(shè)計(jì)可以降低進(jìn)程頻繁地被hrtimer進(jìn)行喚醒。經(jīng)過(guò)以上的討論,我們可以得出以下的圖示,它表明了每個(gè)cpu上的hrtimer是如何被組織在一起的:
圖 1.1 ?每個(gè)cpu的hrtimer組織結(jié)構(gòu)
總結(jié)一下:
每個(gè)cpu有一個(gè)hrtimer_cpu_base結(jié)構(gòu);
hrtimer_cpu_base結(jié)構(gòu)管理著3種不同的時(shí)間基準(zhǔn)系統(tǒng)的hrtimer,分別是:實(shí)時(shí)時(shí)間,啟動(dòng)時(shí)間和單調(diào)時(shí)間;
每種時(shí)間基準(zhǔn)系統(tǒng)通過(guò)它的active字段(timerqueue_head結(jié)構(gòu)指針),指向它們各自的紅黑樹;
紅黑樹上,按到期時(shí)間進(jìn)行排序,最先到期的hrtimer位于最左下的節(jié)點(diǎn),并被記錄在active.next字段中;
3中時(shí)間基準(zhǔn)的最先到期時(shí)間可能不同,所以,它們之中最先到期的時(shí)間被記錄在hrtimer_cpu_base的expires_next字段中。
2. ?hrtimer如何運(yùn)轉(zhuǎn)
hrtimer的實(shí)現(xiàn)需要一定的硬件基礎(chǔ),它的實(shí)現(xiàn)依賴于我們前幾章介紹的timekeeper和clock_event_device,如果你對(duì)timekeeper和clock_event_device不了解請(qǐng)參考以下文章:Linux時(shí)間子系統(tǒng)之三:時(shí)間的維護(hù)者:timekeeper,Linux時(shí)間子系統(tǒng)之四:定時(shí)器的引擎:clock_event_device。hrtimer系統(tǒng)需要通過(guò)timekeeper獲取當(dāng)前的時(shí)間,計(jì)算與到期時(shí)間的差值,并根據(jù)該差值,設(shè)定該cpu的tick_device(clock_event_device)的下一次的到期時(shí)間,時(shí)間一到,在clock_event_device的事件回調(diào)函數(shù)中處理到期的hrtimer。現(xiàn)在你或許有疑問(wèn):前面在介紹clock_event_device時(shí),我們知道,每個(gè)cpu有自己的tick_device,通常用于周期性地產(chǎn)生進(jìn)程調(diào)度和時(shí)間統(tǒng)計(jì)的tick事件,這里又說(shuō)要用tick_device調(diào)度hrtimer系統(tǒng),通常cpu只有一個(gè)tick_device,那他們?nèi)绾螀f(xié)調(diào)工作?這個(gè)問(wèn)題也一度困擾著我,如果再加上NO_HZ配置帶來(lái)tickless特性,你可能會(huì)更暈。這里我們先把這個(gè)疑問(wèn)放下,我將在后面的章節(jié)中來(lái)討論這個(gè)問(wèn)題,現(xiàn)在我們只要先知道,一旦開啟了hrtimer,tick_device所關(guān)聯(lián)的clock_event_device的事件回調(diào)函數(shù)會(huì)被修改為:hrtimer_interrupt,并且會(huì)被設(shè)置成工作于CLOCK_EVT_MODE_ONESHOT單觸發(fā)模式。
2.1 ?添加一個(gè)hrtimer
要添加一個(gè)hrtimer,系統(tǒng)提供了一些api供我們使用,首先我們需要定義一個(gè)hrtimer結(jié)構(gòu)的實(shí)例,然后用hrtimer_init函數(shù)對(duì)它進(jìn)行初始化,它的原型如下:
[cpp]?view plain?copy
void?hrtimer_init(struct?hrtimer?*timer,?clockid_t?which_clock,??
enum?hrtimer_mode?mode);??
which_clock可以是CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_BOOTTIME中的一種,mode則可以是相對(duì)時(shí)間HRTIMER_MODE_REL,也可以是絕對(duì)時(shí)間HRTIMER_MODE_ABS。設(shè)定回調(diào)函數(shù):
[cpp]?view plain?copy
timer.function?=?hr_callback;??
如果定時(shí)器無(wú)需指定一個(gè)到期范圍,可以在設(shè)定回調(diào)函數(shù)后直接使用hrtimer_start激活該定時(shí)器:
[cpp]?view plain?copy
int?hrtimer_start(struct?hrtimer?*timer,?ktime_t?tim,??
const?enum?hrtimer_mode?mode);??
如果需要指定到期范圍,則可以使用hrtimer_start_range_ns激活定時(shí)器:
[cpp]?view plain?copy
hrtimer_start_range_ns(struct?hrtimer?*timer,?ktime_t?tim,??
unsigned?long?range_ns,?const?enum?hrtimer_mode?mode);??
要取消一個(gè)hrtimer,使用hrtimer_cancel:
[cpp]?view plain?copy
int?hrtimer_cancel(struct?hrtimer?*timer);??
以下兩個(gè)函數(shù)用于推后定時(shí)器的到期時(shí)間:
[cpp]?view plain?copy
extern?u64??
hrtimer_forward(struct?hrtimer?*timer,?ktime_t?now,?ktime_t?interval);??
/*?Forward?a?hrtimer?so?it?expires?after?the?hrtimer's?current?now?*/??
static?inline?u64?hrtimer_forward_now(struct?hrtimer?*timer,??
ktime_t?interval)??
{??
return?hrtimer_forward(timer,?timer->base->get_time(),?interval);??
}??
以下幾個(gè)函數(shù)用于獲取定時(shí)器的當(dāng)前狀態(tài):
[cpp]?view plain?copy
static?inline?int?hrtimer_active(const?struct?hrtimer?*timer)??
{??
return?timer->state?!=?HRTIMER_STATE_INACTIVE;??
}??
static?inline?int?hrtimer_is_queued(struct?hrtimer?*timer)??
{??
return?timer->state?&?HRTIMER_STATE_ENQUEUED;??
}??
static?inline?int?hrtimer_callback_running(struct?hrtimer?*timer)??
{??
return?timer->state?&?HRTIMER_STATE_CALLBACK;??
}??
hrtimer_init最終會(huì)進(jìn)入__hrtimer_init函數(shù),該函數(shù)的主要目的是初始化hrtimer的base字段,同時(shí)初始化作為紅黑樹的節(jié)點(diǎn)的node字段:
[cpp]?view plain?copy
static?void?__hrtimer_init(struct?hrtimer?*timer,?clockid_t?clock_id,??
enum?hrtimer_mode?mode)??
{??
struct?hrtimer_cpu_base?*cpu_base;??
int?base;??
memset(timer,?0,?sizeof(struct?hrtimer));??
cpu_base?=?&__raw_get_cpu_var(hrtimer_bases);??
if?(clock_id?==?CLOCK_REALTIME?&&?mode?!=?HRTIMER_MODE_ABS)??
clock_id?=?CLOCK_MONOTONIC;??
base?=?hrtimer_clockid_to_base(clock_id);??
timer->base?=?&cpu_base->clock_base[base];??
timerqueue_init(&timer->node);??
......??
}??
hrtimer_start和hrtimer_start_range_ns最終會(huì)把實(shí)際的工作交由__hrtimer_start_range_ns來(lái)完成:
[cpp]?view plain?copy
int?__hrtimer_start_range_ns(struct?hrtimer?*timer,?ktime_t?tim,??
unsigned?long?delta_ns,?const?enum?hrtimer_mode?mode,??
int?wakeup)??
{??
......??????????
/*?取得hrtimer_clock_base指針?*/??
base?=?lock_hrtimer_base(timer,?&flags);???
/*?如果已經(jīng)在紅黑樹中,先移除它:?*/??
ret?=?remove_hrtimer(timer,?base);?......??
/*?如果是相對(duì)時(shí)間,則需要加上當(dāng)前時(shí)間,因?yàn)閮?nèi)部是使用絕對(duì)時(shí)間?*/??
if?(mode?&?HRTIMER_MODE_REL)?{??
tim?=?ktime_add_safe(tim,?new_base->get_time());??
......??
}???
/*?設(shè)置到期的時(shí)間范圍?*/??
hrtimer_set_expires_range_ns(timer,?tim,?delta_ns);??
......???
/*?把hrtime按到期時(shí)間排序,加入到對(duì)應(yīng)時(shí)間基準(zhǔn)系統(tǒng)的紅黑樹中?*/??
/*?如果該定時(shí)器的是最早到期的,將會(huì)返回true?*/??
leftmost?=?enqueue_hrtimer(timer,?new_base);??
/*??
*?Only?allow?reprogramming?if?the?new?base?is?on?this?CPU.??
*?(it?might?still?be?on?another?CPU?if?the?timer?was?pending)??
*??
*?XXX?send_remote_softirq()???
*?定時(shí)器比之前的到期時(shí)間要早,所以需要重新對(duì)tick_device進(jìn)行編程,重新設(shè)定的的到期時(shí)間?
*/??
if?(leftmost?&&?new_base->cpu_base?==?&__get_cpu_var(hrtimer_bases))??
hrtimer_enqueue_reprogram(timer,?new_base,?wakeup);??
unlock_hrtimer_base(timer,?&flags);??
return?ret;??
}??
2.2 ?hrtimer的到期處理
高精度定時(shí)器系統(tǒng)有3個(gè)入口可以對(duì)到期定時(shí)器進(jìn)行處理,它們分別是:
沒(méi)有切換到高精度模式時(shí),在每個(gè)jiffie的tick事件中斷中進(jìn)行查詢和處理;
在HRTIMER_SOFTIRQ軟中斷中進(jìn)行查詢和處理;
切換到高精度模式后,在每個(gè)clock_event_device的到期事件中斷中進(jìn)行查詢和處理;
低精度模式??因?yàn)橄到y(tǒng)并不是一開始就會(huì)支持高精度模式,而是在系統(tǒng)啟動(dòng)后的某個(gè)階段,等待所有的條件都滿足后,才會(huì)切換到高精度模式,當(dāng)系統(tǒng)還沒(méi)有切換到高精度模式時(shí),所有的高精度定時(shí)器運(yùn)行在低精度模式下,在每個(gè)jiffie的tick事件中斷中進(jìn)行到期定時(shí)器的查詢和處理,顯然這時(shí)候的精度和低分辨率定時(shí)器是一樣的(HZ級(jí)別)。低精度模式下,每個(gè)tick事件中斷中,hrtimer_run_queues函數(shù)會(huì)被調(diào)用,由它完成定時(shí)器的到期處理。hrtimer_run_queues首先判斷目前高精度模式是否已經(jīng)啟用,如果已經(jīng)切換到了高精度模式,什么也不做,直接返回:
[cpp]?view plain?copy
void?hrtimer_run_queues(void)??
{??
if?(hrtimer_hres_active())??
return;??
如果hrtimer_hres_active返回false,說(shuō)明目前處于低精度模式下,則繼續(xù)處理,它用一個(gè)for循環(huán)遍歷各個(gè)時(shí)間基準(zhǔn)系統(tǒng),查詢每個(gè)hrtimer_clock_base對(duì)應(yīng)紅黑樹的左下節(jié)點(diǎn),判斷它的時(shí)間是否到期,如果到期,通過(guò)__run_hrtimer函數(shù),對(duì)到期定時(shí)器進(jìn)行處理,包括:調(diào)用定時(shí)器的回調(diào)函數(shù)、從紅黑樹中移除該定時(shí)器、根據(jù)回調(diào)函數(shù)的返回值決定是否重新啟動(dòng)該定時(shí)器等等:
[cpp]?view plain?copy
for?(index?=?0;?index?
base?=?&cpu_base->clock_base[index];??
if?(!timerqueue_getnext(&base->active))??
continue;??
if?(gettime)?{??
hrtimer_get_softirq_time(cpu_base);??
gettime?=?0;??
}??
raw_spin_lock(&cpu_base->lock);??
while?((node?=?timerqueue_getnext(&base->active)))?{??
struct?hrtimer?*timer;??
timer?=?container_of(node,?struct?hrtimer,?node);??
if?(base->softirq_time.tv64?<=??
hrtimer_get_expires_tv64(timer))??
break;??
__run_hrtimer(timer,?&base->softirq_time);??
}??
raw_spin_unlock(&cpu_base->lock);??
}??
上面的timerqueue_getnext函數(shù)返回紅黑樹中的左下節(jié)點(diǎn),之所以可以在while循環(huán)中使用該函數(shù),是因?yàn)開_run_hrtimer會(huì)在移除舊的左下節(jié)點(diǎn)時(shí),新的左下節(jié)點(diǎn)會(huì)被更新到base->active->next字段中,使得循環(huán)可以繼續(xù)執(zhí)行,直到?jīng)]有新的到期定時(shí)器為止。
高精度模式? 切換到高精度模式后,原來(lái)給cpu提供tick事件的tick_device(clock_event_device)會(huì)被高精度定時(shí)器系統(tǒng)接管,它的中斷事件回調(diào)函數(shù)被設(shè)置為hrtimer_interrupt,紅黑樹中最左下的節(jié)點(diǎn)的定時(shí)器的到期時(shí)間被編程到該clock_event_device中,這樣每次clock_event_device的中斷意味著至少有一個(gè)高精度定時(shí)器到期。另外,當(dāng)timekeeper系統(tǒng)中的時(shí)間需要修正,或者clock_event_device的到期事件時(shí)間被重新編程時(shí),系統(tǒng)會(huì)發(fā)出HRTIMER_SOFTIRQ軟中斷,軟中斷的處理函數(shù)run_hrtimer_softirq最終也會(huì)調(diào)用hrtimer_interrupt函數(shù)對(duì)到期定時(shí)器進(jìn)行處理,所以在這里我們只要討論hrtimer_interrupt函數(shù)的實(shí)現(xiàn)即可。
hrtimer_interrupt函數(shù)的前半部分和低精度模式下的hrtimer_run_queues函數(shù)完成相同的事情:它用一個(gè)for循環(huán)遍歷各個(gè)時(shí)間基準(zhǔn)系統(tǒng),查詢每個(gè)hrtimer_clock_base對(duì)應(yīng)紅黑樹的左下節(jié)點(diǎn),判斷它的時(shí)間是否到期,如果到期,通過(guò)__run_hrtimer函數(shù),對(duì)到期定時(shí)器進(jìn)行處理,所以我們只討論后半部分,在處理完所有到期定時(shí)器后,下一個(gè)到期定時(shí)器的到期時(shí)間保存在變量expires_next中,接下來(lái)的工作就是把這個(gè)到期時(shí)間編程到tick_device中:
[cpp]?view plain?copy
void?hrtimer_interrupt(struct?clock_event_device?*dev)??
{??
......??
for?(i?=?0;?i?
......??
while?((node?=?timerqueue_getnext(&base->active)))?{??
......??
if?(basenow.tv64?
ktime_t?expires;??
expires?=?ktime_sub(hrtimer_get_expires(timer),??
base->offset);??
if?(expires.tv64?
expires_next?=?expires;??
break;??
}??
__run_hrtimer(timer,?&basenow);??
}??
}??
/*?
*?Store?the?new?expiry?value?so?the?migration?code?can?verify?
*?against?it.?
*/??
cpu_base->expires_next?=?expires_next;??
raw_spin_unlock(&cpu_base->lock);??
/*?Reprogramming?necessary???*/??
if?(expires_next.tv64?==?KTIME_MAX?||??
!tick_program_event(expires_next,?0))?{??
cpu_base->hang_detected?=?0;??
return;??
}??
如果這時(shí)的tick_program_event返回了非0值,表示過(guò)期時(shí)間已經(jīng)在當(dāng)前時(shí)間的前面,這通常由以下原因造成:
系統(tǒng)正在被調(diào)試跟蹤,導(dǎo)致時(shí)間在走,程序不走;
定時(shí)器的回調(diào)函數(shù)花了太長(zhǎng)的時(shí)間;
系統(tǒng)運(yùn)行在虛擬機(jī)中,而虛擬機(jī)被調(diào)度導(dǎo)致停止運(yùn)行;
為了避免這些情況的發(fā)生,接下來(lái)系統(tǒng)提供3次機(jī)會(huì),重新執(zhí)行前面的循環(huán),處理到期的定時(shí)器:
[cpp]?view plain?copy
raw_spin_lock(&cpu_base->lock);??
now?=?hrtimer_update_base(cpu_base);??
cpu_base->nr_retries++;??
if?(++retries?3)??
goto?retry;??
如果3次循環(huán)后還無(wú)法完成到期處理,系統(tǒng)不再循環(huán),轉(zhuǎn)為計(jì)算本次總循環(huán)的時(shí)間,然后把tick_device的到期時(shí)間強(qiáng)制設(shè)置為當(dāng)前時(shí)間加上本次的總循環(huán)時(shí)間,不過(guò)推后的時(shí)間被限制在100ms以內(nèi):
[cpp]?view plain?copy
delta?=?ktime_sub(now,?entry_time);??
if?(delta.tv64?>?cpu_base->max_hang_time.tv64)??
cpu_base->max_hang_time?=?delta;??
/*?
*?Limit?it?to?a?sensible?value?as?we?enforce?a?longer?
*?delay.?Give?the?CPU?at?least?100ms?to?catch?up.?
*/??
if?(delta.tv64?>?100?*?NSEC_PER_MSEC)??
expires_next?=?ktime_add_ns(now,?100?*?NSEC_PER_MSEC);??
else??
expires_next?=?ktime_add(now,?delta);??
tick_program_event(expires_next,?1);??
printk_once(KERN_WARNING?"hrtimer:?interrupt?took?%llu?ns ",??
ktime_to_ns(delta));??
}??
3. ?切換到高精度模式
上面提到,盡管內(nèi)核配置成支持高精度定時(shí)器,但并不是一開始就工作于高精度模式,系統(tǒng)在啟動(dòng)的開始階段,還是按照傳統(tǒng)的模式在運(yùn)行:tick_device按HZ頻率定期地產(chǎn)生tick事件,這時(shí)的hrtimer工作在低分辨率模式,到期事件在每個(gè)tick事件中斷中由hrtimer_run_queues函數(shù)處理,同時(shí),在低分辨率定時(shí)器(時(shí)間輪)的軟件中斷TIMER_SOFTIRQ中,hrtimer_run_pending會(huì)被調(diào)用,系統(tǒng)在這個(gè)函數(shù)中判斷系統(tǒng)的條件是否滿足切換到高精度模式,如果條件滿足,則會(huì)切換至高分辨率模式,另外提一下,NO_HZ模式也是在該函數(shù)中判斷并切換。
[cpp]?view plain?copy
void?hrtimer_run_pending(void)??
{??
if?(hrtimer_hres_active())??
return;??
......??
if?(tick_check_oneshot_change(!hrtimer_is_hres_enabled()))??
hrtimer_switch_to_hres();??
}??
因?yàn)椴还芟到y(tǒng)是否工作于高精度模式,每個(gè)TIMER_SOFTIRQ期間,該函數(shù)都會(huì)被調(diào)用,所以函數(shù)一開始先用hrtimer_hres_active判斷目前高精度模式是否已經(jīng)激活,如果已經(jīng)激活,則說(shuō)明之前的調(diào)用中已經(jīng)切換了工作模式,不必再次切換,直接返回。hrtimer_hres_active很簡(jiǎn)單:
[cpp]?view plain?copy
DEFINE_PER_CPU(struct?hrtimer_cpu_base,?hrtimer_bases)?=?{??
......??
}??
static?inline?int?hrtimer_hres_active(void)??
{??
return?__this_cpu_read(hrtimer_bases.hres_active);??
}??
hrtimer_run_pending函數(shù)接著通過(guò)tick_check_oneshot_change判斷系統(tǒng)是否可以切換到高精度模式,
[cpp]?view plain?copy
int?tick_check_oneshot_change(int?allow_nohz)??
{??
struct?tick_sched?*ts?=?&__get_cpu_var(tick_cpu_sched);??
if?(!test_and_clear_bit(0,?&ts->check_clocks))??
return?0;??
if?(ts->nohz_mode?!=?NOHZ_MODE_INACTIVE)??
return?0;??
if?(!timekeeping_valid_for_hres()?||?!tick_is_oneshot_available())??
return?0;??
if?(!allow_nohz)??
return?1;??
tick_nohz_switch_to_nohz();??
return?0;??
}??
函數(shù)的一開始先判斷check_clock標(biāo)志的第0位是否被置位,如果沒(méi)有置位,說(shuō)明系統(tǒng)中沒(méi)有注冊(cè)符合要求的時(shí)鐘事件設(shè)備,函數(shù)直接返回,check_clock標(biāo)志由clocksource和clock_event_device系統(tǒng)的notify系統(tǒng)置位,當(dāng)系統(tǒng)中有更高精度的clocksource被注冊(cè)和選擇后,或者有更精確的支持CLOCK_EVT_MODE_ONESHOT模式的clock_event_device被注冊(cè)時(shí),通過(guò)它們的notify函數(shù),check_clock標(biāo)志的第0為會(huì)置位。
如果tick_sched結(jié)構(gòu)中的nohz_mode字段不是NOHZ_MODE_INACTIVE,表明系統(tǒng)已經(jīng)切換到其它模式,直接返回。nohz_mode的取值有3種:
NOHZ_MODE_INACTIVE ? ?// 未啟用NO_HZ模式
NOHZ_MODE_LOWRES ? ?//?啟用NO_HZ模式,hrtimer工作于低精度模式下
NOHZ_MODE_HIGHRES ? // 啟用NO_HZ模式,hrtimer工作于高精度模式下
接下來(lái)的timerkeeping_valid_for_hres判斷timekeeper系統(tǒng)是否支持高精度模式,tick_is_oneshot_available判斷tick_device是否支持CLOCK_EVT_MODE_ONESHOT模式。如果都滿足要求,則繼續(xù)往下判斷。allow_nohz是函數(shù)的參數(shù),為true表明可以切換到NOHZ_MODE_LOWRES 模式,函數(shù)將進(jìn)入tick_nohz_switch_to_nohz,切換至NOHZ_MODE_LOWRES 模式,這里我們傳入的allow_nohz是表達(dá)式:
(!hrtimer_is_hres_enabled())
所以當(dāng)系統(tǒng)不允許高精度模式時(shí),將會(huì)在tick_check_oneshot_change函數(shù)內(nèi),通過(guò)tick_nohz_switch_to_nohz切換至NOHZ_MODE_LOWRES 模式,如果系統(tǒng)允許高精度模式,傳入的allow_nohz參數(shù)為false,tick_check_oneshot_change函數(shù)返回1,回到上面的hrtimer_run_pending函數(shù),hrtimer_switch_to_hres函數(shù)將會(huì)被調(diào)用,已完成切換到NOHZ_MODE_HIGHRES高精度模式。好啦,真正的切換函數(shù)找到了,我們看一看它如何切換:
首先,它通過(guò)hrtimer_cpu_base中的hres_active字段判斷該cpu是否已經(jīng)切換至高精度模式,如果是則直接返回:
[cpp]?view plain?copy
static?int?hrtimer_switch_to_hres(void)??
{??
int?i,?cpu?=?smp_processor_id();??
struct?hrtimer_cpu_base?*base?=?&per_cpu(hrtimer_bases,?cpu);??
unsigned?long?flags;??
if?(base->hres_active)??
return?1;??
接著,通過(guò)tick_init_highres函數(shù)接管tick_device關(guān)聯(lián)的clock_event_device:
[cpp]?view plain?copy
local_irq_save(flags);??
if?(tick_init_highres())?{??
local_irq_restore(flags);??
printk(KERN_WARNING?"Could?not?switch?to?high?resolution?"??
"mode?on?CPU?%d ",?cpu);??
return?0;??
}??
tick_init_highres函數(shù)把tick_device切換到CLOCK_EVT_FEAT_ONESHOT模式,同時(shí)把clock_event_device的回調(diào)handler設(shè)置為hrtimer_interrupt,這樣設(shè)置以后,tick_device的中斷回調(diào)將由hrtimer_interrupt接管,hrtimer_interrupt在上面已經(jīng)討論過(guò),它將完成高精度定時(shí)器的調(diào)度和到期處理。
接著,設(shè)置hres_active標(biāo)志,以表明高精度模式已經(jīng)切換,然后把3個(gè)時(shí)間基準(zhǔn)系統(tǒng)的resolution字段設(shè)為KTIME_HIGH_RES:
[cpp]?view plain?copy
base->hres_active?=?1;??
for?(i?=?0;?i?
base->clock_base[i].resolution?=?KTIME_HIGH_RES;??
最后,因?yàn)閠ick_device被高精度定時(shí)器接管,它將不會(huì)再提供原有的tick事件機(jī)制,所以需要由高精度定時(shí)器系統(tǒng)模擬一個(gè)tick事件設(shè)備,繼續(xù)為系統(tǒng)提供tick事件能力,這個(gè)工作由tick_setup_sched_timer函數(shù)完成。因?yàn)閯倓偼瓿汕袚Q,tick_device的到期時(shí)間并沒(méi)有被正確地設(shè)置為下一個(gè)到期定時(shí)器的時(shí)間,這里使用retrigger_next_event函數(shù),傳入?yún)?shù)NULL,使得tick_device立刻產(chǎn)生到期中斷,hrtimer_interrupt被調(diào)用一次,然后下一個(gè)到期的定時(shí)器的時(shí)間會(huì)編程到tick_device中,從而完成了到高精度模式的切換:
[cpp]?view plain?copy
tick_setup_sched_timer();??
/*?"Retrigger"?the?interrupt?to?get?things?going?*/??
retrigger_next_event(NULL);??
local_irq_restore(flags);??
return?1;??
整個(gè)切換過(guò)程可以用下圖表示:
圖3.1 ?低精度模式切換至高精度模式
4. ?模擬tick事件
根據(jù)上一節(jié)的討論,當(dāng)系統(tǒng)切換到高精度模式后,tick_device被高精度定時(shí)器系統(tǒng)接管,不再定期地產(chǎn)生tick事件,我們知道,到目前的版本為止(V3.4),內(nèi)核還沒(méi)有徹底廢除jiffies機(jī)制,系統(tǒng)還是依賴定期到來(lái)的tick事件,供進(jìn)程調(diào)度系統(tǒng)和時(shí)間更新等操作,大量存在的低精度定時(shí)器也仍然依賴于jiffies的計(jì)數(shù),所以,盡管tick_device被接管,高精度定時(shí)器系統(tǒng)還是要想辦法繼續(xù)提供定期的tick事件。為了達(dá)到這一目的,內(nèi)核使用了一個(gè)取巧的辦法:既然高精度模式已經(jīng)啟用,可以定義一個(gè)hrtimer,把它的到期時(shí)間設(shè)定為一個(gè)jiffy的時(shí)間,當(dāng)這個(gè)hrtimer到期時(shí),在這個(gè)hrtimer的到期回調(diào)函數(shù)中,進(jìn)行和原來(lái)的tick_device同樣的操作,然后把該hrtimer的到期時(shí)間順延一個(gè)jiffy周期,如此反復(fù)循環(huán),完美地模擬了原有tick_device的功能。下面我們看看具體點(diǎn)代碼是如何實(shí)現(xiàn)的。
在kernel/time/tick-sched.c中,內(nèi)核定義了一個(gè)per_cpu全局變量:tick_cpu_sched,從而為每個(gè)cpu提供了一個(gè)tick_sched結(jié)構(gòu),?該結(jié)構(gòu)主要用于管理NO_HZ配置下的tickless處理,因?yàn)槟Mtick事件與tickless有很強(qiáng)的相關(guān)性,所以高精度定時(shí)器系統(tǒng)也利用了該結(jié)構(gòu)的以下字段,用于完成模擬tick事件的操作:
[cpp]?view plain?copy
struct?tick_sched?{??
struct?hrtimer??????????sched_timer;??
unsigned?long???????????check_clocks;??
enum?tick_nohz_mode?????nohz_mode;??
......??
};??
sched_timer就是要用于模擬tick事件的hrtimer,check_clock上面幾節(jié)已經(jīng)討論過(guò),用于notify系統(tǒng)通知hrtimer系統(tǒng)需要檢查是否切換到高精度模式,nohz_mode則用于表示當(dāng)前的工作模式。
?
上一節(jié)提到,用于切換至高精度模式的函數(shù)是hrtimer_switch_to_hres,在它的最后,調(diào)用了函數(shù)tick_setup_sched_timer,該函數(shù)的作用就是設(shè)置一個(gè)用于模擬tick事件的hrtimer:
[cpp]?view plain?copy
void?tick_setup_sched_timer(void)??
{??
struct?tick_sched?*ts?=?&__get_cpu_var(tick_cpu_sched);??
ktime_t?now?=?ktime_get();??
/*?
*?Emulate?tick?processing?via?per-CPU?hrtimers:?
*/??
hrtimer_init(&ts->sched_timer,?CLOCK_MONOTONIC,?HRTIMER_MODE_ABS);??
ts->sched_timer.function?=?tick_sched_timer;??
/*?Get?the?next?period?(per?cpu)?*/??
hrtimer_set_expires(&ts->sched_timer,?tick_init_jiffy_update());??
for?(;;)?{??
hrtimer_forward(&ts->sched_timer,?now,?tick_period);??
hrtimer_start_expires(&ts->sched_timer,??
HRTIMER_MODE_ABS_PINNED);??
/*?Check,?if?the?timer?was?already?in?the?past?*/??
if?(hrtimer_active(&ts->sched_timer))??
break;??
now?=?ktime_get();??
}??
#ifdef?CONFIG_NO_HZ??
if?(tick_nohz_enabled)??
ts->nohz_mode?=?NOHZ_MODE_HIGHRES;??
#endif??
}??
該函數(shù)首先初始化該cpu所屬的tick_sched結(jié)構(gòu)中sched_timer字段,把該hrtimer的回調(diào)函數(shù)設(shè)置為tick_sched_timer,然后把它的到期時(shí)間設(shè)定為下一個(gè)jiffy時(shí)刻,返回前把工作模式設(shè)置為NOHZ_MODE_HIGHRES,表明是利用高精度模式實(shí)現(xiàn)NO_HZ。
接著我們關(guān)注一下hrtimer的回調(diào)函數(shù)tick_sched_timer,我們知道,系統(tǒng)中的jiffies計(jì)數(shù),時(shí)間更新等是全局操作,在smp系統(tǒng)中,只有一個(gè)cpu負(fù)責(zé)該工作,所以在tick_sched_timer的一開始,先判斷當(dāng)前cpu是否負(fù)責(zé)更新jiffies和時(shí)間,如果是,則執(zhí)行更新操作:
[cpp]?view plain?copy
static?enum?hrtimer_restart?tick_sched_timer(struct?hrtimer?*timer)??
{??
......??
#ifdef?CONFIG_NO_HZ??
if?(unlikely(tick_do_timer_cpu?==?TICK_DO_TIMER_NONE))??
tick_do_timer_cpu?=?cpu;??
#endif??
/*?Check,?if?the?jiffies?need?an?update?*/??
if?(tick_do_timer_cpu?==?cpu)??
tick_do_update_jiffies64(now);??
然后,利用regs指針確保當(dāng)前是在中斷上下文中,然后調(diào)用update_process_timer:
[cpp]?view plain?copy
if?(regs)?{??
......??
update_process_times(user_mode(regs));??
......??
}??
最后,把hrtimer的到期時(shí)間推進(jìn)一個(gè)tick周期,返回HRTIMER_RESTART表明該hrtimer需要再次啟動(dòng),以便產(chǎn)生下一個(gè)tick事件。
[cpp]?view plain?copy
hrtimer_forward(timer,?now,?tick_period);??
return?HRTIMER_RESTART;??
}??
關(guān)于update_process_times,如果你你感興趣,回看一下本系列關(guān)于clock_event_device的那一章:Linux時(shí)間子系統(tǒng)之四:定時(shí)器的引擎:clock_event_device中的第5小節(jié),對(duì)比一下模擬tick事件的hrtimer的回調(diào)函數(shù)tick_sched_timer和切換前tick_device的回調(diào)函數(shù)tick_handle_periodic,它們是如此地相像,實(shí)際上,它們幾乎完成了一樣的工作。
?
評(píng)論
查看更多