Linux時間子系統(二) 軟件架構

1、前言linux

本文的主要內容是描述內核時間子系統的軟件框架。首先介紹了從舊的時間子系統遷移到新的時間子系統的源由,介紹新的時間子系統的優點。第三章彙整了時間子系統的相關文件以及內核配置。最後描述各類內核配置下的時間子系統的數據流和控制流。架構

 

2、背景介紹框架

一、傳統內核時間子系統的軟件架構less

讓咱們先回到遠古的2.4內核時代,其內核的時間子系統的軟件框架以下:異步

old time

首先,在每一個architecture相關的代碼中要有實現clock event和clock source模塊。這裏聽起來名字是高大上,實際上,這裏僅僅是借用了新時間子系統的名詞,對於舊的內核,clock event就是經過timer硬件的中斷處理函數完成的,在此基礎上能夠構建tick模塊,tick模塊維護了系統的tick,例如系統存在10ms的tick,每次tick到來的時候,timekeeping模塊就增長系統時間,若是timekeeping徹底是tick驅動,那麼它精度只能是10ms,爲了更高精度,clock source模塊就是一個提供tick之間的offset時間信息的接口函數。函數

最初的linux kernel中只支持低精度的timer,也就是基於tick的timer。若是內核採用10ms的tick,那麼低精度的timer的最高的精度也只有10ms,想要取得us甚至ns級別的精度是不可能的。各個內核的驅動模塊均可以使用低精度timer實現本身的定時功能。各個進程的時間統計也是基於tick的,內核的調度器根據這些信息進行調度。System Load和Kernel Profiling模塊也是基於tick的,用於計算系統負荷和進行內核性能剖析。性能

從用戶空間空間來看,有兩種需求,一種是獲取或者設定當前的系統時間的接口函數:例如time,stime,gettimeofday等。另一種是timer相關的接口函數:例如setitimer、alarm等,當timer到期後會向該進程發送signal。url

二、爲什麼會引入新的時間子系統軟件架構?spa

可是隨着技術發展,出現了下面兩種新的需求:.net

(1)嵌入式設備須要較好的電源管理策略。傳統的linux會有一個週期性的時鐘,即使是系統無事可作的時候也要醒來,這樣致使系統不斷的從低功耗(idle)狀態進入高功耗的狀態。這樣的設計不符合電源管理的需求。

(2)多媒體的應用程序須要很是精確的timer,例如爲了不視頻的跳幀、音頻回放中的跳動,這些須要系統提供足夠精度的timer

和低精度timer不一樣,高精度timer使用了人類的最直觀的時間單位ns(低精度timer使用的tick是和內核配置相關,不夠直接)。本質上linux kernel提供了高精度timer以後,其實沒必要提供低精度timer了,不過因爲低精度timer存在了很長的歷史,而且在滲入到內核各個部分,若是去掉低精度timer很容易引發linux kernel穩定性和健壯性的問題,所以目前的linux kernel保持了低精度timer和高精度timer並存。

在新的需求的推進下,內核開發者對linux的時間子系統的軟件框架進行修改,讓代碼層次更清晰,同時又是靈活可配置的,一個示意性的block圖以下所示:

timehl

在引入multi-core以後,過去HW timer的功能被分紅兩個部分,一個是free running的system counter,是全局的,不屬於任何一個CPU。另一部分就是產生定時事件的HW block,咱們稱之timer,timer硬件被嵌入到各個cpu core中,所以,咱們更準確的稱之爲CPU local Timer,這些timer都是基於一個Global counter運做的。在驅動層,咱們提供一個clock source chip driver的模塊來驅動硬件,這是模塊是和硬件體系結構有關的。若是系統內存在多個HW timer和counter block,那麼系統中可能會存在多個clock source chip driver。

面對形形色色的timer和counter硬件,linux kernel抽象出了通用clock event layer和通用clock source模塊,這兩個模塊和硬件無關。底層的clock source chip driver會調用通用clock event和clock source模塊的接口函數,註冊clock source和clock event設備。clock source設備對應硬件的system free running counter,提供了一個基礎的timeline。固然了,實際的timeline象一條直線,無限延伸。對於kernel而言,其timeline是構建在system free running counter之上的,所以clocksource 對應的timeline存在溢出問題。若是選用64bit的HW counter,而且輸入頻率不那麼高,那麼溢出時間可能會長達50年甚至更多,那麼從應用的角度來看,能維持50年左右的timeline也能夠接受。若是說clock source是一個time line,那麼clock event是在timeline上指定的點產生clock event的設備,之因此能產生異步事件,固然是基於中斷子系統了,clock source chip driver會申請中斷並調用通用clock event模塊的callback函數來通知這樣的異步事件。

