http://blog.csdn.net/droidphone/article/details/8074892 上一篇文章,我介紹了傳統(tǒng)的低分辨率定時器的實現(xiàn)原理。而隨著內(nèi)核的不斷演進,大牛們已經(jīng)對這種低分辨率定時器的精度不再滿足,而且,硬件也在不斷地發(fā)展,系統(tǒng)中的定時器硬件的精度也越來越高,這也給高分辨率定時器的出現(xiàn)創(chuàng)造了條件。內(nèi)核從2.6.16開始加入了高精度定時器架構(gòu)。在實現(xiàn)方式上,內(nèi)核的高分辨率定時器的實現(xiàn)代碼幾乎沒有借用低分辨率定時器的數(shù)據(jù)結(jié)構(gòu)和代碼,內(nèi)核文檔給出的解釋主要有以下幾點:
低分辨率定時器的代碼和jiffies的關(guān)系太過緊密,并且默認按32位進行設(shè)計,并且它的代碼已經(jīng)經(jīng)過長時間的優(yōu)化,目前的使用也是沒有任何錯誤,如果硬要基于它來實現(xiàn)高分辨率定時器,勢必會打破原有的時間輪概念,并且會引入一大堆#if–#else判斷;雖然大部分時間里,時間輪可以實現(xiàn)O(1)時間復(fù)雜度,但是當(dāng)有進位發(fā)生時,不可預(yù)測的O(N)定時器級聯(lián)遷移時間,這對于低分辨率定時器來說問題不大,可是它大大地影響了定時器的精度;低分辨率定時器幾乎是為“超時”而設(shè)計的,并為此對它進行了大量的優(yōu)化,對于這些以“超時”未目的而使用定時器,它們大多數(shù)期望在超時到來之前獲得正確的結(jié)果,然后刪除定時器,精確時間并不是它們主要的目的,例如網(wǎng)絡(luò)通信、設(shè)備IO等等。為此,內(nèi)核為高精度定時器重新設(shè)計了一套軟件架構(gòu),它可以為我們提供納秒級的定時精度,以滿足對精確時間有迫切需求的應(yīng)用程序或內(nèi)核驅(qū)動,例如多媒體應(yīng)用,音頻設(shè)備的驅(qū)動程序等等。以下的討論用hrtimer(high resolution timer)表示高精度定時器。
我們知道,低分辨率定時器使用5個鏈表數(shù)組來組織timer_list結(jié)構(gòu),形成了著名的時間輪概念,對于高分辨率定時器,我們期望組織它們的數(shù)據(jù)結(jié)構(gòu)至少具備以下條件:
穩(wěn)定而且快速的查找能力;快速地插入和刪除定時器的能力;排序功能;內(nèi)核的開發(fā)者考察了多種數(shù)據(jù)結(jié)構(gòu),例如基數(shù)樹、哈希表等等,最終他們選擇了紅黑樹(rbtree)來組織hrtimer,紅黑樹已經(jīng)以庫的形式存在于內(nèi)核中,并被成功地使用在內(nèi)存管理子系統(tǒng)和文件系統(tǒng)中,隨著系統(tǒng)的運行,hrtimer不停地被創(chuàng)建和銷毀,新的hrtimer按順序被插入到紅黑樹中,樹的最左邊的節(jié)點就是最快到期的定時器,內(nèi)核用一個hrtimer結(jié)構(gòu)來表示一個高精度定時器:
struct hrtimer { struct timerqueue_node node; ktime_t _softexpires; enum hrtimer_restart (*function)(struct hrtimer *); struct hrtimer_clock_base *base; unsigned long state; ...... };定時器的到期時間用ktime_t來表示,_softexpires字段記錄了時間,定時器一旦到期,function字段指定的回調(diào)函數(shù)會被調(diào)用,該函數(shù)的返回值為一個枚舉值,它決定了該hrtimer是否需要被重新激活:
enum hrtimer_restart { HRTIMER_NORESTART, /* Timer is not restarted */ HRTIMER_RESTART, /* Timer must be restarted */ };state字段用于表示hrtimer當(dāng)前的狀態(tài),有幾下幾種位組合:
#define HRTIMER_STATE_INACTIVE 0x00 // 定時器未激活 #define HRTIMER_STATE_ENQUEUED 0x01 // 定時器已經(jīng)被排入紅黑樹中 #define HRTIMER_STATE_CALLBACK 0x02 // 定時器的回調(diào)函數(shù)正在被調(diào)用 #define HRTIMER_STATE_MIGRATE 0x04 // 定時器正在CPU之間做遷移hrtimer的到期時間可以基于以下幾種時間基準(zhǔn)系統(tǒng):
enum hrtimer_base_type { HRTIMER_BASE_MONOTONIC, // 單調(diào)遞增的monotonic時間,不包含休眠時間 HRTIMER_BASE_REALTIME, // 平常使用的墻上真實時間 HRTIMER_BASE_BOOTTIME, // 單調(diào)遞增的boottime,包含休眠時間 HRTIMER_MAX_CLOCK_BASES, // 用于后續(xù)數(shù)組的定義 };和低分辨率定時器一樣,處于效率和上鎖的考慮,每個cpu單獨管理屬于自己的hrtimer,為此,專門定義了一個結(jié)構(gòu)hrtimer_cpu_base:
struct hrtimer_cpu_base { ...... struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES]; };其中,clock_base數(shù)組為每種時間基準(zhǔn)系統(tǒng)都定義了一個hrtimer_clock_base結(jié)構(gòu),它的定義如下:
struct hrtimer_clock_base { struct hrtimer_cpu_base *cpu_base; // 指向所屬cpu的hrtimer_cpu_base結(jié)構(gòu) ...... struct timerqueue_head active; // 紅黑樹,包含了所有使用該時間基準(zhǔn)系統(tǒng)的hrtimer ktime_t resolution; // 時間基準(zhǔn)系統(tǒng)的分辨率 ktime_t (*get_time)(void); // 獲取該基準(zhǔn)系統(tǒng)的時間函數(shù) ktime_t softirq_time;// 當(dāng)用jiffies ktime_t offset; // };active字段是一個timerqueue_head結(jié)構(gòu),它實際上是對rbtree的進一步封裝:
struct timerqueue_node { struct rb_node node; // 紅黑樹的節(jié)點 ktime_t expires; // 該節(jié)點代表隊hrtimer的到期時間,與hrtimer結(jié)構(gòu)中的_softexpires稍有不同 }; struct timerqueue_head { struct rb_root head; // 紅黑樹的根節(jié)點 struct timerqueue_node *next; // 該紅黑樹中最早到期的節(jié)點,也就是最左下的節(jié)點 };timerqueue_head結(jié)構(gòu)在紅黑樹的基礎(chǔ)上,增加了一個next字段,用于保存樹中最先到期的定時器節(jié)點,實際上就是樹的最左下方的節(jié)點,有了next字段,當(dāng)?shù)狡谑录絹頃r,系統(tǒng)不必遍歷整個紅黑樹,只要取出next字段對應(yīng)的節(jié)點進行處理即可。timerqueue_node用于表示一個hrtimer節(jié)點,它在標(biāo)準(zhǔn)紅黑樹節(jié)點rb_node的基礎(chǔ)上增加了expires字段,該字段和hrtimer中的_softexpires字段一起,設(shè)定了hrtimer的到期時間的一個范圍,hrtimer可以在hrtimer._softexpires至timerqueue_node.expires之間的任何時刻到期,我們也稱timerqueue_node.expires為硬過期時間(hard),意思很明顯:到了此時刻,定時器一定會到期,有了這個范圍可以選擇,定時器系統(tǒng)可以讓范圍接近的多個定時器在同一時刻同時到期,這種設(shè)計可以降低進程頻繁地被hrtimer進行喚醒。經(jīng)過以上的討論,我們可以得出以下的圖示,它表明了每個cpu上的hrtimer是如何被組織在一起的:
圖 1.1 每個cpu的hrtimer組織結(jié)構(gòu)
總結(jié)一下:
每個cpu有一個hrtimer_cpu_base結(jié)構(gòu);hrtimer_cpu_base結(jié)構(gòu)管理著3種不同的時間基準(zhǔn)系統(tǒng)的hrtimer,分別是:實時時間,啟動時間和單調(diào)時間;每種時間基準(zhǔn)系統(tǒng)通過它的active字段(timerqueue_head結(jié)構(gòu)指針),指向它們各自的紅黑樹;紅黑樹上,按到期時間進行排序,最先到期的hrtimer位于最左下的節(jié)點,并被記錄在active.next字段中;3中時間基準(zhǔn)的最先到期時間可能不同,所以,它們之中最先到期的時間被記錄在hrtimer_cpu_base的expires_next字段中。hrtimer的實現(xiàn)需要一定的硬件基礎(chǔ),它的實現(xiàn)依賴于我們前幾章介紹的timekeeper和clock_event_device,如果你對timekeeper和clock_event_device不了解請參考以下文章:linux時間子系統(tǒng)之三:時間的維護者:timekeeper,Linux時間子系統(tǒng)之四:定時器的引擎:clock_event_device。hrtimer系統(tǒng)需要通過timekeeper獲取當(dāng)前的時間,計算與到期時間的差值,并根據(jù)該差值,設(shè)定該cpu的tick_device(clock_event_device)的下一次的到期時間,時間一到,在clock_event_device的事件回調(diào)函數(shù)中處理到期的hrtimer。現(xiàn)在你或許有疑問:前面在介紹clock_event_device時,我們知道,每個cpu有自己的tick_device,通常用于周期性地產(chǎn)生進程調(diào)度和時間統(tǒng)計的tick事件,這里又說要用tick_device調(diào)度hrtimer系統(tǒng),通常cpu只有一個tick_device,那他們?nèi)绾螀f(xié)調(diào)工作?這個問題也一度困擾著我,如果再加上NO_HZ配置帶來tickless特性,你可能會更暈。這里我們先把這個疑問放下,我將在后面的章節(jié)中來討論這個問題,現(xiàn)在我們只要先知道,一旦開啟了hrtimer,tick_device所關(guān)聯(lián)的clock_event_device的事件回調(diào)函數(shù)會被修改為:hrtimer_interrupt,并且會被設(shè)置成工作于CLOCK_EVT_MODE_ONESHOT單觸發(fā)模式。
要添加一個hrtimer,系統(tǒng)提供了一些api供我們使用,首先我們需要定義一個hrtimer結(jié)構(gòu)的實例,然后用hrtimer_init函數(shù)對它進行初始化,它的原型如下:
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode);which_clock可以是CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_BOOTTIME中的一種,mode則可以是相對時間HRTIMER_MODE_REL,也可以是絕對時間HRTIMER_MODE_ABS。設(shè)定回調(diào)函數(shù):
timer.function = hr_callback;如果定時器無需指定一個到期范圍,可以在設(shè)定回調(diào)函數(shù)后直接使用hrtimer_start激活該定時器:
int hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode);如果需要指定到期范圍,則可以使用hrtimer_start_range_ns激活定時器:
hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim, unsigned long range_ns, const enum hrtimer_mode mode);要取消一個hrtimer,使用hrtimer_cancel:
int hrtimer_cancel(struct hrtimer *timer);以下兩個函數(shù)用于推后定時器的到期時間:
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); }以下幾個函數(shù)用于獲取定時器的當(dāng)前狀態(tài):
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最終會進入__hrtimer_init函數(shù),該函數(shù)的主要目的是初始化hrtimer的base字段,同時初始化作為紅黑樹的節(jié)點的node字段:
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最終會把實際的工作交由__hrtimer_start_range_ns來完成:
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); ...... /* 如果是相對時間,則需要加上當(dāng)前時間,因為內(nèi)部是使用絕對時間 */ if (mode & HRTIMER_MODE_REL) { tim = ktime_add_safe(tim, new_base->get_time()); ...... } /* 設(shè)置到期的時間范圍 */ hrtimer_set_expires_range_ns(timer, tim, delta_ns); ...... /* 把hrtime按到期時間排序,加入到對應(yīng)時間基準(zhǔn)系統(tǒng)的紅黑樹中 */ /* 如果該定時器的是最早到期的,將會返回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() ? * 定時器比之前的到期時間要早,所以需要重新對tick_device進行編程,重新設(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; }高精度定時器系統(tǒng)有3個入口可以對到期定時器進行處理,它們分別是:
沒有切換到高精度模式時,在每個jiffie的tick事件中斷中進行查詢和處理;在HRTIMER_SOFTIRQ軟中斷中進行查詢和處理;切換到高精度模式后,在每個clock_event_device的到期事件中斷中進行查詢和處理;低精度模式 因為系統(tǒng)并不是一開始就會支持高精度模式,而是在系統(tǒng)啟動后的某個階段,等待所有的條件都滿足后,才會切換到高精度模式,當(dāng)系統(tǒng)還沒有切換到高精度模式時,所有的高精度定時器運行在低精度模式下,在每個jiffie的tick事件中斷中進行到期定時器的查詢和處理,顯然這時候的精度和低分辨率定時器是一樣的(HZ級別)。低精度模式下,每個tick事件中斷中,hrtimer_run_queues函數(shù)會被調(diào)用,由它完成定時器的到期處理。hrtimer_run_queues首先判斷目前高精度模式是否已經(jīng)啟用,如果已經(jīng)切換到了高精度模式,什么也不做,直接返回:
void hrtimer_run_queues(void) { if (hrtimer_hres_active()) return;如果hrtimer_hres_active返回false,說明目前處于低精度模式下,則繼續(xù)處理,它用一個for循環(huán)遍歷各個時間基準(zhǔn)系統(tǒng),查詢每個hrtimer_clock_base對應(yīng)紅黑樹的左下節(jié)點,判斷它的時間是否到期,如果到期,通過__run_hrtimer函數(shù),對到期定時器進行處理,包括:調(diào)用定時器的回調(diào)函數(shù)、從紅黑樹中移除該定時器、根據(jù)回調(diào)函數(shù)的返回值決定是否重新啟動該定時器等等:
for (index = 0; index < HRTIMER_MAX_CLOCK_BASES; 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é)點,之所以可以在while循環(huán)中使用該函數(shù),是因為__run_hrtimer會在移除舊的左下節(jié)點時,新的左下節(jié)點會被更新到base->active->next字段中,使得循環(huán)可以繼續(xù)執(zhí)行,直到?jīng)]有新的到期定時器為止。
高精度模式 切換到高精度模式后,原來給cpu提供tick事件的tick_device(clock_event_device)會被高精度定時器系統(tǒng)接管,它的中斷事件回調(diào)函數(shù)被設(shè)置為hrtimer_interrupt,這個hrtimer_interrupt在tick_init_highres()函數(shù)中被設(shè)置在clock_event_device的回調(diào)函數(shù)。比如高通的會設(shè)置clock_event_device設(shè)備的的中斷,并設(shè)置回調(diào)函數(shù)為msm_timer_interrupt()。msm_timer_interrupt()函數(shù)會調(diào)用這個hrtimer_interrupt() !!!。 當(dāng)從hrtimer_switch_to_hres()開始切換到高精度模式之后,調(diào)度用的time tick也隨之切到hrtimer上。hrtimer_switch_to_hres()->tick_setup_sched_timer()會把tick_sched_timer掛到hrtimer->function。然后用hrtimer_forward(&ts->sched_timer, now, tick_period)設(shè)置下一次tick到期的hrtimer中斷。到期之后調(diào)用的中斷回調(diào)函數(shù)tick_sched_timer()也是會重新設(shè)置下次tick的時間。
紅黑樹中最左下的節(jié)點的定時器的到期時間被編程到該clock_event_device中,這樣每次clock_event_device的中斷意味著至少有一個高精度定時器到期。另外,當(dāng)timekeeper系統(tǒng)中的時間需要修正,或者clock_event_device的到期事件時間被重新編程時,系統(tǒng)會發(fā)出HRTIMER_SOFTIRQ軟中斷,軟中斷的處理函數(shù)run_hrtimer_softirq最終也會調(diào)用hrtimer_interrupt函數(shù)對到期定時器進行處理,所以在這里我們只要討論hrtimer_interrupt函數(shù)的實現(xiàn)即可。 hrtimer_interrupt函數(shù)的前半部分和低精度模式下的hrtimer_run_queues函數(shù)完成相同的事情:它用一個for循環(huán)遍歷各個時間基準(zhǔn)系統(tǒng),查詢每個hrtimer_clock_base對應(yīng)紅黑樹的左下節(jié)點,判斷它的時間是否到期,如果到期,通過__run_hrtimer函數(shù),對到期定時器進行處理,所以我們只討論后半部分,在處理完所有到期定時器后,下一個到期定時器的到期時間保存在變量expires_next中,接下來的工作就是把這個到期時間編程到tick_device中:
void hrtimer_interrupt(struct clock_event_device *dev) { ...... for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) { ...... while ((node = timerqueue_getnext(&base->active))) { ...... if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer)) { ktime_t expires; expires = ktime_sub(hrtimer_get_expires(timer), base->offset); if (expires.tv64 < expires_next.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; }如果這時的tick_program_event返回了非0值,表示過期時間已經(jīng)在當(dāng)前時間的前面,這通常由以下原因造成:
系統(tǒng)正在被調(diào)試跟蹤,導(dǎo)致時間在走,程序不走;定時器的回調(diào)函數(shù)花了太長的時間;系統(tǒng)運行在虛擬機中,而虛擬機被調(diào)度導(dǎo)致停止運行;為了避免這些情況的發(fā)生,接下來系統(tǒng)提供3次機會,重新執(zhí)行前面的循環(huán),處理到期的定時器:
raw_spin_lock(&cpu_base->lock); now = hrtimer_update_base(cpu_base); cpu_base->nr_retries++; if (++retries < 3) goto retry;如果3次循環(huán)后還無法完成到期處理,系統(tǒng)不再循環(huán),轉(zhuǎn)為計算本次總循環(huán)的時間,然后把tick_device的到期時間強制設(shè)置為當(dāng)前時間加上本次的總循環(huán)時間,不過推后的時間被限制在100ms以內(nèi):
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/n", ktime_to_ns(delta)); }上面提到,盡管內(nèi)核配置成支持高精度定時器,但并不是一開始就工作于高精度模式,系統(tǒng)在啟動的開始階段,還是按照傳統(tǒng)的模式在運行:tick_device按HZ頻率定期地產(chǎn)生tick事件,這時的hrtimer工作在低分辨率模式,到期事件在每個tick事件中斷中由hrtimer_run_queues函數(shù)處理,同時,在低分辨率定時器(時間輪)的軟件中斷TIMER_SOFTIRQ中,hrtimer_run_pending會被調(diào)用,系統(tǒng)在這個函數(shù)中判斷系統(tǒng)的條件是否滿足切換到高精度模式,如果條件滿足,則會切換至高分辨率模式,另外提一下,NO_HZ模式也是在該函數(shù)中判斷并切換。
void hrtimer_run_pending(void) { if (hrtimer_hres_active()) return; ...... if (tick_check_oneshot_change(!hrtimer_is_hres_enabled())) hrtimer_switch_to_hres(); }因為不管系統(tǒng)是否工作于高精度模式,每個TIMER_SOFTIRQ期間,該函數(shù)都會被調(diào)用,所以函數(shù)一開始先用hrtimer_hres_active判斷目前高精度模式是否已經(jīng)激活,如果已經(jīng)激活,則說明之前的調(diào)用中已經(jīng)切換了工作模式,不必再次切換,直接返回。hrtimer_hres_active很簡單:
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ù)接著通過tick_check_oneshot_change判斷系統(tǒng)是否可以切換到高精度模式,
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位是否被置位,如果沒有置位,說明系統(tǒng)中沒有注冊符合要求的時鐘事件設(shè)備,函數(shù)直接返回,check_clock標(biāo)志由clocksource和clock_event_device系統(tǒng)的notify系統(tǒng)置位,當(dāng)系統(tǒng)中有更高精度的clocksource被注冊和選擇后,或者有更精確的支持CLOCK_EVT_MODE_ONESHOT模式的clock_event_device被注冊時,通過它們的notify函數(shù),check_clock標(biāo)志的第0為會置位。 如果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工作于高精度模式下接下來的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ù)將進入tick_nohz_switch_to_nohz,切換至NOHZ_MODE_LOWRES 模式,這里我們傳入的allow_nohz是表達式: (!hrtimer_is_hres_enabled())
所以當(dāng)系統(tǒng)不允許高精度模式時,將會在tick_check_oneshot_change函數(shù)內(nèi),通過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ù)將會被調(diào)用,已完成切換到NOHZ_MODE_HIGHRES高精度模式。好啦,真正的切換函數(shù)找到了,我們看一看它如何切換:
首先,它通過hrtimer_cpu_base中的hres_active字段判斷該cpu是否已經(jīng)切換至高精度模式,如果是則直接返回:
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;接著,通過tick_init_highres函數(shù)接管tick_device關(guān)聯(lián)的clock_event_device:
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/n", cpu); return 0; }tick_init_highres函數(shù)把tick_device切換到CLOCK_EVT_FEAT_ONESHOT模式,同時把clock_event_device的回調(diào)handler設(shè)置為hrtimer_interrupt,這樣設(shè)置以后,tick_device的中斷回調(diào)將由hrtimer_interrupt接管,hrtimer_interrupt在上面已經(jīng)討論過,它將完成高精度定時器的調(diào)度和到期處理。 接著,設(shè)置hres_active標(biāo)志,以表明高精度模式已經(jīng)切換,然后把3個時間基準(zhǔn)系統(tǒng)的resolution字段設(shè)為KTIME_HIGH_RES:
base->hres_active = 1; for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) base->clock_base[i].resolution = KTIME_HIGH_RES;最后,因為tick_device被高精度定時器接管,它將不會再提供原有的tick事件機制,所以需要由高精度定時器系統(tǒng)模擬一個tick事件設(shè)備,繼續(xù)為系統(tǒng)提供tick事件能力,這個工作由tick_setup_sched_timer函數(shù)完成。因為剛剛完成切換,tick_device的到期時間并沒有被正確地設(shè)置為下一個到期定時器的時間,這里使用retrigger_next_event函數(shù),傳入?yún)?shù)NULL,使得tick_device立刻產(chǎn)生到期中斷,hrtimer_interrupt被調(diào)用一次,然后下一個到期的定時器的時間會編程到tick_device中,從而完成了到高精度模式的切換:
tick_setup_sched_timer(); /* "Retrigger" the interrupt to get things going */ retrigger_next_event(NULL); local_irq_restore(flags); return 1;整個切換過程可以用下圖表示:
圖3.1 低精度模式切換至高精度模式
根據(jù)上一節(jié)的討論,當(dāng)系統(tǒng)切換到高精度模式后,tick_device被高精度定時器系統(tǒng)接管,不再定期地產(chǎn)生tick事件,我們知道,到目前的版本為止(V3.4),內(nèi)核還沒有徹底廢除jiffies機制,系統(tǒng)還是依賴定期到來的tick事件,供進程調(diào)度系統(tǒng)和時間更新等操作,大量存在的低精度定時器也仍然依賴于jiffies的計數(shù),所以,盡管tick_device被接管,高精度定時器系統(tǒng)還是要想辦法繼續(xù)提供定期的tick事件。為了達到這一目的,內(nèi)核使用了一個取巧的辦法:既然高精度模式已經(jīng)啟用,可以定義一個hrtimer,把它的到期時間設(shè)定為一個jiffy的時間,當(dāng)這個hrtimer到期時,在這個hrtimer的到期回調(diào)函數(shù)中,進行和原來的tick_device同樣的操作,然后把該hrtimer的到期時間順延一個jiffy周期,如此反復(fù)循環(huán),完美地模擬了原有tick_device的功能。下面我們看看具體點代碼是如何實現(xiàn)的。
在kernel/time/tick-sched.c中,內(nèi)核定義了一個per_cpu全局變量:tick_cpu_sched,從而為每個cpu提供了一個tick_sched結(jié)構(gòu), 該結(jié)構(gòu)主要用于管理NO_HZ配置下的tickless處理,因為模擬tick事件與tickless有很強的相關(guān)性,所以高精度定時器系統(tǒng)也利用了該結(jié)構(gòu)的以下字段,用于完成模擬tick事件的操作:
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)討論過,用于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è)置一個用于模擬tick事件的hrtimer:
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è)定為下一個jiffy時刻,返回前把工作模式設(shè)置為NOHZ_MODE_HIGHRES,表明是利用高精度模式實現(xiàn)NO_HZ。 接著我們關(guān)注一下hrtimer的回調(diào)函數(shù)tick_sched_timer,我們知道,系統(tǒng)中的jiffies計數(shù),時間更新等是全局操作,在smp系統(tǒng)中,只有一個cpu負責(zé)該工作,所以在tick_sched_timer的一開始,先判斷當(dāng)前cpu是否負責(zé)更新jiffies和時間,如果是,則執(zhí)行更新操作:
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:
if (regs) { ...... update_process_times(user_mode(regs)); ...... }最后,把hrtimer的到期時間推進一個tick周期,返回HRTIMER_RESTART表明該hrtimer需要再次啟動,以便產(chǎn)生下一個tick事件。
hrtimer_forward(timer, now, tick_period); return HRTIMER_RESTART; }關(guān)于update_process_times,如果你你感興趣,回看一下本系列關(guān)于clock_event_device的那一章:Linux時間子系統(tǒng)之四:定時器的引擎:clock_event_device中的第5小節(jié),對比一下模擬tick事件的hrtimer的回調(diào)函數(shù)tick_sched_timer和切換前tick_device的回調(diào)函數(shù)tick_handle_periodic,它們是如此地相像,實際上,它們幾乎完成了一樣的工作。
新聞熱點
疑難解答