作者簡介:
程磊,某手機大廠系統開發工程師,閱碼場榮譽總編輯,最大的愛好是鉆研Linux內核基本原理。
一、時間概念解析
1.1 時間使用的需求
1.2 時間體系的要素
1.3 時間的表示維度
1.4 時鐘與走時
1.5 時間需求之間的關系
二、時間子系統的硬件基礎
2.1 時鐘硬件類型
2.2 x86平臺上的時鐘
2.3 ARM平臺上的時鐘
三. 時間子系統的軟件架構
3.1 系統時鐘的設計
3.2 系統時鐘的實現
3.4 用戶空間API的實現
四. 總結回顧
一、時間概念解析
我們住在空間里,活在時間中。時間對我們來說是既熟悉又陌生。熟悉是因為我們每天都在時間的驅動下忙碌著,陌生是因為我們從來沒有停下來認真思考過時間是什么。今天我們先從對時間的使用需求開始說起。
1.1 時間使用的需求
我們對使用時間有三種需求:知時、定時和計時。知時就是我們需要知道現在的時間是多少,表達方式是時分秒、年月日。定時是我們需要在某個時間點被告知,時間點可以是相對的或者絕對的,告知可以是一次性的或者是周期性的,比如每天早上7:30叫我起床,是絕對時間點周期性告知,每隔10分鐘向我匯報一次情況,是相對時間點周期性告知。計時是我們需要知道某件事從開始到結束一共花了多少時間,比如大學運動會1000米賽跑,裁判在運動員起跑時按一下計時器,結束時再按一下計時器,得出某運動員跑一千米用了3分50秒。
1.2 時間體系的要素
為了達到知時的目的,我們首先需要建立時間體系的概念。時間體系由三個要素構成,1時間原點、2時間基本單位、3時間是否會暫停。我們把每天用的這個時間叫做自然時間,自然時間在計算機里面也叫做真實時間(Real Time),注意Real Time在這里是真實時間的意思,而不是實時的意思。自然時間有時候也會被叫做墻鐘時間(wall clock time),或者簡略為墻上時間(wall time),小時候家里墻上用掛鐘來看時間的同學立馬就能明白了。對自然時間建立的時間體系并不是唯一的,可以有不同的時間原點和時間基本單位。我們現在使用的公元紀年,它的時間原點是耶穌出生的那一年的一月一號零時零分零秒。其實我們也可以使用黃帝紀年,那現在就是5000多年了,也可以把建國的時間當做時間原點,那現在就是70幾年。公元紀年的時間基本單位是秒,好在全球的秒都是一樣的,沒有出現什么中秒、美秒、歐秒的區分,不然換算來換算去就會很麻煩。自然時間不會暫停,計算機里面的有些時間體系可能會暫停,這個我們后面再講。我們再來總結一下,現在全世界使用的自然時間體系是公元紀年,其時間原點是耶穌誕生當年的一月一號零時零分零秒,其時間基本單位是秒,時間流逝不會暫停。這就特別好,大家都是在同一個時間體系下生活,這樣討論時間就很方便,不用來回轉換了。如果不同國家使用的時間體系都不相同,時間體系的原點不同,時間基本單位也不相同,那相互之間來回轉換時間就會非常麻煩。
1.3 時間的表示維度
接下來我們說一下時間的表示維度,注意是時間的表示維度,不是時間的維度,時間本身的維度是一維的。如果我告訴你說現在的時間是六百三十七億六千五百七十九萬多秒,你是不是會一臉懵逼,反應不過來。雖然時間的基本單位是秒,但是我們如果直接用秒來表示時間,那將非常難以理解和記憶。為此我們建立了多層級的時間表示維度,60秒是一分鐘,60分鐘是一個小時,24小時是一天,365天是一年。然后我們說今天是某年某月某日,具體時間是幾時幾分幾秒,就非常方便了,很便于我們人類使用理解。對于人類來說時間精確到秒就足夠使用了,但是對于科學研究來說還需要更高的精度,于是我們把1秒的1/1000叫做毫秒,1毫秒的1/1000叫做微秒,1微秒的1/1000叫做納秒。這樣時間的表示維度就很豐富了,便于我們在不同的情況下使用。那么計算機中的時間表示維度是多少呢?人類善于理解多維度的時間表示,但是計算機卻善于處理單維度的時間表示。但是計算機用單維度的時間表示卻有個問題,如果用秒作為基本單位,那么精度顯然達不到,如果用納秒作為基本單位的話,數值又太大。所以計算機中的時間采用的是兩層表示維度,超過1秒的時間用秒表示,不夠一秒的時間用納秒表示,每10億納秒向前進位一秒。這樣計算機中時間處理就非常方便了。
1.4 時鐘與走時
想要實現知時的目的我們就需要有工具,這個工具就叫做時鐘(clock),有了時鐘我們就能夠快速準確地知道自然時間。下面我們來給時鐘下一個定義。時鐘,包括硬件的、軟件的、機械的、電子的,都是用來追蹤和記錄自然時間流逝的工具。下面我們再來說一個動詞,走時,大家一聽這個詞可能會不知道是啥意思。我再來說一句話,這個表走時非常精準,大家立馬就明白了是啥意思。我們再給走時下個定義,走時,是時鐘追蹤和記錄時間流逝的動作。為什么在這里要說個走時的概念呢,因為有了走時的概念,后面的很多東西都能很輕松地講清楚。
1.5 時間需求之間的關系
我們再來看一下知時、計時、定時三者之間的關系。先說知時和計時,其實兩者之間是可以相互轉化的。知時可以轉化為計時,我們在事情開始的時候記錄一下時間,在事情結束的時候記錄一下時間,兩者之間的時間差值就是計時。計時也可以轉化為知時,把計時的起點設置為某一個時間體系的時間原點,那么計時的結果就是知時的結果。計時是時間原點不特定的知時,知時是時間原點特定的計時。知時的結果是一個時間點,它是當前時間點到時間原點的一個時間段。計時的結果是時間段,它是相對于計時原點的時間點。明白了知時和計時之間的關系對于我們理解后面計算機的具體做法有很大的幫助。
下面我們再來看一下定時和知時、計時之間的關系。由于知時、計時可以相互轉換,所以它們可以放在一起討論同定時的關系。定時是需要知時、計時的支持的,如果沒有知時、計時,那么就沒法定時。絕對定時用知時作為基礎時間比較方便,相對定時用計時作為基礎時間比較方便。當然反過來也是可以的,因為知時計時是可以相互轉化的。還有一點就是定時可以用來作為時鐘實現走時的方法,這個在計算機時間管理的實現中就有所體現。
二、時間子系統的硬件基礎
在生活中我們有各種各樣的時鐘來滿足我們對時間的需求。比如以前家里常用的座鐘、掛鐘,個人也會戴個機械手表或者電子手表,這些時鐘既能知時也能定時(有鬧鐘功能),知時本身也能轉化為計時。所以一個時鐘就能滿足我們對時間的所有需求。在有些場合比如大學運動會時,會有專門的計時器,在比賽開始之前把計時器清零,比賽開始的時候按下開始,計時器開始走時,然后每當有一個人達到終點的時候按一下計時,計時器就會把當時的時間記下來,當所有人都跑完的時候按下結束,計時器停止走時。然后回看計時器就可以看到每個人跑完一千米的用時了。這種專用的計時器用來計時就非常方便。
現在家里有座鐘、掛鐘的人已經非常少了,戴手表的人也非常少了,大家基本都是用手機來看時間。手機不僅桌面上有時間顯示,里面還有個時鐘App,它和以前的時鐘功能差不多,而且更強大。時鐘App里面不僅能看時間(知時),還能定鬧鐘(絕對時間定時),里面還有一個計時器功能,實際上是倒計時,倒計時的本質是相對時間定時。里面還有一個秒表的功能,和我們前面說的運動會計時器的功能是一樣的,所以秒表是個專業的計時器。所以手機上的時鐘App完美得實現了我們對時間的所有需求。
手機實際上就是個計算機系統,而且安卓手機用的還是Linux內核。時鐘App所實現的功能需要Linux內核的支持,內核時間子系統的實現需要有硬件的支持。
2.1 時鐘硬件類型
計算機里面一共有三類時鐘硬件,分別是真時鐘RTC(Real Time Clock)、定時器Timer、計時器Counter。RTC相當于是手表、座鐘,定時器相當于是鬧鐘,計時器相當于是運動會中的計時器。注意是三類時鐘硬件,而不是三個,某一類時鐘可能有多個不同的硬件,某一個時鐘硬件也可能實現多種不同的時鐘類型。
計算機中還有其它的時鐘類型,比如晶振時鐘,是驅動CPU運行的周期信號,用來觸發和同步CPU內部的操作,我們常說某CPU是多少GHz,就是說這個時鐘晶振每秒向CPU發送多少信號(大概如此,實際上比較復雜,還有倍頻什么的,這里就不討論了)。晶振時鐘一般在CPU內部,有些嵌入式CPU的晶振在外部。時鐘晶振在軟件層不可見。還有一些設備也有自己的時鐘,還有相應的驅動可以控制它。由于這些時鐘都和時間子系統沒有關系,所以本文中就不討論它們了。
不同平臺的時鐘硬件各有不同,下面我們就來分別說說。
2.2 x86平臺上的時鐘
真時鐘RTC,在x86上的硬件實現也叫做RTC,和CMOS(計算機中有很多叫做CMOS的東西,但是是不同的概念,此處的CMOS是指BIOS設置保存數據的地方)是放在一起的。由于在關機后都需要供電,所以兩者放在了一起,由一個紐扣電池供電。所以有時候也會被人叫做CMOS時鐘。
定時器Timer,在UP時代是PIT(Programmable Interval Timer),它以固定時間間隔向CPU發送中斷信號。PIT可以在系統啟動時設置每秒產生多少個定時器中斷,一般設置是100,250,300,1000,這個值叫做HZ。到了SMP時代,PIT就不適用了,此時有多種不同的定時器。有一個叫做Local APIC Timer的定時器,它是和中斷系統相關的。中斷系統有一個全局的IO APIC,有NR_CPU個Local APIC,一個Local APIC對應一個CPU。所以在每個Local APIC都安裝一個定時器,專門給自己對應的CPU發送定時器中斷,就很方便。還有一個定時器叫做HPET(High Precision Event Timer),它是Intel和微軟共同研發的。它不僅是個定時器,而且還有計時器的功能。HPET不和特定的CPU綁定,所以它可以給任意一個CPU發中斷,這點和Local APIC Timer不同。
計時器Counter,RTC或者定時器雖然也可以實現計時器的目的,但是由于精度太差,所以系統都有專門的計時器硬件。計時器一般都是一個整數寄存器,以特定的時間間隔增長,比如說1納秒增加1,這樣兩次讀它的值就可以算出其中的時間差,而且精度很高。x86上最常用的計時器叫做TSC(Time Stamp Counter),是個64位整數寄存器。還有一個計時器叫做ACPI PMT(ACPI Power Management Timer),但是它是一個設備寄存器,需要通過IO端口來讀取。而TSC是CPU寄存器,可以直接讀取,讀取速度就非常快。
2.3 ARM平臺上的時鐘
暫略
三. 時間子系統的軟件架構
當我們知道了我們明白什么、我們有什么、我們想要什么的時候,我們就會知道我們應該怎么做。
從第一章我們明白了時間的基本概念,從第二章我們知道了我們有RTC、計時器、定時器三類底層硬件,從第三章和第四章我們知道了我們需要什么,那么我們就會很容易的分析出我們應該怎么做。
3.1 系統時鐘的設計
在用戶空間和內核空間都有知時的需求,而底層又有RTC硬件,這樣看來知時的需求很好實現啊,直接訪問RTC硬件就可以了。這么做行嗎?我們來分析一下。首先RTC是個外設,訪問RTC要走IO端口,而這相對來說是個很慢的操作。其次RTC的精度不夠,有的RTC精度是秒,有的是毫秒,這顯然是不夠用的。最后系統要實現很多時間體系,直接訪問RTC靈活性也不夠。所以直接訪問RTC是一個很差的設計,那么該怎么實現知時的需求呢?
我們先來回憶一下時鐘和走時的定義。
時鐘:包括硬件的、軟件的、機械的、電子的,都是用來追蹤和記錄自然時間流逝的工具。
走時:是時鐘追蹤和記錄時間流逝的動作。
我們用機械手表來解釋一個這個概念。手表里面有發條,發條的變化是在追蹤時間的流逝,然后發條通過齒輪把時間的變化記錄在表盤的時針、分針、秒針上,這樣我們就可以看到現在的時間是多少了。
我們再來回憶一下知時和計時之間的關系。知時是原點特定的計時,計時是原點不特定的知時,知時和計時可以相互轉化。知時相減就是計時,給計時一個特定的原點就是知時。計算機上既有RTC也有計時器,RTC雖然又慢精度又低,但是計時器又快精度又高啊。計時器的精度可以達到1納秒或者幾納秒,而且計時器大部分都是通過寄存器訪問的,速度非常快的。給計時器的起點一個確定的時間點,它就是RTC了啊。于是乎方案就出來了:Linux提出了系統時鐘的概念,它是一個軟件時鐘,相應的把RTC叫做硬件時鐘。系統時鐘是用一個變量xtime記錄現在的時間點,xtime的初始值用RTC來初始化,這樣就只用訪問RTC一次就可以了,然后xtime的值隨著計時器的增長而增長。xtime的值的更新有兩種情況,一種是調度器tick的時候從計時器更新一下,一種是讀xtime的時候從計時器更新一下。對于這個時鐘,計時器就相當于是發條,調度器tick就相當于是齒輪,xtime就相當于是時針、分針、秒針,一個軟件時鐘就這么設計好了。
Linux中用來實現系統時鐘的軟件體系叫做The Linux Timekeeping Architecture。如果我們把Timekeeping翻譯成“時間維護”,感覺意思好像不到位。好在我們前面講了“走時”的概念,把Timekeeping翻譯成“走時”的話,一下子就覺得意思到了。后面我們就用“Linux走時框架”這個詞了。在Linux走時框架中有三個基本概念:1.走時器(struct timekeeper),用來記錄一些基本數據,包括系統時鐘的當前時間值和其它全局時間體系的一些數據;2.時鐘源(struct clocksouce),是對計時器硬件的一種抽象;3.時鐘事件設備(struct clock_event_device),是對定時器硬件的一種抽象。這三個對象相互配合共同構成了系統時鐘。
系統可能會有很多計時器硬件和定時器硬件。在系統啟動時每個硬件都會初始化并注冊自己。注冊完之后系統會選擇一個最佳的時鐘源作為走時器的時鐘源,選擇一個最佳的時鐘事件設備作為更新系統時鐘的設備。系統啟動時會去讀取RTC的值來初始化系統時鐘的值,然后時鐘事件設備不斷產生周期性的定時器事件,在定時器事件處理函數中會讀取時鐘源的值,再減去上一次讀到的值,得到時間差,這個時間差就是系統時鐘應該前進的時間值,把這個值更新到走時器中,并相應更新其它時間體系的值。系統時鐘就是按照這種方式不斷地在走時。系統時鐘除了在啟動時和休眠喚醒時會去讀取RTC的值,其它時間都不會和RTC交換,兩者各自獨立地走時,互不影響。
用戶空間API讀取和設置的時間是系統時鐘,和硬件時鐘RTC沒有關系。如果要讀寫RTC的話,需要用ioctl RTC_SET_TIME對/dev/rtc進行操作。stime、settimeofday設置的系統時鐘,不會更改到RTC上,系統重啟后更改就消失了。通過/dev/rtc修改的硬件時間也不會更改到系統時間上,只有系統重啟后才會反映到系統時鐘上。對此有一個系統命令hwclock,它不僅可以修改RTC,也可以在兩者之間進行同步。hwclock --hctosys 把硬件時鐘同步到系統時鐘,hwclock --systohc把系統時鐘同步到硬件時鐘。事實上我們發現用settimeofday修改的系統時鐘在系統重啟后生效了,并沒有丟失,這是為什么呢?是因為系統默認的關機腳本里面會執行hwclock --systohc,把系統時鐘同步到硬件時鐘,所以我們修改的系統時鐘才不會丟失。
3.2 系統時鐘的實現
暫略
3.3 動態tick與定時器
低精度定時器是內核在早期就有的定時器接口,它的實現是靠調度器tick來驅動的。高精度定時器是隨著硬件和軟件的發展而產生的。調度器tick的HZ(每秒tick多少次)是可以配置,它的配置選項有4個,100,、250、300、1000,也即是說每次tick的間隔是10ms、4ms、3.3ms、1ms。所以用調度器tick來驅動低精度定時器是很合適的,tick的精度能滿足低精度定時器的精度。但是用調度器tick來驅動高精度定時器就不合適了,因為這樣高精度定時器的精度最多是1ms,達不到納秒的級別,這樣就算不上是高精度定時器了。所以對于高精度定時器來說,情況就正好反了過來,高精度定時器直接用硬件實現,然后創建一個軟件高精度定時器來模擬調度器tick。也就是說,對于只有低精度定時器的系統來說,是調度器tick驅動低精度定時器;對于有高精度定時器的系統來說,是高精度定時器驅動調度器tick,這個調度器tick再去驅動低精度定時器。
內核的低精度定時器接口和高精度定時器接口都是一次性的,不是周期性的。通過一次性的定時器可以實現周期性的定時器,方法是在每次定時器到期時再設置下一次的定時器,一直這樣就形成了周期性的。這里說的是定時器接口的一次性和周期性,而不是定時器硬件。下面我們再來看看定時器硬件是一次性的還是周期性的。定時器硬件本身可以是一次性的也可以是周期性的,也可以兩種模式都存在,由內核選擇使用哪一種。對于低精度定時器來說,它的定時器硬件可以是一次性的也可以是周期性的,由于調度器tick是周期性的,所以它的底層硬件就是周期性的。低精度定時器的精度最多是1ms,也就是定時器中斷做多一秒有1000次,這對于系統來說是可以承受的。但是對于高精度定時器來說,理論上它的定時器硬件也可以是周期性的。但是如果它的定時器硬件是周期性的,由于它的精度最多可以達到1納秒,也就是說1納秒要發生一次定時器中斷,每秒發生10億次。這對于系統來說是不可承受的,而且并不是每納秒都有定時器事件要處理,所以大部分定時器中斷是沒有用的。如果我們把1納秒1次中斷改為1微妙,1微妙1次中斷不就可以大大減少中斷的數量嘛,但是這樣定時器的精度就是1微妙,達不到1納秒的要求了。所以對于高精度定時器,底層的定時器硬件就只能是一次性的了。每次定時器事件到來的時候再去查看一下下一個最近的定時器事件什么時候到期,然后再去設置一下定時器硬件。這樣高精度定時器就可以一直運行下去了。但是我們的調度器tick也需要定時器中斷,而且是周期性的,怎么辦?好辦,創建一個到期時間為1ms的高精度定時器,每次到期的時候再設置一下繼續觸發,這樣就形成了一個1000HZ周期性的定時器事件,就可以驅動調度器tick。
下面我們講一下定時器和調度器tick的初始化過程,以x86為例。系統啟動時會先初始化timekeeping。然后hpet注冊自己,hpet既有定時器也有計時器,hpet定時器會成為系統定時器,hpet計時器會成為timekeeper的時鐘源。后面tsc計時器也會注冊自己,并成為最終的時鐘源。Local APIC Timer定時器也會注冊自己,并成為最終的per CPU tick device。hpet最終只能做broadcast 定時器了。系統在每次run local timer的時候都會檢測一下,如果不支持高精度定時器,就嘗試切換到動態tick模式,如果支持高精度定時器就切換到高精度定時器模式,此模式下會嘗試切換到動態tick模式。當高精度定時器和動態tick設置成功之后,Local APIC Timer會運行在一次性模式,調度器tick是由一個叫做sched_timer的高精度定時器驅動的。每次定時器到期時都會reprogram next event。
3.4 用戶空間API的實現
用戶空間API的實現文件如下表所示,具體實現細節就不再展開解釋了,大家搜索SYSCALL_DEFINE可以快速找到函數實現的地方。
四. 總結回顧
通過前面的介紹,我們了解了時間的基本概念,知道了計算機中實現時間子系統的基礎硬件,學會了時間的用戶空間API和內核接口,明白了時間子系統的設計原理。下面我們畫個圖總結一下:
審核編輯:湯梓紅
-
ARM
+關注
關注
134文章
9054瀏覽量
366839 -
Linux
+關注
關注
87文章
11231瀏覽量
208937 -
時鐘
+關注
關注
10文章
1720瀏覽量
131371
原文標題:深入理解Linux時間子系統
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論