Linux時間子系統之三:jiffies


 1. jiffies背景介紹

jiffies記錄了系統啟動以來,經過了多少tick。

一個tick代表多長時間,在內核的CONFIG_HZ中定義。比如CONFIG_HZ=200,則一個jiffies對應5ms時間。所以內核基於jiffies的定時器精度也是5ms。

2. jiffies初始化與更新

2.1 jiffies初始化

jiffies的初始值並不是0,而是300s后即將溢出的的值。這是為了存在溢出問題的情況下,盡早暴露問題。

arch/arm/kernel/vmlinux.lds.S:

  jiffies = jiffies_64;


include/linux/jiffies.h: /* * Have the 32 bit jiffies value wrap 5 minutes after boot * so jiffies wrap bugs show up earlier. */ #define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ)) kernel/timer.c: u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES; EXPORT_SYMBOL(jiffies_64);

jiffies_64的初始值是4294907296,2^32-1=4294967295。4294967295-4294907296+1=60000個tick,1HZ等於200,200*300=60000。

下面的log信息是在do_timer中打印的,jiffies_64是jiffies的64位變量,jiffies是jiffies的32位變量,ticks是一次增加的ticks數,最后面的jiffies其實是monotonic的ms數。

[  427.637176] arnoldlu [do_timer 1294] jiffies_64=4294965200 jiffies=4294965200 ticks=4, jiffies=289541
[  430.639099] arnoldlu [do_timer 1294] jiffies_64=4294965800 jiffies=4294965800 ticks=4, jiffies=292543
[  436.638854] arnoldlu [do_timer 1294] jiffies_64=4294967000 jiffies=4294967000 ticks=4, jiffies=298543
[  439.603607] arnoldlu [do_timer 1294] jiffies_64=4294967596 jiffies=300 ticks=1, jiffies=301508------------32位jiffies在溢出過后,從0重新計數。301508ms,換算成tick是60301個tick,即60000+300+1,60000是移除前經歷的tick數目,300是當前jiffies,1是此次新增jiffies數。
[  440.119110] arnoldlu [do_timer 1294] jiffies_64=4294967696 jiffies=400 ticks=4, jiffies=302023------------jiffies_64不存在溢出情況。
[  446.119903] arnoldlu [do_timer 1294] jiffies_64=4294968896 jiffies=1600 ticks=4, jiffies=308024
[  446.600616] arnoldlu [do_timer 1294] jiffies_64=4294968996 jiffies=1700 ticks=1, jiffies=308505

 

 2.2 jiffies的更新

jiffies就是ktime_get()的另一種形式。

void do_timer(unsigned long ticks)
{
    jiffies_64 += ticks;
    update_wall_time();
    calc_global_load(ticks);
}


/*
 * Must be called with interrupts disabled !
 */
static void tick_do_update_jiffies64(ktime_t now)-----------------只有一個參數,所有的now值都是通過ktime_get()獲取的。
{
    unsigned long ticks = 0;
    ktime_t delta;

    /*
     * Do a quick check without holding xtime_lock:
     */
    delta = ktime_sub(now, last_jiffies_update);-------------------last_jiffies_update是上一次jiffies更新的時間,類型為ktime_t。
    if (delta.tv64 < tick_period.tv64)-----------------------------tick_period = ktime_set(0, NSEC_PER_SEC / HZ),tick_period是一個tick時間值,單位是ktime_t。
        return;

    /* Reevalute with xtime_lock held */
    write_seqlock(&xtime_lock);

    delta = ktime_sub(now, last_jiffies_update);--------------------為什么重新做一次?!delta表示從上次更新jiffies到現在的時間差。 if (delta.tv64 >= tick_period.tv64) {

        delta = ktime_sub(delta, tick_period);
        last_jiffies_update = ktime_add(last_jiffies_update,
                        tick_period);

        /* Slow path for long timeouts */
        if (unlikely(delta.tv64 >= tick_period.tv64)) {-------------減去一個tick_period之后,delta還大於一個tick_period。
            s64 incr = ktime_to_ns(tick_period);

            ticks = ktime_divns(delta, incr);-----------------------計算剩余的ticks

            last_jiffies_update = ktime_add_ns(last_jiffies_update,
                               incr * ticks);
        }
        do_timer(++ticks);------------------------------------------如果delta沒有超過兩個tick,此時ticks為1;如果ticks超過兩個這時++ticks就包括第一次delta和第二次delta兩部分。 /* Keep the tick_next_period variable up to date */
        tick_next_period = ktime_add(last_jiffies_update, tick_period);
    }
    write_sequnlock(&xtime_lock);
}

 jiffies是由do_timer()更新的,下面是調用tick_do_update_jiffies64的幾條路徑:

