W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎勵
內(nèi)核通過定時(shí)器中斷來跟蹤時(shí)間的流動. 中斷在第 10 章詳細(xì)描述.
定時(shí)器中斷由系統(tǒng)定時(shí)硬件以規(guī)律地間隔產(chǎn)生; 這個(gè)間隔在啟動時(shí)由內(nèi)核根據(jù) HZ 值來編程, HZ 是一個(gè)體系依賴的值, 在 中定義或者它所包含的一個(gè)子平臺文件中. 在發(fā)布的內(nèi)核源碼中的缺省值在真實(shí)硬件上從 50 到 1200 嘀噠每秒, 在軟件模擬器中往下到 24. 大部分平臺運(yùn)行在 100 或者 1000 中斷每秒; 流行的 x86 PC 缺省是 1000, 盡管它在以前版本上(向上直到并且包括 2.4)常常是 100. 作為一個(gè)通用的規(guī)則, 即便如果你知道 HZ 的值, 在編程時(shí)你應(yīng)當(dāng)從不依賴這個(gè)特定值.
可能改變 HZ 的值, 對于那些要系統(tǒng)有一個(gè)不同的時(shí)鐘中斷頻率的人. 如果你在頭文件中改變 HZ 的值, 你需要使用新的值重編譯內(nèi)核和所有的模塊. 如果你愿意付出額外的時(shí)間中斷的代價(jià)來獲得你的目標(biāo), 你可能想提升 HZ 來得到你的異步任務(wù)的更細(xì)粒度的精度. 實(shí)際上, 提升 HZ 到 1000 在使用 2.4 或 2.2 內(nèi)核版本的 x86 工業(yè)系統(tǒng)中是相當(dāng)普遍的. 但是, 對于當(dāng)前版本, 最好的方法是保持 HZ 的缺省值, 由于我們完全信任內(nèi)核開發(fā)者, 他們肯定已經(jīng)選擇了最好的值. 另外, 一些內(nèi)部計(jì)算當(dāng)前實(shí)現(xiàn)為只為從 12 到 1535 范圍的 HZ (見 和 RFC-1589).
每次發(fā)生一個(gè)時(shí)鐘中斷, 一個(gè)內(nèi)核計(jì)數(shù)器的值遞增. 這個(gè)計(jì)數(shù)器在系統(tǒng)啟動時(shí)初始化為 0, 因此它代表從最后一次啟動以來的時(shí)鐘嘀噠的數(shù)目. 這個(gè)計(jì)數(shù)器是一個(gè) 64-位 變量( 即便在 32-位的體系上)并且稱為 jiffies_64. 但是, 驅(qū)動編寫者正常地存取 jiffies 變量, 一個(gè) unsigned long, 或者和 jiffies_64 是同一個(gè)或者它的低有效位. 使用 jiffies 常常是首選, 因?yàn)樗? 并且再所有的體系上存取 64-位的 jiffies_64 值不必要是原子的.
除了低精度的內(nèi)核管理的 jiffy 機(jī)制, 一些 CPU 平臺特有一個(gè)高精度的軟件可讀的計(jì)數(shù)器. 盡管它的實(shí)際使用有些在各個(gè)平臺不同, 它有時(shí)是一個(gè)非常有力的工具.
這個(gè)計(jì)數(shù)器和來讀取它的實(shí)用函數(shù)位于 , 盡管你會常常只是包含 , 它會自動地將 jiffies.h 拉進(jìn)來. 不用說, jiffies 和 jiffies_64 必須當(dāng)作只讀的.
無論何時(shí)你的代碼需要記住當(dāng)前的 jiffies 值, 可以簡單地存取這個(gè) unsigned long 變量, 它被聲明做 volatile 來告知編譯器不要優(yōu)化內(nèi)存讀. 你需要讀取當(dāng)前的計(jì)數(shù)器, 無論何時(shí)你的代碼需要計(jì)算一個(gè)將來的時(shí)間戳, 如下面例子所示:
#include <linux/jiffies.h>
unsigned long j, stamp_1, stamp_half, stamp_n;
j = jiffies; /* read the current value */
stamp_1 = j + HZ; /* 1 second in the future */
stamp_half = j + HZ/2; /* half a second */
stamp_n = j + n * HZ / 1000; /* n milliseconds */
這個(gè)代碼對于 jiffies 回繞沒有問題, 只要不同的值以正確的方式進(jìn)行比較. 盡管在 32-位 平臺上當(dāng) HZ 是 1000 時(shí), 計(jì)數(shù)器只是每 50 天回繞一次, 你的代碼應(yīng)當(dāng)準(zhǔn)備面對這個(gè)事件. 為比較你的被緩存的值( 象上面的 stamp_1 ) 和當(dāng)前值, 你應(yīng)當(dāng)使用下面一個(gè)宏定義:
#include <linux/jiffies.h>
int time_after(unsigned long a, unsigned long b);
int time_before(unsigned long a, unsigned long b);
int time_after_eq(unsigned long a, unsigned long b);
int time_before_eq(unsigned long a, unsigned long b);
第一個(gè)當(dāng) a, 作為一個(gè) jiffies 的快照, 代表 b 之后的一個(gè)時(shí)間時(shí), 取值為真, 第二個(gè)當(dāng) 時(shí)間 a 在時(shí)間 b 之前時(shí)取值為真, 以及最后 2 個(gè)比較"之后或相同"和"之前或相同". 這個(gè)代碼工作通過轉(zhuǎn)換這個(gè)值為 signed long, 減它們, 并且比較結(jié)果. 如果你需要以一種安全的方式知道 2 個(gè) jiffies 實(shí)例之間的差, 你可以使用同樣的技巧: diff = (long)t2 - (long)t1;.
你可以轉(zhuǎn)換一個(gè) jiffies 差為毫秒, 一般地通過:
msec = diff * 1000 / HZ;
有時(shí), 但是, 你需要與用戶空間程序交換時(shí)間表示, 它們打算使用 struct timeval 和 struct timespec 來表示時(shí)間. 這 2 個(gè)結(jié)構(gòu)代表一個(gè)精確的時(shí)間量, 使用 2 個(gè)成員: seconds 和 microseconds 在舊的流行的 struct timeval 中使用, seconds 和 nanoseconds 在新的 struct timespec 中使用. 內(nèi)核輸出 4 個(gè)幫助函數(shù)來轉(zhuǎn)換以 jiffies 表達(dá)的時(shí)間值, 到和從這些結(jié)構(gòu):
#include <linux/time.h>
unsigned long timespec_to_jiffies(struct timespec *value);
void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
unsigned long timeval_to_jiffies(struct timeval *value);
void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);
存取這個(gè) 64-位 jiffy 計(jì)數(shù)值不象存取 jiffies 那樣直接. 而在 64-位 計(jì)算機(jī)體系上, 這 2 個(gè)變量實(shí)際上是一個(gè), 存取這個(gè)值對于 32-位 處理器不是原子的. 這意味著你可能讀到錯誤的值如果這個(gè)變量的兩半在你正在讀取它們時(shí)被更新. 極不可能你會需要讀取這個(gè) 64-位 計(jì)數(shù)器, 但是萬一你需要, 你會高興地得知內(nèi)核輸出了一個(gè)特別地幫助函數(shù), 為你完成正確地加鎖:
#include <linux/jiffies.h>
u64 get_jiffies_64(void);
在上面的原型中, 使用了 u64 類型. 這是一個(gè)定義在 中的類型, 在 11 章中討論, 并且表示一個(gè) unsigned 64-位 類型.
如果你在奇怪 32-位 平臺如何同時(shí)更新 32-位 和 64-位 計(jì)數(shù)器, 讀你的平臺的連接腳本( 查找一個(gè)文件, 它的名子匹配 valinux.lds). 在那里, jiffies 符號被定義來存取這個(gè) 64-位 值的低有效字, 根據(jù)平臺是小端或者大端. 實(shí)際上, 同樣的技巧也用在 64-位 平臺上, 因此這個(gè) unsigned long 和 u64 變量在同一個(gè)地址被存取.
最后, 注意實(shí)際的時(shí)鐘頻率幾乎完全對用戶空間隱藏. 宏 HZ 一直擴(kuò)展為 100 當(dāng)用戶空間程序包含 param.h, 并且每個(gè)報(bào)告給用戶空間的計(jì)數(shù)器都對應(yīng)地被轉(zhuǎn)換. 這應(yīng)用于 clock(3), times(2), 以及任何相關(guān)的函數(shù). 對 HZ 值的用戶可用的唯一證據(jù)是時(shí)鐘中斷多快發(fā)生, 如在 /proc/interrupts 所顯示的. 例如, 你可以獲得 HZ, 通過用在 /proc/uptime 中報(bào)告的系統(tǒng) uptime 除這個(gè)計(jì)數(shù)值.
如果你需要測量非常短時(shí)間間隔, 或者你需要非常高精度, 你可以借助平臺依賴的資源, 一個(gè)要精度不要移植性的選擇.
在現(xiàn)代處理器中, 對于經(jīng)驗(yàn)性能數(shù)字的迫切需求被大部分 CPU 設(shè)計(jì)中內(nèi)在的指令定時(shí)不確定性所阻礙, 這是由于緩存內(nèi)存, 指令調(diào)度, 以及分支預(yù)測引起. 作為回應(yīng), CPU 制造商引入一個(gè)方法來計(jì)數(shù)時(shí)鐘周期, 作為一個(gè)容易并且可靠的方法來測量時(shí)間流失. 因此, 大部分現(xiàn)代處理器包含一個(gè)計(jì)數(shù)器寄存器, 它在每個(gè)時(shí)鐘周期固定地遞增一次. 現(xiàn)在, 資格時(shí)鐘計(jì)數(shù)器是唯一可靠的方法來進(jìn)行高精度的時(shí)間管理任務(wù).
細(xì)節(jié)每個(gè)平臺不同: 這個(gè)寄存器可以或者不可以從用戶空間可讀, 它可以或者不可以寫, 并且它可能是 64 或者 32 位寬. 在后一種情況, 你必須準(zhǔn)備處理溢出, 就象我們處理 jiffy 計(jì)數(shù)器一樣. 這個(gè)寄存器甚至可能對你的平臺來說不存在, 或者它可能被硬件設(shè)計(jì)者在一個(gè)外部設(shè)備實(shí)現(xiàn), 如果 CPU 缺少這個(gè)特性并且你在使用一個(gè)特殊用途的計(jì)算機(jī).
無論是否寄存器可以被清零, 我們強(qiáng)烈不鼓勵復(fù)位它, 即便當(dāng)硬件允許時(shí). 畢竟, 在任何給定時(shí)間你可能不是這個(gè)計(jì)數(shù)器的唯一用戶; 在一些支持 SMP 的平臺上, 例如, 內(nèi)核依賴這樣一個(gè)計(jì)數(shù)器來在處理器之間同步. 因?yàn)槟憧梢砸恢睖y量各個(gè)值的差, 只要差沒有超過溢出時(shí)間, 你可以通過修改它的當(dāng)前值來做這個(gè)事情不用聲明獨(dú)自擁有這個(gè)寄存器.
最有名的計(jì)數(shù)器寄存器是 TSC ( timestamp counter), 在 x86 處理器中隨 Pentium 引入的并且在所有從那之后的 CPU 中出現(xiàn) -- 包括 x86_64 平臺. 它是一個(gè) 64-位 寄存器計(jì)數(shù) CPU 的時(shí)鐘周期; 它可從內(nèi)核和用戶空間讀取.
在包含了 (一個(gè) x86-特定的頭文件, 它的名子代表"machine-specific registers"), 你可使用一個(gè)這些宏:
rdtsc(low32,high32);
rdtscl(low32);
rdtscll(var64);
第一個(gè)宏自動讀取 64-位 值到 2 個(gè) 32-位 變量; 下一個(gè)("read low half") 讀取寄存器的低半部到一個(gè) 32-位 變量, 丟棄高半部; 最后一個(gè)讀 64-位 值到一個(gè) long long 變量, 由此得名. 所有這些宏存儲數(shù)值到它們的參數(shù)中.
對大部分的 TSC 應(yīng)用, 讀取這個(gè)計(jì)數(shù)器的的低半部足夠了. 一個(gè) 1-GHz 的 CPU 只在每 4.2 秒溢出一次, 因此你不會需要處理多寄存器變量, 如果你在使用的時(shí)間流失確定地使用更少時(shí)間. 但是, 隨著 CPU 頻率不斷上升以及定時(shí)需求的提高, 將來你會幾乎可能需要常常讀取 64-位 計(jì)數(shù)器.
作為一個(gè)只使用寄存器低半部的例子, 下面的代碼行測量了指令自身的執(zhí)行:
unsigned long ini, end;
rdtscl(ini); rdtscl(end);
printk("time lapse: %li\n", end - ini);
一些其他的平臺提供相似的功能, 并且內(nèi)核頭文件提供一個(gè)體系獨(dú)立的功能, 你可用來代替 rdtsc. 它稱為 get_cycles, 定義在 ( 由 包含). 它的原型是:
#include <linux/timex.h>
cycles_t get_cycles(void);
這個(gè)函數(shù)為每個(gè)平臺定義, 并且它一直返回 0 在沒有周期-計(jì)數(shù)器寄存器的平臺上. cycles_t 類型是一個(gè)合適的 unsigned 類型來持有讀到的值.
不論一個(gè)體系獨(dú)立的函數(shù)是否可用, 我們最好利用機(jī)會來展示一個(gè)內(nèi)聯(lián)匯編代碼的例子. 為此, 我們實(shí)現(xiàn)一個(gè) rdtscl 函數(shù)給 MIPS 處理器, 它與在 x86 上同樣的方式工作.
拖尾的 nop 指令被要求來阻止編譯器在 mfc0 之后馬上存取指令中的目標(biāo)寄存器. 這種內(nèi)部鎖在 RISC 處理器中是典型的, 并且編譯器仍然可以在延遲時(shí)隙中調(diào)度有用的指令. 在這個(gè)情況中, 我們使用 nop 因?yàn)閮?nèi)聯(lián)匯編對編譯器是一個(gè)黑盒并且不會進(jìn)行優(yōu)化.[26]
#define rdtscl(dest) \
__asm__ __volatile__("mfc0 %0,$9; nop" : "=r" (dest))
有這個(gè)宏在, MIPS 處理器可以執(zhí)行同樣的代碼, 如同前面為 x86 展示的一樣的代碼.
使用 gcc 內(nèi)聯(lián)匯編, 通用寄存器的分配留給編譯器. 剛剛展示的這個(gè)宏使用 %0 作為"參數(shù) 0"的一個(gè)占位符, 之后它被指定為"任何用作輸出( = )的寄存器( r )". 這個(gè)宏還聲明輸出寄存器必須對應(yīng) C 表達(dá)式 dest. 內(nèi)聯(lián)函數(shù)的語法是非常強(qiáng)大但是有些復(fù)雜, 特別對于那些有限制每個(gè)寄存器可以做什么的體系上(就是說, x86 家族). 這個(gè)用法在 gcc 文檔中描述, 常常在 info 文檔目錄樹中有.
本節(jié)已展示的這個(gè)簡短的 C-代碼片段已在一個(gè) K7-級 x86 處理器 和一個(gè) MIPS VR4181 ( 使用剛剛描述過的宏 )上運(yùn)行. 前者報(bào)告了一個(gè) 11 個(gè)時(shí)鐘嘀噠的時(shí)間流失而后者只是 2 個(gè)時(shí)鐘嘀噠. 小的數(shù)字是期望的, 因?yàn)?RISC 處理器常常每個(gè)時(shí)鐘周期執(zhí)行一條指令.
有另一個(gè)關(guān)于時(shí)戳計(jì)數(shù)器的事情值得知道: 它們在一個(gè) SMP 系統(tǒng)中不必要跨處理器同步. 為保證得到一個(gè)一致的值, 你應(yīng)當(dāng)為查詢這個(gè)計(jì)數(shù)器的代碼禁止搶占.
[26]?我們在 MIPS 上建立這例子, 因?yàn)榇蟛糠值?MIPS 處理器特有一個(gè) 32-位 計(jì)數(shù)器作為它們內(nèi)部"協(xié)處理器 0"的寄存器 9. 為存取這個(gè)寄存器, 僅僅從內(nèi)核空間可讀, 你可以定義下列的宏來執(zhí)行一條"從協(xié)處理器 0 轉(zhuǎn)移"的匯編指令:
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: