<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Linux時間子系統(tǒng)之:時鐘源

          共 6244字,需瀏覽 13分鐘

           ·

          2022-12-30 17:53

          clock source 用于為 Linux 內(nèi)核提供一個時間基線,如果你用 Linux 的 date 命令獲取當(dāng)前時間,內(nèi)核會讀取當(dāng)前的 clock source,轉(zhuǎn)換并返回合適的時間單位給用戶空間。

          在硬件層,它通常實現(xiàn)為一個由固定時鐘頻率驅(qū)動的計數(shù)器,計數(shù)器只能單調(diào)地增加,直到溢出為止。時鐘源是內(nèi)核計時的基礎(chǔ),系統(tǒng)啟動時,內(nèi)核通過硬件RTC獲得當(dāng)前時間,在這以后,在大多數(shù)情況下,內(nèi)核通過選定的時鐘源更新實時時間信息(墻上時間),而不再讀取RTC的時間。

          本節(jié)的內(nèi)核代碼樹基于V3.4.10。

          struct clocksource結(jié)構(gòu)

          內(nèi)核用一個clocksource結(jié)構(gòu)對真實的時鐘源進(jìn)行軟件抽象,現(xiàn)在我們從clock source的數(shù)據(jù)結(jié)構(gòu)開始,它的定義如下:

          struct clocksource {
           /*
            * Hotpath data, fits in a single cache line when the
            * clocksource itself is cacheline aligned.
            */

           cycle_t (*read)(struct clocksource *cs);
           cycle_t cycle_last;
           cycle_t mask;
           u32 mult;
           u32 shift;
           u64 max_idle_ns;
           u32 maxadj;
          #ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
           struct arch_clocksource_data archdata;
          #endif
           
           const char *name;
           struct list_head list;
           int rating;
           int (*enable)(struct clocksource *cs);
           void (*disable)(struct clocksource *cs);
           unsigned long flags;
           void (*suspend)(struct clocksource *cs);
           void (*resume)(struct clocksource *cs);
           
           /* private: */
          #ifdef CONFIG_CLOCKSOURCE_WATCHDOG
           /* Watchdog related data, used by the framework */
           struct list_head wd_list;
           cycle_t cs_last;
           cycle_t wd_last;
          #endif
          } ____cacheline_aligned;

          我們只關(guān)注clocksource中的幾個重要的字段。

          1 rating:時鐘源的精度

          同一個設(shè)備下,可以有多個時鐘源,每個時鐘源的精度由驅(qū)動它的時鐘頻率決定,比如一個由10MHz時鐘驅(qū)動的時鐘源,他的精度就是100nS。clocksource結(jié)構(gòu)中有一個rating字段,代表著該時鐘源的精度范圍,它的取值范圍如下:

          • 1--99:不適合于用作實際的時鐘源,只用于啟動過程或用于測試;
          • 100--199:基本可用,可用作真實的時鐘源,但不推薦;
          • 200--299:精度較好,可用作真實的時鐘源;
          • 300--399:很好,精確的時鐘源;
          • 400--499:理想的時鐘源,如有可能就必須選擇它作為時鐘源;

          1.2 read回調(diào)函數(shù)

          時鐘源本身不會產(chǎn)生中斷,要獲得時鐘源的當(dāng)前計數(shù),只能通過主動調(diào)用它的read回調(diào)函數(shù)來獲得當(dāng)前的計數(shù)值,注意這里只能獲得計數(shù)值,也就是所謂的cycle,要獲得相應(yīng)的時間,必須要借助clocksource的mult和shift字段進(jìn)行轉(zhuǎn)換計算。

          1.3 mult和shift字段

          因為從clocksource中讀到的值是一個cycle計數(shù)值,要轉(zhuǎn)換為時間,我們必須要知道驅(qū)動clocksource的時鐘頻率F,一個簡單的計算就可以完成:

          t = cycle / F;

          可是clocksource并沒有保存時鐘的頻率F,因為使用上面的公式進(jìn)行計算,需要使用浮點運算,這在內(nèi)核中是不允許的。

          因此,內(nèi)核使用了另外一個變通的辦法,根據(jù)時鐘的頻率和期望的精度,事先計算出兩個輔助常數(shù)mult和shift,然后使用以下公式進(jìn)行cycle和t的轉(zhuǎn)換:

          t = (cycle * mult) >> shift;

          只要我們保證:

          F = (1 << shift) / mult;

          內(nèi)核內(nèi)部使用64位進(jìn)行該轉(zhuǎn)換計算:

          static inline s64 
          clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift)
          {
              return ((u64) cycles * mult) >> shift;
          }

          從轉(zhuǎn)換精度考慮,mult 的值是越大越好,但是為了計算過程不發(fā)生溢出,mult 的值又不能取得過大。為此內(nèi)核假設(shè) cycle 計數(shù)值被轉(zhuǎn)換后的最大時間值:10分鐘(600秒),主要的考慮是 CPU 進(jìn)入 IDLE 狀態(tài)后,時間信息不會被更新,只要在10分鐘內(nèi)退出IDLE,clocksource 的 cycle 計數(shù)值就可以被正確地轉(zhuǎn)換為相應(yīng)的時間,然后系統(tǒng)的時間信息可以被正確地更新。

          當(dāng)然最后的結(jié)果不一定是10分鐘,它由 clocksource_max_deferment 進(jìn)行計算,并保存 max_idle_ns 字段中,tickless 的代碼要考慮這個值,以防止在 NO_HZ 配置環(huán)境下,系統(tǒng)保持 IDLE 狀態(tài)的時間過長。在這樣,由 10 分鐘這個假設(shè)的時間值,我們可以推算出合適的 mult 和 shift 值。

          2. clocksource的注冊和初始化

          通常,clocksource要在初始化階段通過 clocksource_register_hz 函數(shù)通知內(nèi)核它的工作時鐘的頻率,調(diào)用的過程如下:

          由上圖可見,最終大部分工作會轉(zhuǎn)由 __clocksource_register_scale 完成,該函數(shù)首先完成對 mult 和 shift 值的計算,然后根據(jù) mult 和 shift 值,最終通過 clocksource_max_deferment 獲得該 clocksource 可接受的最大 IDLE 時間,并記錄在 clocksource 的 max_idle_ns 字段中。clocksource_enqueue 函數(shù)負(fù)責(zé)按 clocksource 的 rating 的大小,把該 clocksource 按順序掛在全局鏈表 clocksource_list 上,rating 值越大,在鏈表上的位置越靠前。

          每次新的 clocksource 注冊進(jìn)來,都會觸發(fā) clocksource_select 函數(shù)被調(diào)用,它按照 rating 值選擇最好的 clocksource,并記錄在全局變量 curr_clocksource 中,然后通過 timekeeping_notify 函數(shù)通知 timekeeping,當(dāng)前clocksource 已經(jīng)變更,關(guān)于 timekeeping,我將會在后續(xù)的博文中闡述。

          3. clocksource watchdog

          系統(tǒng)中可能同時會注冊對個 clocksource,各個 clocksource 的精度和穩(wěn)定性各不相同,為了篩選這些注冊的 clocksource,內(nèi)核啟用了一個定時器用于監(jiān)控這些 clocksource 的性能,定時器的周期設(shè)為0.5秒:

          #define WATCHDOG_INTERVAL (HZ >> 1)
          #define WATCHDOG_THRESHOLD (NSEC_PER_SEC >> 4)

          當(dāng)有新的 clocksource 被注冊時,除了會掛在全局鏈表 clocksource_list 外,還會同時掛在一個 watchdog 鏈表上:watchdog_list

          定時器周期性地(0.5秒)檢查 watchdog_list 上的 clocksource,WATCHDOG_THRESHOLD 的值定義為0.0625秒。如果在0.5秒內(nèi),clocksource 的偏差大于這個值就表示這個 clocksource 是不穩(wěn)定的,定時器的回調(diào)函數(shù)通過 clocksource_watchdog_kthread 線程標(biāo)記該 clocksource,并把它的 rate 修改為0,表示精度極差。

          4. 建立clocksource的簡要過程

          在系統(tǒng)的啟動階段,內(nèi)核注冊了一個基于 jiffies 的 clocksource,代碼位于 kernel/time/jiffies.c:

          struct clocksource clocksource_jiffies = {
           .name  = "jiffies",
           .rating  = 1/* lowest valid rating*/
           .read  = jiffies_read,
           .mask  = 0xffffffff/*32bits*/
           .mult  = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */
           .shift  = JIFFIES_SHIFT,
          };
          ......
           
          static int __init init_jiffies_clocksource(void)
          {
           return clocksource_register(&clocksource_jiffies);
          }
           
          core_initcall(init_jiffies_clocksource);

          它的精度只有 1/HZ 秒,rating 值為1,如果平臺的代碼沒有提供定制的 clocksource_default_clock 函數(shù),它將返回該 clocksource:

          struct clocksource * __init __weak clocksource_default_clock(void)
          {
           return &clocksource_jiffies;
          }

          然后,在初始化的后段,clocksource 的代碼會把全局變量 curr_clocksource 設(shè)置為上述的 clocksource:

          static int __init clocksource_done_booting(void)
          {
                  ......
           curr_clocksource = clocksource_default_clock();
                  ......
           finished_booting = 1;
                  ......
           clocksource_select();
                  ......
           return 0;
          }
          fs_initcall(clocksource_done_booting);

          當(dāng)然,如果平臺級的代碼在初始化時也會注冊真正的硬件 clocksource,所以經(jīng)過 clocksource_select 函數(shù)后,curr_clocksource 將會被設(shè)為最合適的 clocksource。如果 clocksource_select 函數(shù)認(rèn)為需要切換更好的時鐘源,它會通過 timekeeping_notify 通知 timekeeping 系統(tǒng),使用新的 clocksource 進(jìn)行時間計數(shù)和更新操作。

          原文地址:https://blog.csdn.net/DroidPhone/article/details/7975694

          瀏覽 79
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  第一页在线| 欧美AAAAA| 蜜乳AV一区二区三区 | 熟女一区二区三区视频 | 免费高清在线观看黄色视频 |