tick_check_idle-->tick_check_nohz-->tick_nohz_update_jiffies-->tick_do_update_jiffies64
tick_nohz_idle_enter/tick_nohz_irq_exit-->tick_nohz_stop_sched_tick-->tick_do_update_jiffies64
tick_setup_sched_timer-->tick_sched_timer-->tick_do_update_jiffies64

  2.3 jiffies回繞周期

在一jiffies為5秒情況下,2^32-1個jiffies時間為4294967295。

jiffies回繞周期為:4294967295*5(ms)=248.5513480902778(天)。

2.4 jiffies和jiffies_64的關系

arch/arm/kernel/vmlinux.lds.S中定義了賦值:

#ifndef __ARMEB__
jiffies = jiffies_64;
#else
jiffies = jiffies_64 + 4;
#endif

 

這里使得 jiffies 只占用了 jiffies_64 的低 32 位。這里可能會有疑問,鏈接器腳本如何得知這兩個定義在別的文件里的變量?long long 型變量賦值給 long 型變量怎么不會發出警告? 


關於上面的疑問,涉及到鏈接器中的一個重要的概念: 
在目標文件內定義的符號可以在鏈接器腳本內賦值,此時該符號應試被定義為全局的。每個符號都對應了一個地址,在鏈接器中的賦值就是更改這個符號對應的地址。

所以,這和 C 語言中的賦值是完全不同的概念!C 中是賦值,鏈接器中是改變地址,所以不存在發出類型不相符的警告問題。 

 

 

 

在設備驅動程序中,通常使用 jiffies 變量。

因為在 32 位的系統中訪問 64 位的 jiffies_64 沒有直接訪問 jiffies 來得快,因為在 32 位系統中訪問 64 位變量需要進行兩次內存訪問,

而且在兩次內存訪問中可能不是原子的,且可能會被中斷,從而造成讀取數據的不正確。

 

對於需要訪問 jiffies_64 變量(一般在驅動程序中很少訪問 jiffies_64,通常只有內核核心代碼才會訪問),內核提供了 get_jiffies_64() 來訪問,該函數采用了加鎖機制,以防止讀取數據的不正確。

    seq_printf(m, "%lu.%02lu %lu.%02lu jiffies=%lu &jiffies=%p (u64)jiffies=%llu (u64)jiffies_64=%llu &jiffies_64=%p get_jiffies_64()=%llu\n",
            (unsigned long) uptime.tv_sec,
            (uptime.tv_nsec / (NSEC_PER_SEC / 100)),
            (unsigned long) idle.tv_sec,
            (idle.tv_nsec / (NSEC_PER_SEC / 100)), jiffies, &jiffies, (u64)jiffies, (u64)jiffies_64, &jiffies_64, get_jiffies_64());

 下面是上面代碼打印結果:

79.87 68.30 jiffies=4294923270 &jiffies=c0624168 (u64)jiffies=4294923270 (u64)jiffies_64=4294923270 &jiffies_64=c0624168 get_jiffies_64()=4294923270

337.74 320.52 jiffies=7548 &jiffies=c0624168 (u64)jiffies=7548 (u64)jiffies_64=4294974844 &jiffies_64=c0624168 get_jiffies_64()=4294974844

結論:

1. jiffies 的地址和 jiffies_64 是一樣的,不同的是在程序中體現的長度不同罷了。也可以看到,鏈接器對定義在目標文件中的全局變量(同名全局符號)是可見的。 

2. 32位的jiffies容易溢出,在開機300秒回繞。

3. 將jiffies強轉成unsigned long long也沒有獲取jiffies_64的值。

4. 在32位系統上,讀取64位變量需要兩次內存訪問,最好使用get_jiffies_64()避免兩次內存訪問數據不一致。

 

 

3. jiffies相關應用

3.1 jiffies和其他時間之間的轉換

