一、提要
計時器屬於作業系統中的基礎組件,不管是使用者空間的程式開發,還是核心空間的程式開發,很多時候都需要有定時器作為基礎組件的支援。使用定時器的目的無非是為了周期性的執行某一任務,或者是到了一個指定時間去執行某一個任務。
本文首先討論了在 Linux 環境下,計時器的分類與實現,並對相應的介面函數進行使用。
二、計時器的種類
RTC(Real Time Clock)
系統時鐘,獨立於CPU和其他所有晶片,能夠在IRQ8上發出周期性的中斷,頻率在2Hz-8192Hz之間。
TSC(Time Stamp Counter)
時間戳記計時器,主體是位於CPU裡面的一個64位的TSC寄存器。每個CPU刻度其值加一。可以通過組合語言指令rdtsc讀這個寄存器。
PIT(Programmable Interval Timer)
可程式化間隔定時器,通過發出一個特殊的中斷來通知核心一個時間間隔過去了,PIT永遠以核心確定的固定頻率不停地發出中斷。
APIC - CPU本地定時器
HPET - 高精度事件定時器
ACPI 電源管理定時器
三、計時體系的結構
核心會周期性地做下面的事:
1)更新系統啟動以來所經過的時間;
2)更新時間和日期;
3)處理時間片的分配;
4)更新資源使用統計數;
5)檢查每個軟定時器的時間間隔是否已到。
計時器的初始化
1)初始化xtime變數(存放目前時間和日期);
2)初始化wall_to_monotonic變數;
3)如果核心支援HPET,它將調用hpet_enable函數來確認ACPI韌體是否探測到了該晶片並將它的寄存器映射到了記憶體位址空間中;
4)調用select_timer()來挑選系統中可利用的最好的定時器資源(精度優先),設定cur_timer變數指向該定時器資源對應的定時器對象的地址;
5)調用setup_irq(0,&irq0)來建立與IRQ0相應的中斷門,IRQ0引腳串連著系統時鐘的中斷源(PIT或者HPET).
時鐘終端處理常式的執行
1)在xtime_lock順序鎖上產生一個write_seqlock()來保護與定時器相關的核心變數;
2)執行cur_timer定時器對象的mark_offset方法。cur_timer指向的定時器在計時器的初始化已確定;
3)調用do_timer_interrupt()函數;
4)調用write_sequnlock釋放xtime_lock順序鎖;
5)返回1,報告中斷已經有效處理了。
Linux考慮兩種類型的定時器,動態定時器(dynamic timer)和間隔定時器(interval timer)。第一種類型由核心使用,後者可以由進程在使用者態建立。
建立動態定時器的步驟
1)建立一個新的timer_list對象(靜態全域變數、定義局部變數、動態分配);
2)調用init_timer(&t)初始化這個對象;
3)把定時器到期時要啟用的函數地址放入function欄位;
4)如果動態定時器還沒有被插入到鏈表中,如expires欄位賦一個合適的值並調用add_timer(&t)把t插入鏈表;
5)如已插入,調用mod_timer()來更新expires欄位。
三、計時器的使用
unsigned int alarm(unsigned int seconds);
函數說明: alarm()用來設定訊號SIGALRM在經過參數seconds指定的秒數後傳送給目前的進程。如果參數seconds為0,則之前設定的鬧鐘會被取消,並將剩下的時間返回。
傳回值: 返回之前鬧鐘的剩餘秒數,如果之前未設鬧鐘則返回0。
alarm()執行後,進程將繼續執行,在後期(alarm以後)的執行過程中將會在seconds秒後收到訊號SIGALRM並執行其處理函數。
例子:
#include <stdio.h>#include <unistd.h>#include <signal.h>void alarm_handler(int a){printf("Timer is up!\n");alarm(3);}int main(){signal(SIGALRM,alarm_handler);alarm(1); while(1) {};return 1;}
執行 結果:
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
setitimer()比alarm功能強大,支援3種類型的定時器:
ITIMER_REAL : 以系統真實的時間來計算,它送出SIGALRM訊號。
ITIMER_VIRTUAL : -以該進程在使用者態下花費的時間來計算,它送出SIGVTALRM訊號。
ITIMER_PROF : 以該進程在使用者態下和核心態下所費的時間來計算,它送出SIGPROF訊號。
setitimer()第一個參數which指定定時器類型(上面三種之一);第二個參數是結構itimerval的一個執行個體;第三個參數可不做處理。
setitimer()調用成功返回0,否則返回-1。
相關結構體:
struct itimercal:
struct itimerval {
struct timeval it_interval; /* timer interval */
struct timeval it_value; /* current value *
};
struct timeval {
long tv_sec;
long tv_usec;
};
itimerval結構中的it_value是減少的時間,當這個值為0的時候就發出相應的訊號了. 然後再將it_value設定為it_interval值.這樣就實現了輪詢的定時,而不是想alarm那樣只能定時一次,而且其精確度也很高。
例子:
#include <stdio.h>#include <unistd.h>#include <signal.h>#include <sys/time.h>void alarm_handler(int sig){switch(sig){case SIGALRM:printf("Catch a SIGALRM!\n");break;case SIGVTALRM:printf("Catch a SIGVTALRM!\n");break;}return;}int main(){struct itimerval value, ovalue, value2; signal(SIGALRM, alarm_handler); signal(SIGVTALRM, alarm_handler); value.it_value.tv_sec = 1; value.it_value.tv_usec = 0; value.it_interval.tv_sec = 1; value.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &value, &ovalue); value2.it_value.tv_sec = 0; value2.it_value.tv_usec = 500000; value2.it_interval.tv_sec = 0; value2.it_interval.tv_usec = 500000; setitimer(ITIMER_VIRTUAL, &value2, &ovalue); while(1);return 1;}
執行:
四、參考
understanding the kernel 3rd Edith
linux應用程式層定時器與休眠 - http://blog.csdn.net/max415/article/details/2315977