tick device layer基於clock event設備進行工做的:通常而言,每一個CPU造成本身的一個小系統,有本身的調度、有本身的進程統計等,這個小系統都是擁有本身的tick設備,並且是惟一的。對於clock event設備而言就不是這樣了,硬件有多少個timer硬件就註冊多少個clock event device,各個cpu的tick device會選擇本身適合的那個clock event設備。tick device能夠工做在periodic mode或者one shot mode,固然,這是和系統配置有關。所以,在tick device layer,有多少個cpu,就會有多少個tick device,咱們稱之local tick device。固然,有些事情(例如整個系統的負荷計算)不適合在local tick驅動下進行,所以,全部的local tick device中會有一個被選擇作global tick device,該device負責維護整個系統的jiffies,更新wall clock,計算全局負荷什麼的。

高精度的timer須要高精度的clock event,工做在one shot  mode的tick device工提供高精度的clock event。所以,基於one shot mode下的tick device,系統實現了高精度timer,系統的各個模塊可使用高精度timer的接口來完成定時服務。雖然有了高精度timer的出現, 內核並無拋棄老的低精度timer機制(內核開發人員試圖整合高精度timer和低精度的timer,不過失敗了,因此目前內核中,兩種timer是同時存在的)。當系統處於高精度timer的時候(tick device處於one shot mode),系統會setup一個特別的高精度timer(能夠稱之sched timer),該高精度timer會週期性的觸發,從而模擬的傳統的periodic tick,從而推進了傳統低精度timer的運轉。所以,一些傳統的內核模塊仍然能夠調用經典的低精度timer模塊的接口。

 

3、時間子系統的文件整理

一、文件彙整

linux kernel 時間子系統的源文件位於linux/kernel/time/目錄下,咱們整理以下:

文件名 描述
time.c
timeconv.c
time.c文件是一個向用戶空間提供時間接口的模塊。具體包括:time, stime, gettimeofday, settimeofday,adjtime。除此以外,該文件還提供一些時間格式轉換的接口函數(其餘內核模塊使用),例如jiffes和微秒之間的轉換,日曆時間(Gregorian date)和xtime時間的轉換。xtime的時間格式就是到linux epoch的秒以及納秒值。
timeconv.c中包含了從calendar time到broken-down time之間的轉換函數接口。
timer.c 傳統的低精度timer模塊,基本tick的。
time_list.c
timer_status.c
向用戶空間提供的調試接口。在用戶空間,能夠經過/proc/timer_list接口能夠得到內核中的時間子系統的相關信息。例如:系統中的當前正在使用的clock source設備、clock event設備和tick device的信息。經過/proc/timer_stats能夠獲取timer的統計信息。
hrtimer.c 高精度timer模塊
itimer.c interval timer模塊
posix-timers.c
posix-cpu-timers.c
posix-clock.c
POSIX timer模塊和POSIX clock模塊
alarmtimer.c alarmtimer模塊
clocksource.c
jiffies.c
clocksource.c是通用clocksource driver。其實也能夠把system tick也當作一個特定的clocksource,其代碼在jiffies.c文件中
timekeeping.c
timekeeping_debug.c
timekeeping模塊
ntp.c NTP模塊
clockevent.c clockevent模塊
tick-common.c
tick-oneshot.c
tick-sched.c
這三個文件屬於tick device layer。
tick-common.c文件是periodic tick模塊,用於管理週期性tick事件。
tick-oneshot.c文件是for高精度timer的,用於管理高精度tick時間。
tick-sched.c是用於dynamic tick的。
tick-broadcast.c
tick-broadcast-hrtimer.c
broadcast tick模塊。
sched_clock.c 通用sched clock模塊。這個模塊主要是提供一個sched_clock的接口函數,調用該函數能夠獲取當前時間點到系統啓動之間的納秒值。
底層的HW counter實際上是千差萬別的,有些平臺能夠提供64-bit的HW counter,所以,在那樣的平臺中,咱們能夠不使用這個通用sched clock模塊(不配置CONFIG_GENERIC_SCHED_CLOCK這個內核選項),而在本身的clock source chip driver中直接提供sched_clock接口。
使用通用sched clock模塊的好處是:該模塊擴展了64-bit的counter,即便底層的HW counter比特數目不足(有些平臺HW counter只有32個bit)。

 

二、通用clock source和clock event的內核配置

(1)CONFIG_GENERIC_CLOCKEVENTS和CONFIG_GENERIC_CLOCKEVENTS_BUILD:使用新的時間子系統的構架,若是不配置,那麼將使用第二節描述的舊的時間子系統架構。

(2)曾經有一個CONFIG_ GENERIC_TIME的配置項對應clocksource的配置,不過在某個版本中刪除了,也就是說目前的內核都是使用通用clocksource模塊的,沒法再退回到過去使用arch相關的clocksource的時代。爲了兼容舊風格的timekeeping接口,kernel仍然提供了CONFIG_ARCH_USES_GETTIMEOFFSET這個配置項。因而可知,在軟件框架在演化的過程當中,若是這是一個被其餘模塊使用的基礎組件,咱們不多是徹底推到重來,必須考慮對舊的軟件的兼容性,雖然是一個沉重的負擔,可是必須這麼作。

三、tick device的配置

若是選擇了新的時間子系統的軟件架構(配置了CONFIG_GENERIC_CLOCKEVENTS),那麼內核會打開Timers subsystem的配置選項,主要是和tick以及高精度timer配置相關。和tick相關的配置有三種,包括:

(1)不管什麼時候,都啓用用週期性的tick,即使是在系統idle的時候。這時候要配置CONFIG_HZ_PERIODIC選項。

(2)在系統idle的時候,停掉週期性tick。對應的配置項是CONFIG_NO_HZ_IDLE。配置tickless idle system也會同時enable NO_HZ_COMMON的選項。

(3)Full dynticks system。即使在非idle的狀態下,也就是說cpu上還運行在task,也可能會停掉tick。這個選項和實時應用相關。對應的配置項是CONFIG_NO_HZ_FULL。配置Full dynticks system也會同時enable NO_HZ_COMMON的選項。本文不描述該系統,有興趣的同窗能夠自行閱讀。

上面的三個選項只能是配置其一。上面描述的是新的內核配置方法,對於舊的內核,CONFIG_NO_HZ用來配置dynamic tick或者叫作tickless idle system(非idle時有周期性tick,idle狀態,timer中斷再也不周期性觸發,只會按照須要觸發),爲了兼容舊的系統,新的內核仍然支持了這個選項。

四、timer模塊的配置

和高精度timer相關的配置比較簡單,只有一個CONFIG_HIGH_RES_TIMERS的配置項。若是配置了高精度timer,或者配置了NO_HZ_COMMON的選項,那麼必定須要配置CONFIG_TICK_ONESHOT,表示系統支持支持one-shot類型的tick device。

五、 如何進行時間子系統的內核配置

根據上一節的描述,linux內核能夠有下面的兩種時間子系統的構架:

(1)新的通用時間子系統軟件框架(配置了CONFIG_GENERIC_CLOCKEVENTS)

(2)傳統時間子系統軟件框架(不配置CONFIG_GENERIC_CLOCKEVENTS,配置CONFIG_ARCH_USES_GETTIMEOFFSET)

對於咱們工程人員,除非你是維護一箇舊的系統,不然固然使用新的通用時間子系統軟件框架了,這時候可能的配置包括:

(1)使用低精度timer和週期tick。傳統的linuxer應該會喜歡這個配置,保持和傳統的unix的一致性。

(2)使用低精度timer和Dynamic tick

(3)使用高精度timer和週期tick

(4)使用高精度timer和Dynamic tick。新潮的linux應該會喜歡這個配置,一個字,cool……

注:本文主要描述普通的dynamic tick系統(tickless idle system),後續會有專門的文章描述full dynamic tick系統。

 

4、時間子系統的數據流和控制流

一、使用低精度timer + 週期tick

