kernel-2.6.22中的arm arch加入了對dynticks, clocksource/event支持.
imx31的BSP在clock這里有一些改動. 找了些kernel clock及timer子系統(tǒng)近來的變化, 總結(jié)一下.
一般來說Soft-Timer (timer wheel / hrtimer)
都是由Hardware-Timer(時鐘中斷之類)以及相關(guān)的clock source(e.g GPT in Soc)驅(qū)動,
所以我打算先從clock這層開始介紹, 接著是soft-timer, kernel timekeeping, 最后來看一些應(yīng)用.
Clock Source
clock source定義了一個clock device的基本屬性及行為, 這些clock device一般都有計數(shù), 定時, 產(chǎn)生中斷能力, 比如GPT. 結(jié)構(gòu)定義如下:
|
struct clocksource {
char *name;
struct list_head list;
int rating;
cycle_t (*read)(void);
cycle_t mask;
u32 mult; /* cycle -> xtime interval, maybe two clock cycle trigger one interrupt (one xtime interval) */
u32 shift;
unsigned long flags;
cycle_t (*vread)(void);
void (*resume)(void);
/* timekeeping specific data, ignore */
cycle_t cycle_interval; /* just the rate of GPT count per OS HZ */
u64 xtime_interval; /* xtime_interval = cycle_interval * mult. */
cycle_t cycle_last ____cacheline_aligned_in_smp; /* last cycle in rate count */
u64 xtime_nsec; /* cycle count, remain from xtime.tv_nsec
* now nsec rate count offset = xtime_nsec + * xtime.tv_nsec << shift */
s64 error;
};
|
最重要的成員是read(),
cycle_last和cycle_interval. 分別定義了讀取clock device count 寄存器當前計數(shù)值接口,
保存上一次周期計數(shù)值和每個tick周期間隔值. 這個結(jié)構(gòu)內(nèi)的值, 無論是cycle_t,
還是u64類型(實際cycle_t就是u64)都是計數(shù)值(cycle), 而不是nsec, sec和jiffies.
read()是整個kernel讀取精確的單調(diào)時間計數(shù)的接口, kernel會用它來計算其他時間, 比如:jiffies, xtime.
clocksource的引入, 解決了之前kernel各個arch都有自己的clock device的管理方式, 基本都隱藏在MSL層, kernel core 及driver很難訪問的問題. 它導(dǎo)出了以下接口:
1) clocksource_register() 注冊clocksource
2) clocksource_get_next() 獲取當前clocksource設(shè)備
3) clocksource_read() 讀取clock, 實際跑到clocksource->read()
當driver處理的時間精度比較高的時, 可以通過上面的接口, 直接拿clock device來讀.
當然目前ticker時鐘中斷源也會以clocksource的形式存在.
Clock Event
Clock event的主要作用是分發(fā)clock事件及設(shè)置下一次觸發(fā)條件. 在沒有clock event之前, 時鐘中斷都是周期性地產(chǎn)生, 也就是熟知的jiffies和HZ.
Clock Event device主要的結(jié)構(gòu):
|
struct clock_event_device {
const char *name;
unsigned int features;
unsigned long max_delta_ns;
unsigned long min_delta_ns;
unsigned long mult;
int shift;
int rating;
int irq;
cpumask_t cpumask;
int (*set_next_event)(unsigned long evt,
struct clock_event_device *);
void (*set_mode)(enum clock_event_mode mode,
struct clock_event_device *);
void (*event_handler)(struct clock_event_device *);
void (*broadcast)(cpumask_t mask);
struct list_head list;
enum clock_event_mode mode;
ktime_t next_event;
};
|
最重要的是set_next_event(),
event_handler(). 前者是設(shè)置下一個clock事件的觸發(fā)條件, 一般就是往clock device里重設(shè)一下定時器.
后者是event handler, 事件處理函數(shù). 該處理函數(shù)會在時鐘中斷ISR里被調(diào)用. 如果這個clock用來做為ticker時鐘,
那么handler的執(zhí)行和之前kernel的時鐘中斷ISR基本相同, 類似timer_tick(). 事件處理函數(shù)可以在運行時動態(tài)替換,
這就給kernel一個改變整個時鐘中斷處理方式的機會, 也就給highres tick及dynamic tick一個動態(tài)掛載的機會.
目前kernel內(nèi)部有periodic/highres/dynamic tick三種時鐘中斷處理方式. 后面會介紹.
hrtimer & timer wheel
首先說一下timer wheel. 它就是kernel一直采用的基于jiffies的timer機制, 接口包括init_timer(), mod_timer(), del_timer()等, 很熟悉把.
hrtimer
的出現(xiàn), 并沒有拋棄老的timer wheel機制(也不太可能拋棄:)). hrtimer做為kernel里的timer定時器, 而timer
wheel則主要用來做timeout定時器. 分工比較明確. hrtimers采用紅黑樹來組織timers, 而timer
wheel采用鏈表和桶.
hrtimer精度由原來的timer wheel的jiffies提高到
nanosecond. 主要用于向應(yīng)用層提供nanosleep, posix-timers和itimer接口, 當然驅(qū)動和其他子系統(tǒng)也會需要high resolution的timer.
kernel
里原先每秒周期性地產(chǎn)生HZ個ticker(中斷), 被在下一個過期的hrtimer的時間點上產(chǎn)生中斷代替. 也就是說時鐘中斷不再是周期性的,
而是由timer來驅(qū)動(靠clockevent的set_next_event接口設(shè)置下一個事件中斷), 只要沒有hrtimer加載,
就沒有中斷. 但是為了保證系統(tǒng)時間(進程時間統(tǒng)計, jiffies的維護)更新, 每個tick_period(NSEC_PER_SEC/HZ,
再次強調(diào)hrtimer精度是nsec)都會有一個叫做tick_sched_timer的hrtimer加載.

