標籤:
轉自:http://blog.csdn.net/wowuyinglingluan/article/details/45720151
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
目錄(?)[-]
- 整段代碼
- 關於無效的forward
- 關於定時精度問題
- 精確調整和overrun問題
- 存疑
隨著各種嵌入式裝置上採用linux,特別是Android系統的廣泛應用,linux的hrtimer高精度模式開始被廣泛支援。當然,雖說可以支援到ns精度,具體實現依賴於硬體定時器和核心編譯條件,不過,一般情況下,幾十us的定時精度都是支援的。這給編寫驅動帶來了很大的方便。
之前有一篇非常強大的博文 Linux時間子系統之六:高精度定時器(HRTIMER)的原理和實現,已經將hrtimer的基本原理和hrtimer的應用方法做了清晰詳盡的剖析,這裡針對在應用hrtimer時,往往導致定時器沒有按照設計精度執行的問題,分析一下hrtimer_forword的內部的小段代碼。
整段代碼
817 u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)818 {819 u64 orun = 1;820 ktime_t delta;821 822 delta = ktime_sub(now, hrtimer_get_expires(timer));823 824 if (delta.tv64 < 0)825 return 0;826 827 if (interval.tv64 < timer->base->resolution.tv64)828 interval.tv64 = timer->base->resolution.tv64;829 830 if (unlikely(delta.tv64 >= interval.tv64)) {831 s64 incr = ktime_to_ns(interval);832 833 orun = ktime_divns(delta, incr);834 hrtimer_add_expires_ns(timer, incr * orun);835 if (hrtimer_get_expires_tv64(timer) > now.tv64)836 return orun;837 /*838 * This (and the ktime_add() below) is the839 * correction for exact:840 */841 orun++;842 }843 hrtimer_add_expires(timer, interval);844 845 return orun;846 }
關於無效的forward
以上是3.4版本核心中的hrtimer_forward的實現,我們來分析一下這裡面的幾個關鍵環節:
首先時給出的now,這個時間很重要,
822 delta = ktime_sub(now, hrtimer_get_expires(timer));823 824 if (delta.tv64 < 0)825 return 0;
因為如上我們可以看到,如果目前時間比定時器到期時間要早,說明定時器上還有一個工作需要在目前時間完成,因此,不會進行任何操作。直接返回,也就是說,如果我們希望這次hrtimer_forward起作用,那麼實際上,不會起任何作用。
關於定時精度問題
接下來看看定時精度,如果我們看一下針對某一嵌入式系統的linux源碼中hrtimer的實現,我們就會看到,良好的實現均準確地設定了resolution,這個resolution是定時器能夠實現的最高精度,當然這一般不是硬體決定的(一個主頻48Mhz的MCU,其硬體定時器的定時精度都在20ns),而是嵌入式系統設計者根據系統的整體效能考慮權衡的結果。
827 if (interval.tv64 < timer->base->resolution.tv64)828 interval.tv64 = timer->base->resolution.tv64;
因此我們看到,如果我們給出的interval如果比這個精度要高,那麼,我們只能得到最短等於該精度的定時。
精確調整和overrun問題
接下去的代碼,還有點兒讓對gcc不太瞭解的人糊塗,就是
830 if (unlikely(delta.tv64 >= interval.tv64)) {
這句話的關節在於unlikely宏,其實這是在提醒gcc編譯器,一般情況下,delta.tv64 >= interval.tv64的條件按是不成立的,這可以讓gcc編譯出更加高效的代碼。並不影響if內部的判斷,我們可以直接理解為:
if (delta.tv64 >= interval.tv64) {
而在這個條件為真的時候,會對expires作較為精密的外科手術。delta是一個由於hrtimer自身遍曆以及我們callback函數內部的業務處理,耗費了一定時間,已經超過了 interval,那麼則認為出現了overrun現象,即下一次定時已經被覆蓋掉了。這時,核心希望做一些補救工作,不過也做不了多少,就是告訴你按照你給出interval,已經逾時了幾次了,並且看看是否可以根據這個來微調後續的定時時間。這裡的代碼挺有意思,我們連起來看一下
/*這是delta怎麼來的*/822 delta = ktime_sub(now, hrtimer_get_expires(timer));831 s64 incr = ktime_to_ns(interval);832 /*這是一個將當前的expires加上detal所能包含的interval整數倍的時間的計算833 orun = ktime_divns(delta, incr);834 hrtimer_add_expires_ns(timer, incr * orun);
那麼,如果hrtimer的處理,是在硬體中斷上的原子性操作,且ktime_divns只是簡單的整數除法,以下的代碼絕對不可能成立:
835 if (hrtimer_get_expires_tv64(timer) > now.tv64)836 return orun;
那麼,實際上,它是一個通過損失精度來完成計算的除法,如果除數大於32位,那麼除數與被除數一起損失精度到除數小於等於32位為止,以下是其函數定義:
u64 ktime_divns(const ktime_t kt, s64 div)313 {314 u64 dclc;315 int sft = 0;316 317 dclc = ktime_to_ns(kt);318 /* Make sure the divisor is less than 2^32: */319 while (div >> 32) {320 sft++;321 div >>= 1;322 }323 dclc >>= sft;324 do_div(dclc, (unsigned long) div);325 326 return dclc;327 }
因此,如果inteval是一個大於2^32的大數的時候,還真有可能讓條件成立滴^_^。這是傳回值和載入expires上的值都對,沒有什麼要講的了。
如果不成立,則
841 orun++;842 }843 hrtimer_add_expires(timer, interval);
存疑
那麼在這樣的一個狀態下,很不幸,expires被加上了(incr * orun+interval)這麼多的時間,我們可以理解為,此時,核心認為你希望得到的定時時間為interval給定定時時間的整數倍,然後你還知道有幾次overrun,這個主意不壞,但是我們帶入實際的數值,就會感覺比較好玩了。
假設:
delta=5ns,interval=4ns
則:
行833計算結果:orrun=1
行834在原expires上加上了4ns
那麼,由於行835此時條件不會為真那麼在:
行841,orrun變成了2
然後在行843,expires的值加上了4ns
至此,expires一共加上了8ns,如果所有其他動作瞬間完成的話,將在3ns後得到下一次callback的機會。
如果按照這樣的邏輯,我們看到返回的結果淩亂了,明明overrun了一次,結果會返回2。如果我沒有理解錯誤的話,此時的overrun不能反應實際的情況了
關於hrtimer_forward小段代碼的分析【轉】