1.核心中時間的概念
(1)核心中有大量事件是基于時間驅動的,相對時間和絕對時間這兩個概念對核心時間管理來說都至關重要。
(2)系統定時器和時鐘中斷處理程式是Linux系統核心管理機制中的中樞。核心必須在硬體的幫助下才能計算和管理時間,定時器以某種頻率自行觸發時鐘中斷。當中斷發生時,核心就通過一種特殊的中斷處理程式對其進行處理。
(3)核心可以動态建立和撤銷動态定時器--- 一種用來推遲執行程式的工具。
牆上時間:就是實際時間,對使用者空間的應用程式很重要,可以用來維護實際日期和時間。
系統運作時間:自系統啟動以來經曆的時間,對核心和使用者空間都有用。
2.HZ
系統定時器頻率(節拍率),是通過靜态預處理定義的。在
定義,對使用者空間,核心HZ幾乎完全隐藏,确切的 HZ 值隻能通過 /proc/interrupts 獲得:=(/proc/interrupts) / (/proc/uptime) ,自開機以來系統的滴答數除以運作時間.
更高的節拍率意味着時鐘産生更加頻繁,中斷處理程式也會執行更頻繁。更高的時鐘中斷解析度可提高時間事件驅動時間的解析度和準确度(時鐘中斷執行準确度是+/-1/(2HZ))。
高HZ優勢:
①核心定時器能夠以更高的頻率和更高的準确度運作;
②依賴定時值執行的系統調用,比如poll()和select(),能夠以更高的精度運作;
③對諸如資源消耗和系統運作時間等的測量會有更精細的解析度;
④提高程序搶占的準确度;
高HZ劣勢
節拍率越高,時鐘中斷頻率越高,中斷程式占用處理器時間就越多。這樣不但減少了處理器工作時間,還更頻繁的打亂了處理器高速緩存,增加功耗。
無節拍OS
當核心配置CONFIG_HZ時,系統根據這個選項動态排程時鐘中斷,這樣實質性受益是省電。
3.jiffies
(1)用來記錄自系統啟動以來産生的節拍總數,轉化為時間(jiffies/HZ)
Jiffies總是全局的unsigned long變量,在32位體系結構上,它就是32位,在HZ=100時,497天溢出。在64位體系上是64位,我們看不到溢出。通過連接配接,jiffies被jiffies_64覆寫(在64位機兩者相同,在32位機jiffies取jiffies_64低32位)。通過函數get_jiffies_64()可以讀取完整64位jiffies值。
(2)為防止溢出問題,核心提供幾個宏來比較節拍計數
此處)折疊或打開
- time_after(unknown,known);//當jiffies超過指定known時,傳回真
- time_before(unknown,known)
- time_after_eq(unknown,known)
- time_before_eq(unknown,known)
- unknown通常是jiffies,known = jiffies+HZ/2
(3)使用者空間和HZ
核心定義USER_HZ給應用層,當USER_HZ小于等于HZ,且兩者互為整數倍時傳回給應用的時間是 return x/(HZ/USER_HZ)
核心使用函數jiffies_64_to_clock_t()将64位jiffies從HZ轉換到USER_HZ
此處)折疊或打開
- unsigned long start;
- unsigned long total_time;
- start = jiffies;
- /* do some work ... */
- total_time = jiffies - start;
- printk(“That took %lu ticksn”, jiffies_to_clock_t(total_time));
- printk(“That took %lu secondsn”, total_time / HZ);
4.硬時鐘和定時器
(1)實時時鐘(RTC)用來持久存放系統時間,系統啟動時,核心讀取RTC來初始化牆上時間,該時間存放在xtime變量中。
(2)系統定時器,在X86中,采用可程式設計中斷時鐘(PIT)作為時鐘中斷源。
5.時鐘中斷處理函數
分兩部分:體系結構相關和體系結構無關。
(1)體系結構相關例程,作為系統定時器的中斷處理函數注冊到核心,主要完成以下工作:
擷取xtime_lock鎖,以便對通路jiffies_64和牆上時間xtime進行保護;
需要時應答或重新設定系統時鐘;
周期性地使用牆上時間更新實時時鐘;
調用體系無關時鐘例程:tick_periodic().
(2)中斷主要通過體系無關例程tick_periodic()完成更多工作
給jiffies_64變量加1;
更新資源消耗的統計值,比如目前程序所消耗的系統時間和使用者時間;
執行已經到期的動态定時器;
執行sheduler_tick()函數;
更新牆上時間,該事件存放在xtime中
此處)折疊或打開
- /*
- * Periodic tick
- */
- static void tick_periodic(int cpu)
- {
- if (tick_do_timer_cpu == cpu) {
- write_seqlock(&xtime_lock);
- /* Keep track of the next tick event */
- tick_next_period = ktime_add(tick_next_period, tick_period);
- do_timer(1); //操作jiffies_64,更新牆上時間,更新系統平均負載
- write_sequnlock(&xtime_lock);
- }
- update_process_times(user_mode(get_irq_regs()));//更新所消耗的各種節拍數
- profile_tick(CPU_PROFILING);
- }
核心對程序進行時間計數,是根據中斷發生時處理器所處模式統計的,它把上個節拍全部算給了程序。這種粒度的程序統計方式是傳統unix具有的,現在還沒有更加精密的統計算法。
run_lock_timer(),标記一個軟中斷去處理所有到期定時器。
以上全部工作每1/HZ秒發生一次。
6.實際時間
目前實際時間(牆上時間)定義在檔案kernel/time/timekeeping.c中
此處)折疊或打開
- struct timespec xtime;
- timespec 定義在<linux/time.h>
- struct timespec {
- time_t tv_sec; //秒,存放自1970年1月1日(utc)以來經過的時間
- long tv_nsec; //自從上一秒開始結果的ns數
- };
- 讀寫xtime變量需要使用xtime_lock鎖,一個seqlock鎖。
- write_seqlock(&xtime_lock);
- /* update xtime ... */
- write_sequnlock(&xtime_lock);
- 讀取xtime時也要使用read_seqbegin()和read_seqretry()
- unsigned long seq;
- do {
- unsigned long lost;
- seq = read_seqbegin(&xtime_lock);
- usec = timer->get_offset();
- lost = jiffies - wall_jiffies;
- if (lost)
- usec += lost * (1000000 / HZ);
- sec = xtime.tv_sec;usec += (xtime.tv_nsec / 1000);
- } while (read_seqretry(&xtime_lock, seq));
在使用者空間取得牆上時間用gettimeofday(),其在核心中調用sys_systimeofday()實作,可以用settimtofday()來設定目前時間,它需要有CAP_SYS_TIME權能。
7.定時器
指定函數将在定時器到期時自動異步執行,執行後自動撤銷。
定義在檔案
中
此處)折疊或打開
- struct timer_list {
- struct list_head entry; //定時器連結清單的入口
- unsigned long expires; //以jiffes為機關的計時值
- void (*function)(unsigned long); //定時器處理函數
- unsigned long data; //傳給處理函數的參數
- struct tvec_base *base; //定時器内部值,使用者不使用
- };
使用者一般不操作這些結構體,而是通過核心定義好的一系列宏來處理
此處)折疊或打開
- 定義定時器:
- struct timer_list my_timer;
- 初始化定時器:
- init_timer(&my_timer);
- 填充定時器結構體:
- my_timer.expires = jiffes+HZ;//延時1s
- my_timer.data = NULL; //可以利用同一個處理器函數注冊多個定時器
- my_timer.function = my_function;//定時器處理函數
- 核心可以保證不會在逾時時間到期前運作處理器函數,但是有可能會延誤執行,誤差是+/-半個節拍,是以定時器不能用來做硬實時任務。
- 激活定時器
- add_timer(&my_timer);
- mod_timer(&my_timer);//激活或修改定時器,若調用時已激活,傳回0,否則傳回1
- del_timer(&my_timer);//删除定時器,定時器未被激活傳回0,否則傳回1
- 為了防止删除定時器時,定時器在多處理器上可能正在運作,即出現競争條件,使用
- del_timer_sync(&my_timer);//不能在中斷上下文使用
在中斷處理函數中,要保護好共享資料。
定時器作為軟中斷在下半部上下文中執行,在run_local_timers()中調用所有到時定時器。
核心采用分組定時器方法來管理定時器,可以減少搜尋逾時定時器所帶來的負擔。
8.延遲執行
(1)忙等待
該方法用于延遲時間是節拍的整數倍,或對精确度不高時使用
unsigned long timeout = jiffies+10;//延遲10個節拍
while(time_befor(jiffes,timeout)) ;
這是最簡單的延遲方法,但是延遲獨占CPU,很少用。改進版是:
unsigned long delay = jiffies + 5*HZ;
while (time_befor(jiffies,delay))
cond_resched();//排程一個新程式運作,該方法有效條件是系統中存在更重要的任務要運作
因為調用了排程程式,是以不能在中斷上下文使用,隻能在程序上下文使用。所有延遲方法在程序上下文都使用的很好,中斷處理都應該盡快的執行,最好不用延遲。
延遲不管在哪種情況下,都不應該在持有鎖時或禁止中斷時使用。
(2)短延遲,比節拍短,短暫又精确的延遲
此處)折疊或打開
- 定義在<linux/delay.h>和<asm/delay.h>
- void udelay(unsigned long usecs);
- void ndelay(unsigned long usecs);
- void mdelay(unsigned long usecs);
udelay是由忙循環實作的,mdelay調用udelay實作,但是會睡眠,1ms以上的延遲就不要用udelay(),防止溢出。
BogoMIPS值記錄處理器在給定時間内忙循環執行的次數,存放在loops_per_jiffy中
(3)schedule_timeout()
更理想的方案是schedule_timeout()函數,讓需要延遲睡眠執行的任務睡眠到指定延遲時間耗盡再重新執行,同樣,隻保證延遲時間盡量接近睡眠時間,時間到期後,核心喚醒被延遲任務并将其重新放回運作隊列。
set_current_state(TASK_INTERRUPTIBLE);//将任務設定為可中斷睡眠狀态
schedule_timeout(s*HZ); //睡眠s秒
①schedule_timeout()的實作
此處)折疊或打開
- signed long __sched schedule_timeout(signed long timeout)
- {
- struct timer_list timer;
- unsigned long expire;
- switch (timeout)
- {
- case MAX_SCHEDULE_TIMEOUT: //用來檢查是否無限期睡眠
- /*
- * These two special cases are useful to be comfortable
- * in the caller. Nothing more. We could take
- * MAX_SCHEDULE_TIMEOUT from one of the negative value
- * but I' d like to return a valid offset (>=0) to allow
- * the caller to do everything it want with the retval.
- */
- schedule();
- goto out;
- default:
- /*
- * Another bit of PARANOID. Note that the retval will be
- * 0 since no piece of kernel is supposed to do a check
- * for a negative retval of schedule_timeout() (since it
- * should never happens anyway). You just have the printk()
- * that will tell you if something is gone wrong and where.
- */
- if (timeout < 0) {
- printk(KERN_ERR "schedule_timeout: wrong timeout "
- "value %lxn", timeout);
- dump_stack();
- current->state = TASK_RUNNING;
- goto out;
- }
- }
- expire = timeout + jiffies; //設定逾時時間
- setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);//設定逾時函數
- __mod_timer(&timer, expire);
- schedule();//
- del_singleshot_timer_sync(&timer);
- /* Remove the timer from the object tracker */
- destroy_timer_on_stack(&timer);
- timeout = expire - jiffies;
- out:
- return timeout < 0 ? 0 : timeout;
- }
若任務提前喚醒(比如收到信号),那麼定時器被撤銷,傳回剩餘時間。