參考:1、http://bbs.eyeler.com/thread-69-1-1.html
2、《Linxu Kernel Development》3ed_CN p166~p185
3、《Professional Linux Kernel Architecture》1ed_CN p714~p760
4、http://blog.csdn.net/droidphone/article/details/8017604
5、2.6.34
此記錄的主要目的記錄下如上參考中的一些知識點、基本概念,以及作為后續記錄的參考。
對於unicore的內核2.6.32.9,其時間子系統是基於低分辨率周期時鍾實現的,但是在此記錄及后續的記錄中將會談下“低分辨率動態時鍾、高分辨率動態時鍾、高分辨率周期時鍾”。
---------------------------------------------------------------------------------------------------------------------------------------
內核中的兩種定時器:
1、timeout:表示將在一定時間之后發生的事件,但可以且通常會在發生之前取消。 2、timer: 用於實現時序,此類定時器通常都會到期,而且與超時類定時器相比,需要更高的時間分辨率。
timer wheel的實現要點在於:
timer wheel的實現可以自行參考內核代碼,網上講的也很多。
1、void __run_timers(struct tvec_base *base) 在當前第一組tv1的元素全被遍歷后(函數也會執行),將會調用cascade函數,主要的功能就在於取下特定tv數組下的某一個數組元素中的鏈表,然后重新加入前面的各個數組的各個數組元數的鏈表中。方法比較巧妙,這樣每次執行定時函數時只需從tv1上取出過期的節點即可執行,但是我們也需要注意執行cascade的時間也可能會很長。
2、void internal_add_timer(struct tvec_base *base, struct timer_list *timer) 注意其中是如何索引到各個數組的數組元素下標的: unsigned long idx = expires - base->timer_jiffies;下標 vec = base->tv[1..5].vec + i; 插入哪個鏈表
低分辨率定時器的重要性在於:
1)處理全局jiffies計數器。該值周期性地增長(如果使用了低分辨率動態時鍾,可能不會周期性增長),它是一種特別簡單的時間基准。
2)進行各進程統計。
內核中的各種time,注意下struct timekeeper timekeeper:
1)wall time
RTC time
在SOC系統中,RTC可以集成到SOC芯片中,並做為一個單獨的電壓域,系統掉電時,可由后備電池供電,RTC中的時間信息不會丟失。內核和用戶空間通過驅動程序訪問RTC硬件來獲取或設置時間信息。
xtime
A value representation of the human time of day;日常生活中所見的鍾表時間,精度可達納秒級。
xtime和RTC時間一樣,都是人們日常生活所使用的牆上時間,只是RTC時間的精度比較低,大多數情況下只能達到毫秒級的精度,如果是使用外部的RTC芯片,訪問速度也比較慢,為此,內核維護了另一個wall time時間:xtime。因為xtime實際上是一個內存變量,它的訪問速度非常快,內核大部分時間都是使用xtime來獲得當前時間信息。xtime記錄的是1970年1月1日到當前時刻所經歷的納秒數。
xtime在正常情況下是遞增的,但是用戶可以主動向前或向后調整牆上時間,從而修改xtime。
2) monotonic time
A monotonically increasing value that represents the amount of time that the system has been running.
開機后單調遞增,它不像xtime可能因用戶進行時間調整而產生改變,該時間不計算系統休眠的時間,即系統休眠時,monotonic不會遞增。
monotonic時間不可以往后退,系統啟動后只能不斷遞增。內核並沒有直接定義一個特定的變量來記錄monotonic時間,而是定義了一個變量wall_to_monotonic,記錄了牆上時間和monotonic時間之間的偏移量,當需要獲得monotonic時間時,把xtime和wall_to_monotonic相加即可。計算monotonic時間要去除系統休眠期間花費的時間,內核用total_sleep_time記錄休眠的時間,每次休眠醒來后重新累加該時間,並調整wall_to_monotonic的值,使其在系統休眠醒來后,monotonic時間不會發生跳變。
3)raw_time
monotonic時間雖然不受settimeofday的影響,但會受到ntp調整的影響,但是raw_time不受ntp的影響,它真的就是開完機后就單調地增加。
xtime、monotonic time和raw_time可以通過用戶空間的clock_gettime函數獲得,對應的ID參數分別是CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_MONOTONIC_RAW。
4)clock source
A representation of a free running counter running at a known frequency, usually in hardware.
xtime、monotonic time、raw time都是基於該時鍾源進行計時操作,當有新的精度更高的時鍾源被注冊時,通過timekeeping_notify函數,change_clocksource函數將會被調用,timekeeper.clock字段將會被更新,指向新的clocksource
5)tick
A periodic interrupt generated by a hardware-timer, typically with a fixed interval defined by HZ: jiffies
對於SMP,內核區分如下兩種時鍾類型:
1)全局時鍾(global clock),負責提供周期時鍾,主要用於jiffies更新。 2)每個CPU一個局部時鍾(local clock),用來進行進程統計、性能剖析和實現高分辨率定時器。 全局時鍾的角色,由一個明確選擇的局部時鍾承擔(tick_setup_device函數中,會首次作出選擇)
在設備第一次調用tick_seup_device時(即該時鍾設備沒有相關的時鍾事件設備),內核執行如下操作:
1)如果沒有選定時鍾設備來承擔全局時鍾設備的角色,那么將選擇當前設備來承擔此職責,而tick_do_timer_cpu將設置為當前設備所屬的處理器編號(注意,如果放棄職責該如何處理,可以參考tick_do_timer_cpu定義處的注釋)。tick_period是時鍾周期,單位是納秒,它根據HZ值設置。 2)該設中設備設置為按周期模式工作。
關於上文中提到的2),什么時候會切換成one shot模式?
關注下hrtimer_run_queues,里面調用了tick_check_oneshot_change來判斷是否可以激活高分辨率定時器。此外該函數還檢查是否可以在低分辨率系統上啟用動態時鍾。(如果有一個支持單觸發模式的時鍾,而且其精度可以達到高分辨率定時器所要求的分辨率,即設置了CLOCK_SOURCE_VALID_FOR_HRES標志,那么tick_check_oneshot_change將通知內核可以使用高分辨率定時器)
留意下,在設備被設置成高分辨率周期時鍾、高分辨率動態時鍾時的中斷處理函數的選擇。(tick_handle_periodic、hrtimer_interrupt、tick_nohz_handler)
/*
* tick_do_timer_cpu is a timer core internal variable which holds the CPU NR
* which is responsible for calling do_timer(), i.e. the timekeeping stuff. This
* variable has two functions:
*
* 1) Prevent a thundering herd issue of a gazillion of CPUs trying to grab the
* timekeeping lock all at once. Only the CPU which is assigned to do the
* update is handling it.
*
* 2) Hand off the duty in the NOHZ idle case by setting the value to
* TICK_DO_TIMER_NONE, i.e. a non existing CPU. So the next cpu which looks
* at it will take over and keep the time keeping alive. The handover
* procedure also covers cpu hotplug.
*/
int tick_do_timer_cpu __read_mostly = TICK_DO_TIMER_BOOT;
動態時鍾&WHY
在關注耗電量的系統上,周期性時鍾要求系統在一定的頻率下,周期性的處於活動狀態。因此,長時間休眠是不可能的。引入動態時鍾后,只有在有些任務需要實際執行時,才激活周期時鍾。否則,會臨時禁用周期時鍾。對該技術的支持可以在編譯時選擇,啟動此選項的系統也稱為無時鍾系統(tickless system)
在系統無事所做的idle階段,我們可以通過停止周期時鍾來達到降低系統功耗的目的,只要有進程處於活動狀態,時鍾事件依然會被周期性地發出。
在內核中,如定義了CONFIG_NO_HZ宏,則說明內核支持動態時鍾;但是我們得明白,CONFIG_NO_HZ並不意味着沒有HZ的概念(周期性更新系統統計量),主要區別在於當我們配置CONFIG_NO_HZ時說明:
1)系統支持動態時鍾。啟用動態時鍾時,也定義且使用了HZ,因為它是許多計時任務的基本量。動態和周期時鍾在表面上沒有什么區別,主要的區別在於 進/退 IDLE時的不同處理方法。 2)系統可能需要停止時鍾機制(此時將不會再周期性的產生時鍾中斷,例如系統進入IDLE)或重啟時鍾機制(系統退出IDLE)。 3)根據2),由於可以暫時停止時鍾機制,因此單觸發時鍾是實現動態時鍾的先決條件。(但是請注意,即使時鍾設備處於單觸發模式,也並不一定啟用了動態時鍾!例如,在高分辨率模式下,時鍾總是基於單觸發定時器實現的。)
高分辨率時鍾如何實現周期時鍾的仿真:
在內核切換到高分辨率模式時,將調用tick_setup_sched_timer來激活時鍾仿真層。這將為每個CPU安裝一個高分辨率定時器。所需的struct hrtime實例保存在CPU變量tick_cpu_sched中:該定時器的回調函數選擇了tick_sched_timer,通過返回HRTIMER_RESTART,定時器將自動重新進入隊列,並在下一個時鍾到期時激活。
內核需要處理的情形:
1、沒有動態時鍾的低分辨率系統,總是使用周期時鍾。該內核不包括任何對單觸發操作的支持。 2、啟用了動態時鍾特性的低分辨率系統,以單觸發模式使用時鍾設備。 3、高分辨率系統總是使用單觸發模式,無論是否啟用了動態時鍾特性。
關於宏:
這一項顯得“理論、教條”,因為我很少接觸SMP、至於龍芯系類也只接觸了一小段時間。
1、啟用動態時鍾:CONFIG_NO_HZ 2、啟用高分辨率定時器支持:CONFIG_HIGH_RES_TIMERS 3、支持動態時鍾和高分辨率定時器的必要前提:GENERIC_TIME、GENERIC_CLOCKEVENTS 4、支持時鍾事件的單觸發模式:CONFIG_TICK_ONESHOT,如果啟用了動態時鍾或高分辨率定時器,則自動選中該項
時鍾源(struct clocksource):時間管理的支柱。本質上每個時鍾源都提供了一個單調增加的計數器,通用的內核代碼只能進行只讀訪問。不同時鍾源的精度取決於底層硬件的能力。clocksource不能被編程、沒有事件產生能力。
關於clocksource中幾個關鍵域: read:時鍾源本身不會產生中斷,要獲得時鍾源的當前計數,只能通過主動調用它的read回調函數來獲得當前技術值,注意這里只獲 得計數值,也就是所謂的cycle,要獲得相應的時間,必須要借助clocksource的mult和shift字段進行轉換計算。
void __clocksource_updatefreq_scale(struct clocksource *cs, u32 scale, u32 freq) mult和shift域:因為從clocksource中讀到的值是一個cycle計數值,要轉換為時間,必須知道驅動clocksource的時鍾頻率F,但是clocksource並沒有保存時鍾的頻率F,內核使用的方法是:根據時鍾的頻率和期望的精度,事先計算出兩個輔助常數mult和shift,然后使用下公式進行cycle和t的轉換: t = (cycle * mult) >> shift; 但是要保證:F = (1 << shift ) / mult;(一般PLL都是先倍頻,再分頻,很容易計算) 內核內部使用64位進行該轉換計算: static inline s64 clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift) |---->return ((u64) cycle * mult) >> shift; 問題在於如果mult太大,計算會發生溢出,因此mult值不能太大。內核假設cycle計數值被轉換后的最大時間值為:10分鍾(600s),原因在於CPU進入IDLE狀態后,時間信息不會被更新,只要在10分鍾內退出IDLE,clocksource的cycle計數值就可以被正確的轉換為相 應的時間,然后系統的時間信息可以被正確的更新。結果不一定是10分鍾,該值由clocksource_max_deferment進行計算,並保存在max_idle_ns字段中,tickless的代碼需要考慮這個值,以防止在使用動態時鍾時,系統保持IDLE狀態的時間過長。
時鍾事件設備(struct clock_event_device):向時鍾增加了事件功能,在未來的某個時刻發生。這種設備也稱為時鍾事件源(clock event source)。clock_event_device可以被編程,可以工作在周期模式或單次觸發模式,系統可以對它進行編程,以確定下次事件觸發的時間,clock_event_device主要用於實現普通定時器和高精度定時器,同時也用於產生tick事件,供給進程調度子系統使用。在軟件架構上,clock_evetn_device被分為兩層,與硬件相關的放在machine層,與硬件無關的通用代碼則被集中到了通用時間框架。
時鍾設備(struct tick_device):擴展了時鍾事件源的功能,各個時鍾事件定期觸發。但可以使用動態時鍾機制,在一定時間間隔內停止周期時鍾。
下圖引述自:http://blog.csdn.net/droidphone/article/details/8017604