系統(tǒng)定時(shí)器是一種可編程硬件芯片,能以固定頻率產(chǎn)生中斷,也就是定時(shí)器中斷,其對應(yīng)的中斷處理程序負(fù)責(zé)更新系統(tǒng)時(shí)間,也負(fù)責(zé)執(zhí)行需要周期性運(yùn)行的任務(wù)。
系統(tǒng)定時(shí)器和時(shí)鐘中斷處理程序是Linux系統(tǒng)內(nèi)核管理機(jī)制的中樞。動態(tài)定時(shí)器是一種推遲執(zhí)行程序的工具。
一、內(nèi)核中的時(shí)間概念
內(nèi)核必須在硬件的幫助下才能計(jì)算和管理時(shí)間。硬件為內(nèi)核提供一個(gè)系統(tǒng)定時(shí)器用以計(jì)算流逝的時(shí)間。系統(tǒng)定時(shí)器以某種頻率自行觸發(fā)(擊中hitting或射中popping)時(shí)鐘中斷,該頻率可以通過編程預(yù)定,稱節(jié)拍率(tick rate)。當(dāng)時(shí)鐘中斷發(fā)生時(shí),內(nèi)核通過特殊的中斷處理程序進(jìn)行處理。節(jié)拍率對內(nèi)核是可知的,所以內(nèi)核知道連續(xù)兩次時(shí)間中斷的間隔時(shí)間,也就是節(jié)拍(tick),等于1/節(jié)拍率(秒)。
- 墻上時(shí)間:實(shí)際時(shí)間
- 系統(tǒng)運(yùn)行時(shí)間:自系統(tǒng)啟動開始所經(jīng)過的時(shí)間
二、節(jié)拍率:HZ
節(jié)拍率是通過靜態(tài)預(yù)處理定義的,也就是HZ(赫茲),在系統(tǒng)啟動時(shí)按照HZ值對硬件進(jìn)行設(shè)置。大多數(shù)體系結(jié)構(gòu)的節(jié)拍率是可調(diào)的。
提高節(jié)拍率的好處:
- 更高的時(shí)鐘中斷解析度可以提高時(shí)間驅(qū)動事件的解析度
- 提高時(shí)間驅(qū)動事件的準(zhǔn)確度
高HZ的優(yōu)勢(也就是高時(shí)鐘中斷解析度和準(zhǔn)確度帶來的好處):
- 內(nèi)核定時(shí)器能以更高的頻度和準(zhǔn)確度運(yùn)行
- 依賴定時(shí)值執(zhí)行的系統(tǒng)調(diào)用以更高的精度運(yùn)行,提高性能
- 對如資源消耗和系統(tǒng)運(yùn)行時(shí)間等的測量會有更精細(xì)的解析度
- 提高進(jìn)程搶占的準(zhǔn)確度
高HZ的劣勢:HZ越高,時(shí)鐘中斷頻率越高,系統(tǒng)負(fù)擔(dān)越重。
三、jiffies
全局變量jiffies用于記錄自系統(tǒng)啟動以來的節(jié)拍的總數(shù)。
jiffies的定義:
extern unsigned long volatile jiffies;
//關(guān)鍵詞volatile指示編譯器在每次訪問變量時(shí)都重新從主內(nèi)存中獲得,而不是通過寄存器中的變量別名來訪問。
在32位體系結(jié)構(gòu)上unsigned long只有32位,很容易會溢出。因此增加了一個(gè)64位的變量:
extern u64 jiffies_64; // 可通過get_jiffies_64()函數(shù)訪問
ld腳本用于連接主內(nèi)核映像,然后用jiffies_64變量覆蓋jiffies。因此,對于32位系統(tǒng),jiffies取jiffies_64的低32位,因?yàn)榇蠖鄶?shù)代碼使用jiffies存放流失的時(shí)間,因此只關(guān)心低32位;時(shí)間管理代碼使用整個(gè)64位jiffies_64,避免溢出。對于64位系統(tǒng),jiffies和jiffies_64是同一個(gè)變量。
因?yàn)?2位體系結(jié)構(gòu)不能原子地一次訪問64位變量中的兩個(gè)32位數(shù)值,因此在讀取jiffies時(shí)需要用xtime_lock鎖對jiffies變量進(jìn)行鎖定。
當(dāng)jiffies變量的值發(fā)生溢出,如果節(jié)拍計(jì)數(shù)還要繼續(xù)增加,jiffies的值就會回繞到0。內(nèi)核提供了宏定義來處理:
#define time_after(unkonwn, known) ((long)(known) - (long)(unknown) < 0)
#define time_before(unkonwn, known) ((long)(unknown) - (long)(known) < 0)
#define time_after_eq(unkonwn, known) ((long)(unknown) - (long)(known) >= 0)
#define time_before(unkonwn, known) ((long)(known) - (long)(unknown) >= 0)
內(nèi)核定義了USER_HZ來代表用戶空間看到的HZ值。
jiffies_to_clock_t(unsigned long)//將HZ表示的節(jié)拍計(jì)數(shù)轉(zhuǎn)化稱USER_HZ表示的節(jié)拍計(jì)數(shù)
jiffies_64_to_clock_t(unsigned long)
四、硬時(shí)鐘和定時(shí)器
實(shí)時(shí)時(shí)鐘(RTC):用于持久存放系統(tǒng)時(shí)間的設(shè)備,即便系統(tǒng)關(guān)閉后,也可以靠主板上的微型電池提供電力保持系統(tǒng)的計(jì)時(shí)。當(dāng)系統(tǒng)啟動時(shí),內(nèi)核通過讀取RTC來初始化墻上時(shí)間,存放在xtime變量中。
系統(tǒng)定時(shí)器提供一種周期性觸發(fā)中斷機(jī)制。
五、時(shí)鐘中斷處理程序
時(shí)鐘中斷處理程序可以劃分為兩部分:
- 體系結(jié)構(gòu)相關(guān)部分
- 體系結(jié)構(gòu)無關(guān)部分
與體系結(jié)構(gòu)相關(guān)的例程作為系統(tǒng)定時(shí)器的中斷處理程序注冊到內(nèi)核中,以便產(chǎn)生時(shí)鐘中斷時(shí)運(yùn)行。其執(zhí)行:
- 獲得xtime_lock鎖,以便對訪問jiffies_64和xtime進(jìn)行保護(hù)
- 需要時(shí)應(yīng)答或重設(shè)系統(tǒng)時(shí)鐘
- 周期性使用墻上時(shí)間更新實(shí)時(shí)時(shí)鐘
- 調(diào)用體系結(jié)構(gòu)無關(guān)的時(shí)鐘例程tick_periodic()
- 釋放xtime_lock鎖
tick_periodic()執(zhí)行:
- jiffies_64加1
- 更新資源消耗的統(tǒng)計(jì)值
- 執(zhí)行以及到期的動態(tài)定時(shí)器
- 執(zhí)行sheduler_tick()函數(shù)
- 更新墻上時(shí)間,存放在xtime變量中
- 計(jì)算平均負(fù)載值
六、實(shí)際(墻上)時(shí)間
墻上時(shí)間保存在xtime變量中:
struct timespec {
_kernel_time_t tv_sec; //秒,19700101以來經(jīng)過的s數(shù)
long tv_nsec; //ns,上一秒開始經(jīng)過的ns數(shù)
};
struct timespec xtime;
獲取墻上時(shí)間的系統(tǒng)調(diào)用為:
int gettimeofday(struct timeval*tv,struct timezone *tz )
七、定時(shí)器
定時(shí)器(也稱動態(tài)定時(shí)器、內(nèi)核定時(shí)器)是管理內(nèi)核流逝的時(shí)間的基礎(chǔ)。由結(jié)構(gòu)timer_list表示:
struct timer_list {
struct list_head entry;
unsigned long expires; //以jiffies為單位的定時(shí)值
void (*function) (unsigned long); //定時(shí)器處理函數(shù)
unsigned long data; //定時(shí)器處理函數(shù)的參數(shù)
struct tvec_t_base_s *base;
};
使用定時(shí)器:
//定義定時(shí)器
struct timer_list my_timer;
//初始化
init_timer(&my_timer);
//填充
my_timer.expires =
my_timer.function =
my_timer.data =
// 激活定時(shí)器
add_timer(&my_timer);
//修改定時(shí)器超時(shí)時(shí)間
mod_timer(&my_timer, jiffies+new_delay);
//停止定時(shí)器
del_timer(&my_timer);
del_timer_sync(&my_timer);
內(nèi)核在時(shí)鐘中斷發(fā)生后執(zhí)行定時(shí)器,定時(shí)器作為軟中斷在下半部上下文執(zhí)行。
八、延遲執(zhí)行
8.1 忙等待
忙等待是最簡單的延遲方法,僅僅在想要延遲的時(shí)間是節(jié)拍的整數(shù)倍,或精確率要求不高時(shí)才可以使用。
8.2 短延遲
有時(shí)內(nèi)核需要很短的延遲,而且還要求延遲的時(shí)間很準(zhǔn)確,就需要延遲函數(shù):
void udelay(unsigned long usecs); //us
void ndelay(unsigned long nsecs);//ns
void mdelay(unsigned long msecs);//ms
BogoMIPS值記錄處理器在給定時(shí)間內(nèi)忙循環(huán)執(zhí)行的次數(shù)。udelay函數(shù)根據(jù)指定的延遲時(shí)間在1s中的占比,就能決定需要進(jìn)行多少次循環(huán)就可以達(dá)到要求的推遲時(shí)間。
8.3 schedule_timeout()
schedule_timeout()會讓需要延遲的任務(wù)睡眠到指定的延遲時(shí)候耗盡再重新運(yùn)行。當(dāng)指定時(shí)間到期后,內(nèi)核喚醒被延遲的任務(wù)并將其重新放回到運(yùn)行隊(duì)列中。schedule_timeout()調(diào)用之前,必須將任務(wù)置成TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE狀態(tài),否則不會休眠。調(diào)用schedule_timeout的代碼必須處于進(jìn)程上下文,并且不能持有鎖。