linux HZ
Linux核心幾個(gè)重要跟時(shí)間有關(guān)的名詞或變數(shù),底下將介紹HZ、tick與jiffies。
HZ
Linux核心每隔固定周期會(huì)發(fā)出timer interrupt (IRQ 0),HZ是用來(lái)定義每一秒有幾次timer interrupts。舉例來(lái)說(shuō),HZ為1000,代表每秒有1000次timer interrupts。 HZ可在編譯核心時(shí)設(shè)定,如下所示(以核心版本2.6.20-15為例):
adrian@adrian-desktop:~$ cd /usr/src/linux
adrian@adrian-desktop:/usr/src/linux$ make menuconfig
Processor type and features ---》 Timer frequency (250 HZ) ---》
其 中HZ可設(shè)定100、250、300或1000。以小弟的核心版本預(yù)設(shè)值為250。
小實(shí)驗(yàn)
觀察/proc/interrupt的 timer中斷次數(shù),并于一秒后再次觀察其值。理論上,兩者應(yīng)該相差250左右。
adrian@adrian-desktop:~$ cat /proc/interrupts | grep timer && sleep 1 && cat /proc/interrupts | grep timer
0: 9309306 IO-APIC-edge timer
0: 9309562 IO-APIC-edge timer
上 面四個(gè)欄位分別為中斷號(hào)碼、CPU中斷次數(shù)、PIC與裝置名稱。
要檢查系統(tǒng)上HZ的值是什么,就執(zhí)行命令
cat /boot/config-`uname -r` | grep ‘^CONFIG_HZ=’
還可以直接更改文件
os/linux/include/asm-cris/param.h?
Tick
Tick是HZ的倒數(shù),意即timer interrupt每發(fā)生一次中斷的時(shí)間。如HZ為250時(shí),tick為4毫秒(millisecond)。
Jiffies
Jiffies為L(zhǎng)inux核心變數(shù)(32位元變數(shù),unsigned long),它被用來(lái)紀(jì)錄系統(tǒng)自開(kāi)幾以來(lái),已經(jīng)過(guò)多少的tick。每發(fā)生一次timer interrupt,Jiffies變數(shù)會(huì)被加一。值得注意的是,Jiffies于系統(tǒng)開(kāi)機(jī)時(shí),并非初始化成零,而是被設(shè)為-300*HZ (arch/i386/kernel/time.c),即代表系統(tǒng)于開(kāi)機(jī)五分鐘后,jiffies便會(huì)溢位。那溢位怎么辦?事實(shí)上,Linux核心定義幾 個(gè)macro(timer_after、time_after_eq、time_before與time_before_eq),即便是溢位,也能藉由這 幾個(gè)macro正確地取得jiffies的內(nèi)容。
另外,80x86架構(gòu)定義一個(gè)與jiffies相關(guān)的變數(shù)jiffies_64 ,此變數(shù)64位元,要等到此變數(shù)溢位可能要好幾百萬(wàn)年。因此要等到溢位這刻發(fā)生應(yīng)該很難吧。那如何經(jīng)由jiffies_64取得jiffies資訊呢?事 實(shí)上,jiffies被對(duì)應(yīng)至jiffies_64最低的32位元。因此,經(jīng)由jiffies_64可以完全不理會(huì)溢位的問(wèn)題便能取得jiffies。
HZ的設(shè)定:
#make menuconfig
processor type and features---》Timer frequency (250 HZ)---》
HZ的不同值會(huì)影響timer (節(jié)拍)中斷的頻率
2.2 jiffies及其溢出
全局變量jiffies取值為自操作系統(tǒng)啟動(dòng)以來(lái)的時(shí)鐘滴答的數(shù)目,在頭文 件中定義,數(shù)據(jù)類型為unsigned long volatile (32位無(wú)符號(hào)長(zhǎng)整型)。關(guān)于 jiffies為什么要采用volatile來(lái)限定,可參考《關(guān)于volatile和jiffies.txt》。
jiffies轉(zhuǎn)換為秒可采用 公式:(jiffies/HZ)計(jì)算,將秒轉(zhuǎn)換為jiffies可采用公式:(seconds*HZ)計(jì)算。
當(dāng)時(shí)鐘中斷發(fā)生時(shí),jiffies 值就加1。因此連續(xù)累加一年又四個(gè)多月后就會(huì)溢出(假定HZ=100,1個(gè)jiffies等于1/100秒,jiffies可記錄的最大秒數(shù)為 (2^32 -1)/100=42949672.95秒,約合497天或1.38年),即當(dāng)取值到達(dá)最大值時(shí)繼續(xù)加1,就變?yōu)榱?。
在 Vxworks操作系統(tǒng)中,定義HZ的值為60,因此連續(xù)累加兩年又三個(gè)多月后也將溢出(jiffies可記錄的最大秒數(shù)為約合2.27年)。如果在 Vxworks操作系統(tǒng)上的應(yīng)用程序?qū)iffies的溢出沒(méi)有加以充分考慮,那么在連續(xù)運(yùn)行兩年又三個(gè)多月后,這些應(yīng)用程序還能夠穩(wěn)定運(yùn)行嗎?
下 面我們來(lái)考慮jiffies的溢出,我們將從以下幾個(gè)方面來(lái)闡述:
。 無(wú)符號(hào)整型溢出的具體過(guò)程
。 jiffies溢出造成程序邏輯 出錯(cuò)
。 Linux內(nèi)核如何來(lái)防止jiffies溢出
。 time_after等比較時(shí)間先/后的宏背后的原理
。 代碼中 使用time_after等比較時(shí)間先/后的宏
3. 無(wú)符號(hào)整型溢出的具體過(guò)程
我們首先來(lái)看看無(wú)符號(hào)長(zhǎng)整型(unsigned long)溢出的具體過(guò)程。實(shí)際上,無(wú)符號(hào)整型的溢出過(guò) 程都很類似。為了更具體地描述無(wú)符號(hào)長(zhǎng)整型溢出的過(guò)程,我們以8位無(wú)符號(hào)整型為例來(lái)加以說(shuō)明。
8位無(wú)符號(hào)整型從0開(kāi)始持續(xù)增長(zhǎng),當(dāng)增長(zhǎng)到最大值 255時(shí),繼續(xù)加1將變?yōu)?,然后該過(guò)程周而復(fù)始:
0, 1, 2, 。.., 253, 254, 255,
0, 1, 2, 。.., 253, 254, 255,
。..
4. jiffies溢出造成程序邏輯出錯(cuò)
下面,通過(guò)一個(gè)例子來(lái)看jiffies的溢出。
例4-1:jiffies溢出造成程 序邏輯出錯(cuò)
unsigned long timeout = jiffies + HZ/2; /* timeout in 0.5s */
/* do some work 。.. */
do_somework();
/* then see whether we took too long */
if (timeout 》 jiffies) {
/* we did not time out, call no_timeout_handler() 。.. */
no_timeout_handler();
} else {
/* we timed out, call timeout_handler() 。.. */
timeout_handler();
}
本 例的目的是從當(dāng)前時(shí)間起,如果在0.5秒內(nèi)執(zhí)行完do_somework(),則調(diào)用no_timeout_handler()。如果在0.5秒后執(zhí)行完 do_somework(),則調(diào)用timeout_handler()。
我們來(lái)看看本例中一種可能的溢出情況,即在設(shè)置timeout并執(zhí)行 do_somework()后,jiffies值溢出,取值為0。設(shè)在設(shè)置timeout后,timeout的值臨近無(wú)符號(hào)長(zhǎng)整型的最大值,即小于 2^32-1。設(shè)執(zhí)行do_somework()花費(fèi)了1秒,那么代碼應(yīng)當(dāng)調(diào)用timeout_handler()。但是當(dāng)jiffies值溢出取值為0 后,條件timeout 》 jiffies成立,jiffies值(等于0)小于timeout(臨近但小于2^32-1),盡管從邏輯上講 jiffies值要比timeout大。但最終代碼調(diào)用的是no_timeout_handler(),而不是timeout_handler()。
5. Linux內(nèi)核如何來(lái)防止jiffies溢出
Linux內(nèi)核中提供了以下四個(gè)宏,可有效地解決由于jiffies溢出而造成程序邏 輯出錯(cuò)的情況。下面是從Linux Kernel 2.6.7版本中摘取出來(lái)的代碼:
/*
* These inlines deal with timer wrapping correctly. You are
* strongly encouraged to use them
* 1. Because people otherwise forget
* 2. Because if the timer wrap changes in future you won‘t have to
* alter your driver code.
*
* time_after(a,b) returns true if the time a is after time b.
*
* Do this with “《0” and “》=0” to only test the sign of the result. A
* good compiler would generate better code (and a really good compiler
* wouldn’t care)。 Gcc is currently neither.
*/
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
?。ǎ╨ong)(b) - (long)(a) 《 0))
#define time_before(a,b) time_after(b,a)
#define time_after_eq(a,b) \
?。╰ypecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
?。ǎ╨ong)(a) - (long)(b) 》= 0))
#define time_before_eq(a,b) time_after_eq(b,a)
在宏time_after中,首先確保兩個(gè)輸入參數(shù)a和b的數(shù)據(jù)類型為unsigned long,然后才執(zhí)行實(shí)際的比較。這是以后編碼中應(yīng)當(dāng)注意 的地方。
6. time_after等比較時(shí)間先后的宏背后的原理
那么,上述time_after等比較時(shí)間先/后的宏為什么能夠解決 jiffies溢出造成的錯(cuò)誤情況呢?
我們?nèi)匀灰?位無(wú)符號(hào)整型(unsigned char)為例來(lái)加以說(shuō)明。仿照上面的 time_after宏,我們可以給出簡(jiǎn)化的8位無(wú)符號(hào)整型對(duì)應(yīng)的after宏:
#define uc_after(a, b) ((char)(b) - (char)(a) 《 0)
設(shè)a和b的數(shù)據(jù)類型為unsigned char,b為臨近8位無(wú)符號(hào)整型最大值附近的一個(gè)固定值254,下面給出隨著a(設(shè)其初始值為254)變 化而得到的計(jì)算值:
a b (char)(b) - (char)(a)
254 254 0
255 - 1
0 - 2
1 - 3
。..
124 -126
125 -127
126 -128
127 127
128 126
。..
252 2
253 1
從上面的計(jì)算可以看出,設(shè)定b不變,隨著a(設(shè)其初始值為254)不斷增長(zhǎng)1,a的取值變化為:
254, 255, (一次產(chǎn)生溢出)
0, 1, 。.., 124, 125, 126, 127, 126, 。.., 253, 254, 255, (二 次產(chǎn)生溢出)
0, 1, 。..
。..
而(char)(b) - (char)(a)的變化為:
0, -1,
-2, -3, 。.., -126, -127, -128, 127, 126, 。.., 1, 0, -1,
-2, -3, 。..
。..
從上面的詳細(xì)過(guò)程可以看出,當(dāng)a取值為254,255, 接著在(一次產(chǎn)生溢出)之后變?yōu)?,然后增長(zhǎng)到127之前,uc_after(a,b)的 結(jié)果都顯示a是在b之后,這也與我們的預(yù)期相符。但在a取值為127之后,uc_after(a,b)的結(jié)果卻顯示a是在b之前。
從上面的運(yùn)算 過(guò)程可以得出以下結(jié)論:
使用uc_after(a,b)宏來(lái)計(jì)算兩個(gè)8位無(wú)符號(hào)整型a和b之間的大?。ɑ蛳?后,before/after), 那么a和b的取值應(yīng)當(dāng)滿足以下限定條件:
。 兩個(gè)值之間相差從邏輯值來(lái)講應(yīng)小于有符號(hào)整型的最大值。
。 對(duì)于8位無(wú)符號(hào)整型,兩個(gè)值 之間相差從邏輯值來(lái)講應(yīng)小于128。
從上面可以類推出以下結(jié)論:
對(duì)于time_after等比較jiffies先/后的宏,兩個(gè)值的 取值應(yīng)當(dāng)滿足以下限定條件:
兩個(gè)值之間相差從邏輯值來(lái)講應(yīng)小于有符號(hào)整型的最大值。
對(duì)于32位無(wú)符號(hào)整型,兩個(gè)值之間相差從邏輯值來(lái) 講應(yīng)小于2147483647。
對(duì)于HZ=100,那么兩個(gè)時(shí)間值之間相差不應(yīng)當(dāng)超過(guò)2147483647/100秒 = 0.69 年 = 248.5天。對(duì)于HZ=60,那么兩個(gè)時(shí)間值之間相差不應(yīng)當(dāng)超過(guò)2147483647/60秒 = 1.135年。在實(shí)際代碼應(yīng)用中,需要比較 先/后的兩個(gè)時(shí)間值之間一般都相差很小,范圍大致在1秒~1天左右,所以以上time_after等比較時(shí)間先/后的宏完全可以放心地用于實(shí)際的代碼 中。
7. 代碼中使用time_after等比較時(shí)間先/后的宏
下面代碼是針對(duì)例4-1修改后的正確代碼:
例7-1:在例4-1基 礎(chǔ)上使用time_before宏后的正確代碼
unsigned long timeout = jiffies + HZ/2; /* timeout in 0.5s */
/* do some work 。.. */
do_somework();
/* then see whether we took too long */
if (time_before(jiffies, timeout)) {
/* we did not time out, call no_timeout_handler() 。.. */
no_timeout_handler();
} else {
/* we timed out, call timeout_handler() 。.. */
timeout_handler();
}
8. 結(jié)論
系統(tǒng)中采用jiffies來(lái)計(jì)算時(shí)間,但由于jiffies溢出可能造成時(shí)間比較的錯(cuò)誤,因而強(qiáng)烈建議在編碼中使用 time_after等宏來(lái)比較時(shí)間先后關(guān)系,這些宏可以放心使用。
內(nèi)核時(shí)鐘:
內(nèi)核使用硬件提供的不同時(shí)鐘來(lái)提供依賴于時(shí)間的服務(wù),如busy-waiting(浪費(fèi)CPU周期)和sleep-waiting(放棄CPU)
HZ and Jiffies
系統(tǒng)時(shí)鐘以可編程的頻率中斷處理器,這個(gè)頻率即每秒滴答數(shù),記錄在HZ,選擇合適的HZ值是個(gè)重要問(wèn)題,而且HZ值是架構(gòu)相關(guān)的,可以在配置菜單中修改。 【2.6.21核提供了對(duì)tickless kernel(CONFIG_NO_HZ)的支持,它會(huì)根據(jù)系統(tǒng)負(fù)載動(dòng)態(tài)觸發(fā)時(shí)鐘中斷】。
jiffies記錄了系統(tǒng)啟動(dòng)后的滴答數(shù),常用的函數(shù):time_before()、 time_after()、time_after_eq()、time_before_eq()。因?yàn)閖iffies隨時(shí)鐘滴答變化,不能 用編譯器優(yōu)化它,應(yīng)取volatile值。
32位jiffies變量會(huì)在50天后溢出,太小,因此內(nèi)核提供變量jiffies_64來(lái)hold 64位jiffies。該64位的低32位即為jiffies,在32位機(jī)上需要兩天指令來(lái)賦值64位數(shù)據(jù),不是原子的,因此內(nèi)核提供函數(shù) get_jiffies_64()。
Long Delays
busy-wait:timebefore(),使CPU忙等待;sleep-wait:shedule_timeout(截至?xí)r間);無(wú)論在內(nèi)核空間還 是用戶空間,都沒(méi)有比HZ更精確的控制了,因?yàn)闀r(shí)間片都是根據(jù)滴答更新的,而且即使定義了您的進(jìn)程在超過(guò)指定時(shí)間后運(yùn)行,調(diào)度器也可能根據(jù)優(yōu)先級(jí)選擇其他 進(jìn)程執(zhí)行。
sleep-wait():wait_event_timeout()用于在滿足某個(gè)條件或超時(shí)后重新執(zhí)行,msleep()睡眠指定的ms后重新進(jìn)入就 緒隊(duì)列,這些長(zhǎng)延遲僅適用于進(jìn)程上下文,在中斷上下文中不能睡眠也不能長(zhǎng)時(shí)間busy-waiting。
內(nèi)核提供了timer API來(lái)在一定時(shí)間后執(zhí)行某個(gè)函數(shù):
#include
struct timer_list my_timer;
init_timer(&my_timer); /* Also see setup_timer() */
my_timer.expire = jiffies + n*HZ; /* n is the timeout in number
of seconds */
my_timer.function = timer_func; /* Function to execute
after n seconds */
my_timer.data = func_parameter; /* Parameter to be passed
to timer_func */
add_timer(&my_timer); /* Start the timer */
如果您想周期性執(zhí)行上述代碼,那么把它們加入timer_func()函數(shù)。您使用mod_timer()來(lái)改變my_timer的 超時(shí)值,del_timer()來(lái)刪掉my_timer, 用timer_pending()查看是否my_timer處于掛起狀態(tài)。
用戶空間函數(shù)clock_settime()和clock_gettime()用 于獲取內(nèi)核時(shí)鐘服務(wù)。用戶應(yīng)用程序使用setitimer()和getitimer()來(lái)控制alarm信號(hào)的傳遞當(dāng)指定超時(shí)發(fā)生后。
Short Delays
前面討論的sleep-wait不適用,只能用busy-wait方法。內(nèi)核用于short delay的有mdelay()、udelay()、ndelay()。
Busy-waiting對(duì)于short durations的實(shí)現(xiàn)是通過(guò)衡量處理器執(zhí)行一條指令和必要數(shù)量的反復(fù)loop來(lái)實(shí)現(xiàn)的,根據(jù)loops_per_jiffy實(shí)現(xiàn)。
Pentium Time Stamp Counter
Time Stamp Counter(TSC)是一個(gè)64位寄存器,僅出現(xiàn)在奔騰兼容機(jī)上,記錄了自啟動(dòng)后處理器消耗的clock cycles數(shù)目。TSC是以processor cycle速率增長(zhǎng)的,使用rdtsc()讀 取,提供微秒級(jí)的精確度。TSC ticks可以通過(guò)除以CPU時(shí)鐘速率來(lái)轉(zhuǎn)換成秒數(shù),這可以從cpu_khz讀 取。
unsigned long low_tsc_ticks0, high_tsc_ticks0;
unsigned long low_tsc_ticks1, high_tsc_ticks1;
unsigned long exec_time;
rdtsc(low_tsc_ticks0, high_tsc_ticks0); /* Timestamp
before */
printk(“Hello World\n”); /* Code to be
profiled */
rdtsc(low_tsc_ticks1, high_tsc_ticks1); /* Timestamp after */
exec_time = low_tsc_ticks1 - low_tsc_ticks0;
從核2.6.21已經(jīng)提供high-resolution timers(CONFIG_HIGH_RES_TIMERS), 它利用硬件相關(guān)高速時(shí)鐘來(lái)提供高精確度的功能函數(shù)如nanosleep()。奔騰機(jī)上使用 TSC實(shí)現(xiàn)該功能。
Real Time Clock
RTC時(shí)鐘track絕對(duì)時(shí)間,記錄在非易失性存儲(chǔ)器中。RTC電池常超過(guò)computer生存期??梢杂肦TC完成以下功能:(1)、讀或設(shè)置絕對(duì)時(shí) 鐘,并在clock updates時(shí)產(chǎn)生中斷;(2)以2HZ到8192HZ來(lái)產(chǎn)生周期性中斷;(3)設(shè)置alarms。
jiffies僅是相對(duì)于系統(tǒng)啟動(dòng)的相對(duì)時(shí)間,如果想獲取absolute time或wall time,則需要使用RTC,內(nèi)核用變量xtime來(lái)記錄,當(dāng)系統(tǒng)啟動(dòng)時(shí),讀取RTC并記錄 在xtime中,當(dāng)系統(tǒng)halt時(shí),則將wall time寫(xiě)回RTC,函數(shù)do_gettimeofday()來(lái)讀取wall time。
#include
static struct timeval curr_time;
do_gettimeofday(&curr_time);
my_timestamp = cpu_to_le32(curr_time.tv_sec); /* Record timestamp */
用戶空間獲取wall time的函數(shù):time()返回calendar time或從00:00:00 on January 1,1970的秒數(shù);(2)localtime():返回calendar time in broken-down format;(3)mktime():與 localtime()相反;(4)gettimeofday()以microsecond 精確度返回calendar時(shí)間(需硬件支持)
另外一個(gè)獲取RTC的方法是通過(guò)字符設(shè)備/dev/rtc,一個(gè)時(shí)刻僅允許一個(gè)處理器訪問(wèn)它。
時(shí)鐘和定時(shí)器對(duì)Linux內(nèi)核來(lái)說(shuō)十分重要。首先內(nèi)核要管理系統(tǒng)的運(yùn)行時(shí)間(uptime)和當(dāng)前墻上時(shí)間(wall time), 即當(dāng)前實(shí)際時(shí)間。其次,內(nèi)核中大量的活動(dòng)由時(shí)間驅(qū)動(dòng)(time driven)。其中一些活動(dòng)是周期性的,比如調(diào)度調(diào)度器(scheduler)中的運(yùn)行隊(duì)列(runqueue)或者刷新屏幕這樣的活動(dòng),它們以固有的 頻率定時(shí)發(fā)生;同時(shí),內(nèi)核要非周期性地調(diào)度某些函數(shù)在未來(lái)某個(gè)時(shí)間發(fā)生,比如推遲執(zhí)行的磁盤(pán)I/O操作等。
---------------------------------------------------------
內(nèi)核必須借助硬件來(lái)實(shí)現(xiàn)時(shí)間管理。實(shí)時(shí)時(shí)鐘(real time clock)是用來(lái)持久存放系統(tǒng)時(shí)間的設(shè)備,它與CMOS集成在一起,并通過(guò)主板電池供電,所以即便在關(guān)閉計(jì)算機(jī)系統(tǒng)之后,實(shí)時(shí)時(shí)鐘仍然能繼續(xù)工作。
系統(tǒng)啟動(dòng)時(shí),內(nèi)核讀取實(shí)時(shí)時(shí)鐘,將所讀的時(shí)間存放在變量xtime中作為墻上時(shí)間(wall time),xtime保存著從1970年1月1日0:00到當(dāng)前時(shí)刻所經(jīng)歷的秒數(shù)。雖然在Intel x86機(jī)器上,內(nèi)核會(huì)周期性地將當(dāng)前時(shí)間存回實(shí)時(shí)時(shí)鐘中,但應(yīng)該明確,實(shí)時(shí)時(shí)鐘的主要作用就是在啟動(dòng)時(shí)初始化墻上時(shí)間xtime。
系統(tǒng)定時(shí)器與動(dòng)態(tài)定時(shí)器
---------------------------------------------------------
周期性發(fā)生的事件都是由系統(tǒng)定時(shí)器(system timer)驅(qū)動(dòng)。在X86體系結(jié)構(gòu)上,系統(tǒng)定時(shí)器通常是一種可編程硬件芯片(如8254 CMOS芯片),又稱可編程間隔定時(shí)器(PIT, Programmable Interval Timer),其產(chǎn)生的中斷就是時(shí)鐘中斷(timer interrupt)。時(shí)鐘中斷對(duì)應(yīng)的處理程序負(fù)責(zé)更新系統(tǒng)時(shí)間和執(zhí)行周期性運(yùn)行的任務(wù)。系統(tǒng)定時(shí)器的頻率稱為節(jié)拍率(tick rate),在內(nèi)核中表示為HZ。
以X86為例,在2.4之前的內(nèi)核中其大小為100; 從內(nèi)核2.6開(kāi)始,HZ = 1000, 也就是說(shuō)每秒時(shí)鐘中斷發(fā)生1000次。這一變化使得系統(tǒng)定時(shí)器的精度(resolution)由10ms提高到1ms,這大大提高了系統(tǒng)對(duì)于時(shí)間驅(qū)動(dòng)事件 調(diào)度的精確性。過(guò)于頻繁的時(shí)鐘中斷不可避免地增加了系統(tǒng)開(kāi)銷(overhead),但是總的來(lái)說(shuō),在現(xiàn)在計(jì)算機(jī)系統(tǒng)上,HZ = 1000不會(huì)導(dǎo)致難以接受的系統(tǒng)開(kāi)銷。
與系統(tǒng)定時(shí)器相對(duì)的是動(dòng)態(tài)定時(shí)器(dynamic timer),它是調(diào)度事件(執(zhí)行調(diào)度程序)在未來(lái)某個(gè)時(shí)刻發(fā)生的時(shí)機(jī)。內(nèi)核可以動(dòng)態(tài)地創(chuàng)建或銷毀動(dòng)態(tài)定時(shí)器。
系統(tǒng)定時(shí)器及其中斷處理程序是內(nèi)核管理機(jī)制的中樞,下面是一些利用系統(tǒng)定時(shí)器周期執(zhí)行的工作(中斷處理程序所做的工作):
(1) 更新系統(tǒng)運(yùn)行時(shí)間(uptime)
?。?) 更新當(dāng)前墻上時(shí)間(wall time)
?。?) 在對(duì)稱多處理器系統(tǒng)(SMP)上,均衡調(diào)度各處理器上的運(yùn)行隊(duì)列
?。?) 檢查當(dāng)前進(jìn)程是否用完了時(shí)間片(time slice),如果用盡,則進(jìn)行重新調(diào)度
(5) 運(yùn)行超時(shí)的動(dòng)態(tài)定時(shí)器
?。?) 更新資源耗盡和處理器時(shí)間的統(tǒng)計(jì)值
內(nèi)核動(dòng)態(tài)定時(shí)器依賴于系統(tǒng)時(shí)鐘中斷,因?yàn)橹挥性谙到y(tǒng)時(shí)鐘中斷發(fā)生后內(nèi)核才會(huì)去檢查當(dāng)前是否有超時(shí)的動(dòng)態(tài)定時(shí)器。
X86體系結(jié)構(gòu)中時(shí)鐘資源還包括CPU本地APIC(local Advanced Programmable Interrupt Controller)中的定時(shí)器和時(shí)間戳計(jì)時(shí)器TSC(Time Stamp Counter)。高精度定時(shí)器將使用CPU本地APIC作為高精度定時(shí)中斷源。
高精度定時(shí)器的設(shè)計(jì)與實(shí)現(xiàn)
---------------------------------------------------------
X86體系結(jié)構(gòu)中,內(nèi)核2.6.X的HZ = 1000, 即系統(tǒng)時(shí)鐘中斷執(zhí)行粒度為1ms,這意味著系統(tǒng)中周期事情最快為1ms執(zhí)行一次,而不可能有更高的精度。動(dòng)態(tài)定時(shí)器隨時(shí)都可能超時(shí),但由于只有在系統(tǒng)時(shí)鐘 中斷到來(lái)時(shí)內(nèi)核才會(huì)檢查執(zhí)行超時(shí)的動(dòng)態(tài)定時(shí)器,所以動(dòng)態(tài)定時(shí)器的平均誤差大約為半個(gè)系統(tǒng)時(shí)鐘周期(即0.5ms)。
對(duì)于實(shí)時(shí)要求較高的電信應(yīng)用來(lái)說(shuō),普通Linux在實(shí)時(shí)性方面與電信平臺(tái)的要求之間還存在一定的差距。CGL為了增強(qiáng)Linux的軟實(shí)時(shí)能力,在以下方面 對(duì)內(nèi)核進(jìn)行了改進(jìn):提供高精度的動(dòng)態(tài)定時(shí)器;提供可搶占式內(nèi)核(preemption kernel)等。下面主要介紹高精度實(shí)時(shí)器的設(shè)計(jì)思想及實(shí)現(xiàn),該實(shí)現(xiàn)遵循PISIX 1003.1b中時(shí)鐘和定時(shí)器相關(guān)API標(biāo)準(zhǔn),方便應(yīng)用程序開(kāi)發(fā)人員的使用。
高精度定時(shí)器的基本設(shè)計(jì)思想為:用 (jiffies+sub_jiffie)表示動(dòng)態(tài)定時(shí)器的超時(shí)時(shí)間,PIT仍然按頻率HZ = 1000產(chǎn)生系統(tǒng)時(shí)鐘中斷。如果在一個(gè)時(shí)鐘中斷tick與下一個(gè)時(shí)鐘中斷(tick+1)之間,即[jiffies, jiffies+1)之間,有高精度動(dòng)態(tài)定時(shí)器等待處理(超時(shí)時(shí)間表示為(jiffies+sub_jiffie), sub_jiffie 《 1), 那么用最近的動(dòng)態(tài)定時(shí)器超時(shí)值sub_jiffie對(duì)硬件定時(shí)器(PIT或local APIC)進(jìn)行設(shè)定,使其在時(shí)刻(jiffies+sub_jiffie)產(chǎn)生中斷,通知內(nèi)核對(duì)該高精度定時(shí)器進(jìn)行處理。而不必總是等到系統(tǒng)時(shí)鐘中斷到來(lái) 后才檢查執(zhí)行所有超時(shí)的定時(shí)器,從而達(dá)到提高動(dòng)態(tài)定時(shí)器精度的目的。
高精度定時(shí)器的中斷源
---------------------------------------------------------
高精度定時(shí)器在內(nèi)核中,仍然使用可編程間隔定時(shí)器PIC產(chǎn)生每秒HZ次的系統(tǒng)時(shí)鐘中斷,對(duì)于采用哪種硬件定時(shí)器產(chǎn)生高精度定時(shí)器中斷則取決于CPU上是否 有本地APIC。若CPU上沒(méi)有本地APIC,那么仍可使用PIT產(chǎn)生高精度定時(shí)中斷,雖然這種然PIT即產(chǎn)生系統(tǒng)時(shí)鐘中斷又產(chǎn)生高精度定時(shí)器中斷的做法 效率不高。
獲取高精度定時(shí)器的發(fā)生時(shí)間
---------------------------------------------------------
高精度定時(shí)器發(fā)生在連續(xù)兩個(gè)jiffies之間(即時(shí)刻(jiffies+sub_jiffie)),要確定其產(chǎn)生時(shí)間,就必須確定sub_jiffie 的大小。通過(guò)函數(shù)get_arch_cycles(ref_jiffies)可獲取sub_jiffie的值,sub_jiffe以CPU時(shí)鐘周期為最小 計(jì)時(shí)單位。函數(shù)具體實(shí)現(xiàn)思想是,通過(guò)訪問(wèn)計(jì)數(shù)器TSC,計(jì)算從上一個(gè)jiffies到當(dāng)前時(shí)刻ref_jiffies之間的TSC差值,最終確定 sub_jiffies的大小。
The high-resolution timer API
---------------------------------------------------------
Last September, this page featured an article on the ktimers patch by Thomas Gleixner. The new timer abstraction was designed to enable the provision of high-resolution timers in the kernel and to address some of the inefficiencies encountered when the current timer code is used in this mode. Since then, there has been a large amount of discussion, and the code has seen significant work. The end product of that work, now called “hrtimers,” was merged for the 2.6.16 release.
At its core, the hrtimer mechanism remains the same. Rather than using the “timer wheel” data structure, hrtimers live on a time-sorted linked list, with the next timer to expire being at the head of the list. A separate red/black tree is also used to enable the insertion and removal of timer events without scanning through the list. But while the core remains the same, just about everything else has changed, at least superficially.
struct ktime_t
-----------------------------
There is a new type, ktime_t, which is used to store a time value in nanoseconds. This type, found in , is meant to be used as an opaque structure. And, interestingly, its definition changes depending on the underlying architecture. On 64-bit systems, a ktime_t is really just a 64-bit integer value in nanoseconds. On 32-bit machines, however, it is a two-field structure: one 32-bit value holds the number of seconds, and the other holds nanoseconds. The order of the two fields depends on whether the host architecture is big-endian or not; they are always arranged so that the two values can, when needed, be treated as a single, 64-bit value. Doing things this way complicates the header files, but it provides for efficient time value manipulation on all architectures.
struct--ktime_t
typedef union {
On 64-bit systems
|----------------------|
| s64 tv64; |
|----------------------|
On 32-bit machines
|----------------------|
| struct { |
| s32 sec, nsec; |
| s32 nsec, sec; |
| } tv; |
|----------------------|
} ktime_t;
初始化ktime_t
-----------------------------
A whole set of functions and macros has been provided for working with ktime_t values, starting with the traditional two ways to declare and initialize them(ktime_t values):
?。?) Initialize to zero
DEFINE_KTIME(name);
?。?) ktime_t kt;
kt = ktime_set(long secs, long nanosecs);
初始化高精度時(shí)鐘hrtimer
-----------------------------
The interface for hrtimers can be found in 。 A timer is represented by struct hrtimer, which must be initialized with:
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock);
System clocks
-----------------------------
Every hrtimer is bound to a specific clock. The system currently supports two clocks, being:
* CLOCK_MONOTONIC: a clock which is guaranteed always to move forward in time, but which does not reflect “wall clock time” in any specific way. In the current implementation, CLOCK_MONOTONIC resembles the jiffies tick count in that it starts at zero when the system boots and increases monotonically from there.
* CLOCK_REALTIME which matches the current real-world time.
The difference between the two clocks can be seen when the system time is adjusted, perhaps as a result of administrator action, tweaking by the network time protocol code, or suspending and resuming the system. In any of these situations, CLOCK_MONOTONIC will tick forward as if nothing had happened, while CLOCK_REALTIME may see discontinuous changes. Which clock should be used will depend mainly on whether the timer needs to be tied to time as the rest of the world sees it or not. The call to hrtimer_init() will tie an hrtimer to a specific clock, but that clock can be changed with:
void hrtimer_rebase(struct hrtimer *timer, clockid_t new_clock);
hrtimer_start()
-----------------------------
Actually setting a timer is accomplished with:
int hrtimer_start(struct hrtimer *timer,
ktime_t time,
enum hrtimer_mode mode);
The mode parameter describes how the time parameter should be interpreted. A mode of HRTIMER_ABS indicates that time is an absolute value, while HRTIMER_REL indicates that time should be interpreted relative to the current time.
評(píng)論