| kernel-2.6.22中的arm arch加入了對dynticks, clocksource/event支持. imx31的BSP在clock這里有一些改動(dòng). 找了些kernel clock及timer子系統近來(lái)的變化, 總結一下. 一般來(lái)說(shuō)Soft-Timer (timer wheel / hrtimer) 都是由Hardware-Timer(時(shí)鐘中斷之類(lèi))以及相關(guān)的clock source(e.g GPT in Soc)驅動(dòng), 所以我打算先從clock這層開(kāi)始介紹, 接著(zhù)是soft-timer, kernel timekeeping, 最后來(lái)看一些應用. Clock Sourceclock source定義了一個(gè)clock device的基本屬性及行為, 這些clock device一般都有計數, 定時(shí), 產(chǎn)生中斷能力, 比如GPT. 結構定義如下:
最重要的成員是read(), cycle_last和cycle_interval. 分別定義了讀取clock device count 寄存器當前計數值接口, 保存上一次周期計數值和每個(gè)tick周期間隔值. 這個(gè)結構內的值, 無(wú)論是cycle_t, 還是u64類(lèi)型(實(shí)際cycle_t就是u64)都是計數值(cycle), 而不是nsec, sec和jiffies. read()是整個(gè)kernel讀取精確的單調時(shí)間計數的接口, kernel會(huì )用它來(lái)計算其他時(shí)間, 比如:jiffies, xtime. clocksource的引入, 解決了之前kernel各個(gè)arch都有自己的clock device的管理方式, 基本都隱藏在MSL層, kernel core 及driver很難訪(fǎng)問(wèn)的問(wèn)題. 它導出了以下接口: 1) clocksource_register() 注冊clocksource 2) clocksource_get_next() 獲取當前clocksource設備 3) clocksource_read() 讀取clock, 實(shí)際跑到clocksource->read() 當driver處理的時(shí)間精度比較高的時(shí), 可以通過(guò)上面的接口, 直接拿clock device來(lái)讀. 當然目前ticker時(shí)鐘中斷源也會(huì )以clocksource的形式存在. Clock EventClock event的主要作用是分發(fā)clock事件及設置下一次觸發(fā)條件. 在沒(méi)有clock event之前, 時(shí)鐘中斷都是周期性地產(chǎn)生, 也就是熟知的jiffies和HZ.Clock Event device主要的結構:
hrtimer & timer wheel首先說(shuō)一下timer wheel. 它就是kernel一直采用的基于jiffies的timer機制, 接口包括init_timer(), mod_timer(), del_timer()等, 很熟悉把.hrtimer 的出現, 并沒(méi)有拋棄老的timer wheel機制(也不太可能拋棄:)). hrtimer做為kernel里的timer定時(shí)器, 而timer wheel則主要用來(lái)做timeout定時(shí)器. 分工比較明確. hrtimers采用紅黑樹(shù)來(lái)組織timers, 而timer wheel采用鏈表和桶. hrtimer精度由原來(lái)的timer wheel的jiffies提高到nanosecond. 主要用于向應用層提供nanosleep, posix-timers和itimer接口, 當然驅動(dòng)和其他子系統也會(huì )需要high resolution的timer. kernel 里原先每秒周期性地產(chǎn)生HZ個(gè)ticker(中斷), 被在下一個(gè)過(guò)期的hrtimer的時(shí)間點(diǎn)上產(chǎn)生中斷代替. 也就是說(shuō)時(shí)鐘中斷不再是周期性的, 而是由timer來(lái)驅動(dòng)(靠clockevent的set_next_event接口設置下一個(gè)事件中斷), 只要沒(méi)有hrtimer加載, 就沒(méi)有中斷. 但是為了保證系統時(shí)間(進(jìn)程時(shí)間統計, jiffies的維護)更新, 每個(gè)tick_period(NSEC_PER_SEC/HZ, 再次強調hrtimer精度是nsec)都會(huì )有一個(gè)叫做tick_sched_timer的hrtimer加載. ![]() 1)no hrtimerkernel 起來(lái), setup_arch()之后的time_init()會(huì )去初始化相應machine結構下的timer. 初始化timer函數都在各個(gè)machine的體系結構代碼中, 初始化完硬件時(shí)鐘, 注冊中斷服務(wù)函數, 使能時(shí)鐘中斷. 中斷服務(wù)程序會(huì )清中斷, 調用timer_tick(), 它執行:1. profile_tick(); /* kernel profile, 不是很了解 */ 2. do_timer(1); /* 更新jiffies */ 3. update_process_times(); /* 計算進(jìn)程耗時(shí), 喚起TIMER_SOFTIRQ(timer wheel), 重新計算調度時(shí)間片等等 */ 最后中斷服務(wù)程序設置定時(shí)器, 使其在下一個(gè)tick產(chǎn)生中斷. 這樣的框架, 使得high-res的timer很難加入. 所有中斷處理code都在體系結構代碼里被寫(xiě)死, 并且代碼重用率很低, 畢竟大多的arch都會(huì )寫(xiě)同樣的中斷處理函數. 2)hrtimerkernel 里有了clockevent/source的引入, 就把clocksource的中斷以一種事件的方式被抽象出來(lái). 事件本身的處理交給event handler. handler可以在kernel里做替換從而改變時(shí)鐘中斷的行為. 時(shí)鐘中斷ISR會(huì )看上去象這樣:
event_handler 在注冊clockevent device時(shí), 會(huì )被默認設置成tick_handle_periodic(). 所以kernel剛起來(lái)的時(shí)候, 時(shí)鐘處理機制仍然是periodic的, ticker中斷周期性的產(chǎn)生. tick_handle_periodic()會(huì )做和timer_tick差不多的事情, 然后調用clockevents_program_event() => arch_clockevent.set_next_event()去設置下一個(gè)周期的定時(shí)器. tick-common.c里把原來(lái)kernel時(shí)鐘的處理方式在clockevent框架下實(shí)現了, 這就是periodic tick的時(shí)鐘機制. hres tick機制在第一個(gè)TIMER SOFTIRQ里會(huì )替換掉periodic tick, 當然要符合一定條件, 比如command line里沒(méi)有把hres(highres=off)禁止掉, clocksource/event支持hres和oneshot的能力. 這里的切換做的比較ugly, 作者的comments也提到了, 每次timer softirq被調度, 都要調用hrtimer_run_queues()檢查一遍hres是否active, 如果能在timer_init()里就把clocksource/event的條件check過(guò), 直接切換到hres就最好了, 不知道是不是有什么限制條件. TIMER SOFTIRQ代碼如下:
切換的過(guò)程比較簡(jiǎn)單, 用hrtimer_interrupt()替換當前clockevent hander, 加載一個(gè)hrtimer: tick_sched_timer在下一個(gè)tick_period過(guò)期, retrigger下一次事件. hrtimer_interrupt() 將過(guò)期的hrtimers從紅黑樹(shù)上摘下來(lái), 放到相應clock_base->cpu_base->cb_pending列表里, 這些過(guò)期timers會(huì )在HRTIMER_SOFTIRQ里執行. 然后根據剩余的最早過(guò)期的timer來(lái)retrigger下一個(gè)event, 再調度HRTIMER_SOFTIRQ. hrtimer softirq執行那些再cb_pending上的過(guò)期定時(shí)器函數. tick_sched_timer這個(gè)hrtimer在每個(gè)tick_period都會(huì )過(guò)期, 執行過(guò)程和timer_tick()差不多, 只是在最后調用hrtimer_forward將自己加載到下一個(gè)周期里去, 保證每個(gè)tick_period都能正確更新kernel內部時(shí)間統計. TimekeepingTimekeeping子系統負責更新xtime, 調整誤差, 及提供get/settimeofday接口. 為了便于理解, 首先介紹一些概念: Times in Kernelkernel的time基本類(lèi)型:1) system time A monotonically increasing value that represents the amount of time the system has been running. 單調增長(cháng)的系統運行時(shí)間, 可以通過(guò)time source, xtime及wall_to_monotonic計算出來(lái). 2) wall time A value representing the the human time of day, as seen on a wrist-watch. Realtime時(shí)間: xtime. 3) time source A representation of a free running counter running at a known frequency, usually in hardware, e.g GPT. 可以通過(guò)clocksource->read()得到counter值 4) tick A periodic interrupt generated by a hardware-timer, typically with a fixed interval defined by HZ: jiffies 這些time之間互相關(guān)聯(lián), 互相可以轉換. system_time = xtime + cyc2ns(clock->read() - clock->cycle_last) + wall_to_monotonic; real_time = xtime + cyc2ns(clock->read() - clock->cycle_last) 也就是說(shuō)real time是從1970年開(kāi)始到現在的nanosecond, 而system time是系統啟動(dòng)到現在的nanosecond. 這兩個(gè)是最重要的時(shí)間, 由此hrtimer可以基于這兩個(gè)time來(lái)設置過(guò)期時(shí)間. 所以引入兩個(gè)clock base. Clock BaseCLOCK_REALTIME: base在實(shí)際的wall timeCLOCK_MONOTONIC: base在系統運行system time hrtimer可以選擇其中之一, 來(lái)設置expire time, 可以是實(shí)際的時(shí)間, 也可以是相對系統的時(shí)間. 他們提供get_time()接口: CLOCK_REALTIME 調用ktime_get_real()來(lái)獲得真實(shí)時(shí)間, 該函數用上面提到的等式計算出realtime. CLOCK_MONOTONIC 調用ktime_get(), 用system_time的等式獲得monotonic time. timekeeping提供兩個(gè)接口do_gettimeofday()/do_settimeofday(), 都是針對realtime操作. 用戶(hù)空間對gettimeofday的syscall也會(huì )最終跑到這里來(lái). do_gettimeofday()會(huì )調用__get_realtime_clock_ts()獲得時(shí)間, 然后轉成timeval. do_settimeofday(), 將用戶(hù)設置的時(shí)間更新到xtime, 重新計算xtime到monotonic的轉換值, 最后通知hrtimers子系統時(shí)間變更.
Userspace Applicationhrtimer的引入, 對用戶(hù)最有用的接口如下:Clock API clock_gettime(clockid_t, struct timespec *) 獲取對應clock的時(shí)間 clock_settime(clockid_t, const struct timespec *) 設置對應clock時(shí)間 clock_nanosleep(clockid_t, int, const struct timespec *, struct timespec *) 進(jìn)程nano sleep clock_getres(clockid_t, struct timespec *) 獲取時(shí)間精度, 一般是nanosec clockid_t 定義了四種clock:
Timer API Timer 可以建立進(jìn)程定時(shí)器,單次或者周期性定時(shí)。 int timer_create(clockid_t clockid, struct sigevent *restrict evp, timer_t *restrict timerid); 創(chuàng )建定時(shí)器。 clockid 指定在哪個(gè)clock base下創(chuàng )建定時(shí)器。 evp (sigevent) 可以指定定時(shí)器到期后內核發(fā)送哪個(gè)信號給進(jìn)程,以及信號所帶參數;默認為SIGALRM。 timerid 返回所建timer的id號。 在 signal處理函數里,可以通過(guò)siginfo_t.si_timerid 獲得當前的信號是由哪個(gè)timer過(guò)期觸發(fā)的。 試驗了一下,最多可創(chuàng )建的timer數目和ulimit里的pending signals的有關(guān)系,不能超過(guò)pending signals的數量。 int timer_gettime(timer_t timerid, struct itimerspec *value); 獲得timer的下次過(guò)期的時(shí)間。 int timer_settime(timer_t timerid, int flags, const struct itimerspec *restrict value, struct itimerspec *restrict ovalue); 設置定時(shí)器的過(guò)期時(shí)間及間隔周期。 int timer_delete(timer_t timerid); 刪除定時(shí)器。 這些系統調用都會(huì )建立一個(gè)posix_timer的hrtimer,在過(guò)期的時(shí)候發(fā)送信號給進(jìn)程。 總結hrtimer 及clockevent/source的引入對于kernel的實(shí)時(shí)性的提高有很大貢獻,也將clock的處理從體系結構的代碼中抽象了出來(lái),增強了代碼 的可重用性。并且對于posix的time/timer標準有了強有力的支持,提高了用戶(hù)空間的應用程序的時(shí)間處理精度及靈活性。如果應用層在使用這些 syscall時(shí)有任何不解之處,直接看看hrtimer的code,對于處理問(wèn)題,理解OS的行為都有很大幫助。 |
聯(lián)系客服