接下來對比一下, hrtimer引入之前及之后, kernel里時鐘中斷的處理的不同. (這里都是基于arm arch的source去分析)
1)no hrtimer
kernel
起來, setup_arch()之后的time_init()會去初始化相應(yīng)machine結(jié)構(gòu)下的timer.
初始化timer函數(shù)都在各個machine的體系結(jié)構(gòu)代碼中, 初始化完硬件時鐘, 注冊中斷服務(wù)函數(shù), 使能時鐘中斷. 中斷服務(wù)程序會清中斷,
調(diào)用timer_tick(), 它執(zhí)行:
1. profile_tick(); /* kernel profile, 不是很了解 */2. do_timer(1); /* 更新jiffies */3. update_process_times(); /* 計算進程耗時, 喚起TIMER_SOFTIRQ(timer wheel), 重新計算調(diào)度時間片等等 */最后中斷服務(wù)程序設(shè)置定時器, 使其在下一個tick產(chǎn)生中斷.
這樣的框架, 使得high-res的timer很難加入. 所有中斷處理code都在體系結(jié)構(gòu)代碼里被寫死, 并且代碼重用率很低, 畢竟大多的arch都會寫同樣的中斷處理函數(shù).
2)hrtimer
kernel
里有了clockevent/source的引入, 就把clocksource的中斷以一種事件的方式被抽象出來. 事件本身的處理交給event
handler. handler可以在kernel里做替換從而改變時鐘中斷的行為. 時鐘中斷ISR會看上去象這樣:
|
static irqreturn_t timer_interrupt(int irq, void *dev_id)
{
/* clear timer interrupt flag */
.....
/* call clock event handler */ arch_clockevent.event_handler(&arch_clockevent);
....
return IRQ_HANDLED;
}
|
event_handler
在注冊clockevent device時, 會被默認設(shè)置成tick_handle_periodic(). 所以kernel剛起來的時候,
時鐘處理機制仍然是periodic的, ticker中斷周期性的產(chǎn)生.
tick_handle_periodic()會做和timer_tick差不多的事情,
然后調(diào)用clockevents_program_event() =>
arch_clockevent.set_next_event()去設(shè)置下一個周期的定時器.
tick-common.c里把原來kernel時鐘的處理方式在clockevent框架下實現(xiàn)了, 這就是periodic tick的時鐘機制.
hres
tick機制在第一個TIMER SOFTIRQ里會替換掉periodic tick, 當然要符合一定條件, 比如command
line里沒有把hres(highres=off)禁止掉, clocksource/event支持hres和oneshot的能力.
這里的切換做的比較ugly, 作者的comments也提到了, 每次timer softirq被調(diào)度,
都要調(diào)用hrtimer_run_queues()檢查一遍hres是否active,
如果能在timer_init()里就把clocksource/event的條件check過, 直接切換到hres就最好了,
不知道是不是有什么限制條件. TIMER SOFTIRQ代碼如下:
|
static void run_timer_softirq(struct softirq_action *h)
{
tvec_base_t *base = __get_cpu_var(tvec_bases);
hrtimer_run_queues(); /* 有機會就切換到hres或者nohz */
if (time_after_eq(jiffies, base->timer_jiffies))
__run_timers(base); /* timer wheel */
}
|
切換的過程比較簡單, 用hrtimer_interrupt()替換當前clockevent hander, 加載一個hrtimer: tick_sched_timer在下一個tick_period過期, retrigger下一次事件.
hrtimer_interrupt()
將過期的hrtimers從紅黑樹上摘下來, 放到相應(yīng)clock_base->cpu_base->cb_pending列表里,
這些過期timers會在HRTIMER_SOFTIRQ里執(zhí)行. 然后根據(jù)剩余的最早過期的timer來retrigger下一個event,
再調(diào)度HRTIMER_SOFTIRQ. hrtimer softirq執(zhí)行那些再cb_pending上的過期定時器函數(shù).
tick_sched_timer這個hrtimer在每個tick_period都會過期, 執(zhí)行過程和timer_tick()差不多,
只是在最后調(diào)用hrtimer_forward將自己加載到下一個周期里去, 保證每個tick_period都能正確更新kernel內(nèi)部時間統(tǒng)計.
Timekeeping
Timekeeping子系統(tǒng)負責(zé)更新xtime, 調(diào)整誤差, 及提供get/settimeofday接口. 為了便于理解, 首先介紹一些概念:
Times in Kernel
kernel的time基本類型:
1) system timeA monotonically increasing value that represents the amount of time the system has been running. 單調(diào)增長的系統(tǒng)運行時間, 可以通過
time source,
xtime及
wall_to_monotonic計算出來.
2) wall timeA value representing the the human time of day, as seen on a wrist-watch. Realtime時間:
xtime.
3) time sourceA representation of a free running counter running at a known frequency, usually in hardware, e.g GPT. 可以通過
clocksource->read()得到counter值
4) tickA periodic interrupt generated by a hardware-timer, typically with a fixed interval
defined by HZ:
jiffies這些time之間互相關(guān)聯(lián), 互相可以轉(zhuǎn)換.
system_time = xtime + cyc2ns(clock->read() - clock->cycle_last) + wall_to_monotonic;
real_time = xtime + cyc2ns(clock->read() - clock->cycle_last)
也就是說real time是從1970年開始到現(xiàn)在的nanosecond, 而system time是系統(tǒng)啟動到現(xiàn)在的nanosecond.
這兩個是最重要的時間, 由此hrtimer可以基于這兩個time來設(shè)置過期時間. 所以引入兩個clock base.
Clock Base
CLOCK_REALTIME: base在實際的wall time
CLOCK_MONOTONIC: base在系統(tǒng)運行system time
hrtimer可以選擇其中之一, 來設(shè)置expire time, 可以是實際的時間, 也可以是相對系統(tǒng)的時間.
他們提供get_time()接口:
CLOCK_REALTIME 調(diào)用ktime_get_real()來獲得真實時間, 該函數(shù)用上面提到的等式計算出realtime.
CLOCK_MONOTONIC 調(diào)用ktime_get(), 用system_time的等式獲得monotonic time.
timekeeping提供兩個接口do_gettimeofday()/do_settimeofday(), 都是針對realtime操作. 用戶空間對gettimeofday的syscall也會最終跑到這里來.
do_gettimeofday()會調(diào)用__get_realtime_clock_ts()獲得時間, 然后轉(zhuǎn)成timeval.
do_settimeofday(), 將用戶設(shè)置的時間更新到xtime, 重新計算xtime到monotonic的轉(zhuǎn)換值, 最后通知hrtimers子系統(tǒng)時間變更.
|
int do_settimeofday(struct timespec *tv)
{
unsigned long flags;
time_t wtm_sec, sec = tv->tv_sec;
long wtm_nsec, nsec = tv->tv_nsec;
if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC)
return -EINVAL;
write_seqlock_irqsave(&xtime_lock, flags);
nsec -= __get_nsec_offset();
wtm_sec = wall_to_monotonic.tv_sec + (xtime.tv_sec - sec);
wtm_nsec = wall_to_monotonic.tv_nsec + (xtime.tv_nsec - nsec);
set_normalized_timespec(&xtime, sec, nsec); /* 重新計算xtime: 用戶設(shè)置的時間減去上一個周期到現(xiàn)在的nsec */
set_normalized_timespec(&wall_to_monotonic, wtm_sec, wtm_nsec); /* 重新調(diào)整wall_to_monotonic */
clock->error = 0;
ntp_clear();
update_vsyscall(&xtime, clock);
write_sequnlock_irqrestore(&xtime_lock, flags);
/* signal hrtimers about time change */
clock_was_set();
return 0;
}
|
Userspace Application
hrtimer的引入, 對用戶最有用的接口如下:
Clock APIclock_gettime(clockid_t, struct timespec *)獲取對應(yīng)clock的時間
clock_settime(clockid_t, const struct timespec *)設(shè)置對應(yīng)clock時間
clock_nanosleep(clockid_t, int, const struct timespec *, struct timespec *)進程nano sleep
clock_getres(clockid_t, struct timespec *)獲取時間精度, 一般是nanosec
clockid_t 定義了四種clock:
- CLOCK_REALTIME
- System-wide realtime clock. Setting this clock requires appropriate privileges.
- CLOCK_MONOTONIC
- Clock that cannot be set and represents monotonic time since some unspecified starting point.
- CLOCK_PROCESS_CPUTIME_ID
- High-resolution per-process timer from the CPU.
- CLOCK_THREAD_CPUTIME_ID
- Thread-specific CPU-time clock.
前兩者前面提到了, 后兩個是和進程/線程統(tǒng)計時間有關(guān)系, 還沒有仔細研究過, 是utime/stime之類的時間. 應(yīng)用層可以利用這四種clock, 提高靈活性及精度.
Timer API
Timer 可以建立進程定時器,單次或者周期性定時。
int timer_create(clockid_t clockid, struct sigevent *restrict evp, timer_t *restrict timerid);創(chuàng)建定時器。
clockid 指定在哪個clock base下創(chuàng)建定時器。
evp (sigevent) 可以指定定時器到期后內(nèi)核發(fā)送哪個信號給進程,以及信號所帶參數(shù);默認為SIGALRM。
timerid 返回所建timer的id號。
在
signal處理函數(shù)里,可以通過siginfo_t.si_timerid 獲得當前的信號是由哪個timer過期觸發(fā)的。
試驗了一下,最多可創(chuàng)建的timer數(shù)目和ulimit里的pending signals的有關(guān)系,不能超過pending signals的數(shù)量。
int timer_gettime(timer_t timerid, struct itimerspec *value); 獲得timer的下次過期的時間。
int timer_settime(timer_t timerid, int flags, const struct itimerspec *restrict value, struct itimerspec *restrict ovalue);設(shè)置定時器的過期時間及間隔周期。
int timer_delete(timer_t timerid);刪除定時器。
這些系統(tǒng)調(diào)用都會建立一個posix_timer的hrtimer,在過期的時候發(fā)送信號給進程。
總結(jié)
hrtimer
及clockevent/source的引入對于kernel的實時性的提高有很大貢獻,也將clock的處理從體系結(jié)構(gòu)的代碼中抽象了出來,增強了代碼
的可重用性。并且對于posix的time/timer標準有了強有力的支持,提高了用戶空間的應(yīng)用程序的時間處理精度及靈活性。如果應(yīng)用層在使用這些
syscall時有任何不解之處,直接看看hrtimer的code,對于處理問題,理解OS的行為都有很大幫助。