include/linux/jiffies.h:
/*
* Convert various time units to each other: */ extern unsigned int jiffies_to_msecs(const unsigned long j); extern unsigned int jiffies_to_usecs(const unsigned long j); extern unsigned long msecs_to_jiffies(const unsigned int m); extern unsigned long usecs_to_jiffies(const unsigned int u); extern unsigned long timespec_to_jiffies(const struct timespec *value); extern void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value); extern unsigned long timeval_to_jiffies(const struct timeval *value); extern void jiffies_to_timeval(const unsigned long jiffies, struct timeval *value); extern clock_t jiffies_to_clock_t(unsigned long x); extern unsigned long clock_t_to_jiffies(unsigned long x); extern u64 jiffies_64_to_clock_t(u64 x); extern u64 nsecs_to_jiffies64(u64 n); extern unsigned long nsecs_to_jiffies(u64 n);

3.2 jiffies比較函數

include/linux/jiffies.h:

time_after(a,b)----------------------------------------使用這些宏定義可以防止jiffies的wrapping
time_before(a,b)
time_after_eq(a,b)    
time_before_eq(a,b)

#define time_is_before_jiffies(a) time_after(jiffies, a)------------------------將a和jiffies進行比較
#define time_is_after_jiffies(a) time_before(jiffies, a)
#define time_is_before_eq_jiffies(a) time_after_eq(jiffies, a)
#define time_is_after_eq_jiffies(a) time_before_eq(jiffies, a)

time_after為什么就放回繞功能?

#define typecheck(type,x) \
({    type __dummy; \---------------------------定義一個類型為type的__dummy變量
    typeof(x) __dummy2; \-----------------------在定義一個和x類型一樣的__dummy2
    (void)(&__dummy == &__dummy2); \------------兩種不同類型的指針進行比較,不同類型的指針比較會出現編譯錯誤。實現了類型檢查
    1; \----------------------------------------為1,表示檢查通過。
})


#define time_after(a,b)        \
    (typecheck(unsigned long, a) && \
     typecheck(unsigned long, b) && \----------兩個參數類型檢查
     ((long)((b) - (a)) < 0))------------------重點在於(long)類型的轉換

 

在理想的情況下,時間是可以不停增長的,后來的時間值一定比前面的值大。所以b-a一定小於0。然后計算機的世界不是一個理想的世界,

所有的值都有其位數限制的。在32位平台上,long的位數為32位。按照二進制補碼的表示方式,從0到0x7fffffff的區間,值是逐漸遞增的。

從0x80000000到0xFFFFFFFF這個區間,值是逐漸縮小的。

這就有4中情況:

1. a和b都在0到0x7FFFFFFF之間:

a若在b之后發生,則a的值大於b。那么(long)b-(long)a<0。

2. a和b都在0x80000000到0xFFFFFFFF之間:

a若在b之后發生,b為較大的負數,a為較小的負數,那么(long)b-(long)a<0。

3. b在0到0x7FFFFFFF之間,而a在0x80000000到0xFFFFFFFF之間:

a為負數。b-a,相當於b+(-a)。只要a與b之間的絕對差值小於或等於0x80000000,則b+(-a)仍然為負數。

4. b在0x80000000到0xFFFFFFFF之間,而a在0到0x7FFFFFFF之間:

b為負數,b-a等於b+(-a)。同樣在a與b之間的絕對差值小於或等於0x80000000,則b+(-a)仍然為負數。

 總結這四種情況,在a與b的絕對值相差不到0x80000000時,這個宏是正確的。而在利用jiffies作為時間度量和比較單位時,時間差並不會太大。

 3.3 jiffies是否suspend補償

在2-3之間有一個8.55秒的suspend。

分析如下:

1-2:將jiffies差值轉換成秒,可以看出和uptime差值一樣。

2-3:jiffies差值轉換成秒比uptime少了8.77秒,約等於8.55,這中間包括進入suspend退出suspend耗時。

3-4:jiffies差值和uptime差值兩者一致。

結論:所以jiffies沒有包含suspend時間。

 3.4 jiffies與低精度timer

 低精度timer的單位是jiffies,所以低精度timer的精度也依賴於jiffies的大小。

創建修改低精度timer的時候 __mod_timer-->internal_add_timer,將當前timer加入到低精度timer的時間輪中。

 

參考資料:

1.Linux內核中的jiffies及其作用介紹及jiffies等相關函數詳解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM