1. 程式人生 > >Linux時間子系統(二) 軟件架構

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

wow 狀態 獲取 文章 一次 locks 系統時間 發送 clas

一、前言

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

二、背景介紹

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

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

技術分享圖片

首先,在每個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。

2、為何會引入新的時間子系統軟件架構?

但是隨著技術發展,出現了下面兩種新的需求:

(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圖如下所示:

技術分享圖片

在引入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模塊的接口。

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

1、文件匯整

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)。

2、通用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這個配置項。由此可見,在軟件框架在演化的過程中,如果這是一個被其他模塊使用的基礎組件,我們不可能是完全推到重來,必須考慮對舊的軟件的兼容性,雖然是一個沈重的負擔,但是必須這麽做。

3、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中斷不再周期性觸發,只會按照需要觸發),為了兼容舊的系統,新的內核仍然支持了這個選項。

4、timer模塊的配置

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

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

根據上一節的描述,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系統。

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

1、使用低精度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的運作毫無壓力,和過去一樣。

2、低精度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的工作原理同上。

3、高精度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狀態,從而減少功耗。

4、高精度timer + 周期性Tick

這種配置不多見,多半是由於硬件無法支持one shot的clock event device,這種情況下,整個系統仍然是運行在周期tick的模式下。

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