咱們首先看週期性tick的實現。起始點必定是底層的clock source chip driver,該driver會調用註冊clock event的接口函數(clockevents_config_and_register或者clockevents_register_device),一旦增長了一個clock event device,須要通知上層的tick device layer,畢竟有可能新註冊的這個device更好、更適合某個tick device呢(經過調用tick_check_new_device函數實現)。要是這個clock event device被某個tick device收留了(要麼該tick device以前沒有匹配的clock event device,要麼新的clock event device更適合該tick device),那麼就啓動對該tick device的配置(參考tick_setup_device)。根據當前系統的配置狀況(週期性tick),會調用tick_setup_periodic函數,這時候,該tick device對應的clock event device的clock event handler被設置爲tick_handle_periodic。底層硬件會週期性的產生中斷,從而會週期性的調用tick_handle_periodic從而驅動整個系統的運轉。須要注意的是:即使是配置了CONFIG_NO_HZ和CONFIG_TICK_ONESHOT,系統中沒有提供one shot的clock event device,這種狀況下,整個系統仍然是運行在週期tick的模式下。

下面來到低精度timer模塊了,其實即使沒有使能高精度timer,內核也會把高精度timer模塊的代碼編譯進kernel的image中,這一點能夠從Makefile文件中看出:

obj-y += time.o timer.o hrtimer.o itimer.o posix-timers.o posix-cpu-timers.o

高精度timer老是會被編入最後的kernel中。在這種構架下,各個內核模塊也能夠調用linux kernel中的高精度timer模塊的接口函數來實現高精度timer,可是,這時候高精度timer模塊是運行在低精度的模式,也就是說這些hrtimer雖然是按照高精度timer的紅黑樹進行組織,可是系統只是在每一週期性tick到來的時候調用hrtimer_run_queues函數,來檢查是否有expire的hrtimer。毫無疑問,這裏的高精度timer也就是沒有意義了。

因爲存在週期性tick,低精度timer的運做毫無壓力,和過去同樣。

二、低精度timer + Dynamic Tick

系統開始的時候並非直接進入Dynamic tick mode的,而是經歷一個切換過程。開始的時候,系統運行在週期tick的模式下,各個cpu對應的tick device的(clock event device的)event handler是tick_handle_periodic。在timer的軟中斷上下文中,會調用tick_check_oneshot_change進行是否切換到one shot模式的檢查,若是系統中有支持one-shot的clock event device,而且沒有配置高精度timer的話,那麼就會發生tick mode的切換(調用tick_nohz_switch_to_nohz),這時候,tick device會切換到one shot模式,而event handler被設置爲tick_nohz_handler。因爲這時候的clock event device工做在one shot模式,所以當系統正常運行的時候,在event handler中每次都要reprogram clock event,以便正常產生tick。當cpu運行idle進程的時候,clock event device再也不reprogram產生下次的tick信號,這樣,整個系統的週期性的tick就停下來。

高精度timer和低精度timer的工做原理同上。

三、高精度timer + Dynamic Tick

一樣的,系統開始的時候並非直接進入Dynamic tick mode的,而是經歷一個切換過程。系統開始的時候是運行在週期tick的模式下,event handler是tick_handle_periodic。在週期tick的軟中斷上下文中(參考run_timer_softirq),若是知足條件,會調用hrtimer_switch_to_hres將hrtimer從低精度模式切換到高精度模式上。這時候,系統會有下面的動做:

(1)Tick device的clock event設備切換到oneshot mode(參考tick_init_highres函數)

(2)Tick device的clock event設備的event handler會更新爲hrtimer_interrupt(參考tick_init_highres函數)

(3)設定sched timer(也就是模擬週期tick那個高精度timer,參考tick_setup_sched_timer函數)

這樣,當下一次tick到來的時候,系統會調用hrtimer_interrupt來處理這個tick(該tick是經過sched timer產生的)。

在Dynamic tick的模式下,各個cpu的tick device工做在one shot模式,該tick device對應的clock event設備也工做在one shot的模式,這時候,硬件Timer的中斷不會週期性的產生,可是linux kernel中不少的模塊是依賴於週期性的tick的,所以,在這種狀況下,系統使用hrtime模擬了一個週期性的tick。在切換到dynamic tick模式的時候會初始化這個高精度timer,該高精度timer的回調函數是tick_sched_timer。這個函數執行的函數相似週期性tick中event handler執行的內容。不過在最後會reprogram該高精度timer,以即可以週期性的產生clock event。當系統進入idle的時候,就會stop這個高精度timer,這樣,當沒有用戶事件的時候,CPU能夠持續在idle狀態,從而減小功耗。

四、高精度timer + 週期性Tick

這種配置很少見,多半是因爲硬件沒法支持one shot的clock event device,這種狀況下,整個系統仍然是運行在週期tick的模式下。

相關文章
相關標籤/搜索