天天看點

Linux時間子系統之(一):時間的基本概念【轉】

本文使用Q & A的方式來和大家以前探讨一下時間的基本概念

一、什麼是時間?

這個問題實在是太複雜了,我都不知道這是一個實體學、宇宙學、還是熱力學異或是哲學問題,我隻是想從幾個側面來了解一下時間這個概念。本節内容都是我坐在公共汽車上瞎想的,對實體學有興趣的人可以指出我的錯誤(一個搞linux kernel的人不會有太深刻的實體學知識的),對Linux時間子系統有興趣的人還是忽略這個小節吧。

1、時間和空間以及相對性

有沒有絕對時間的概念呢?時間是否是獨立于一切存在的呢?相信有絕對時間的存在比較符合人類的思維,也就是說,有一根絕對時間軸存在,任何事件都可以投射在這個時間軸的某個點上,不論觀察者處于什麼狀态,大家共享一個絕對的時間軸。在這樣的時間架構下,同時的概念是絕對的,隻要發生在時間軸的同一點上,那麼兩個事件就是同時發生。如果兩個事件不同時發生,那麼他們之間的間隔也是絕對的,所有感覺到的時間間隔都是相同的。與之相對的是絕對空間的概念。任何事件發生的地點都可以對應到絕對空間中的一個位置點,兩個位置點的長度是絕對的,任何觀察者測量的結果都是一樣的,無論觀察者處于什麼樣的狀态。

當然,了解狹義相對論的同學應該是可以建立起來自己的時空觀,其實沒有絕對的時間和空間(時間和空間是相對的而且是有關聯的,組成四維時空),同時是相對的(相對于自己的參考系),事件A和事件B先後發生,位于不同的慣性參考系的觀察者可以不同的觀測結果,可能一個認為A和B相關1秒,而另外一個慣性參考系的人認為A和B之間相隔1.2秒。當然,我們研究linux kernel中的時間子系統,這裡的參考系不可能是以光速級别在運動,是以傳統的牛頓的時空觀也是OK的,它是愛因斯坦時空觀的一個特例。

2、時間的方向

時間有沒有起點和終點?時間的箭頭指向何方?什麼是現在?什麼是未來?時間是不是可逆的?有沒有可能穿越?

根據熱力學第二定律,對于一個孤立系統,其内部自發進行的與熱相關的過程必然向熵增的方向進行。一個孤立系統如果不受外界任何影響,且系統最終處于平衡态,那麼在平衡态時,系統的熵取最大值。從這個角度看,時間的方向就是熵增的方向,并且是不可逆的。

此外,當時間沿着它的方向流動的時候,是連續的,還是有一個基本的機關,一份一份的流動?我想如果能量的交換(熱交換)是一份一份的,那麼時間也是離散的。

3、感覺時間

時間是和變化相關的,如果一切處于靜止态,沒有位移、沒有能量的變化,那麼時間還有存在的意義嗎?物體從一個位置A移動到了另外一個位置B,觀察者通過感覺位置的變化來感覺時間,判斷事件的先後順序。當一切處于靜止狀态,沒有任何的運動,各種恒星和行星都處于靜止态,電子不再圍繞原子核進行高速運動,宇宙中的所有的系統沒有能量的交換,那麼時間也就停止了,或者說不存在了。本質上,宇宙的狀态和時間軸應該是一一對應的,如果宇宙的狀态隻有一種,那麼時間軸就停止在一個點上,不再流動了。

二、度量時間

1、如何定義秒?

在定義秒這個術語的時候是有兩個标準的:

(1)以铯133的振蕩頻率來定義秒。

(2)依據地球自轉和公轉來定義秒。

毫無疑問,第二種方法是符合人類習慣的,大家已經習慣了一天24個小時,每個小時60分鐘,一分鐘包括60秒數。我們可以用平均太陽日的1/86400來定義,也可以用繞太陽軌道公轉一年的時間來定義秒,但是這種基于天體運動而定義的秒其實是不精準的,因為地球的自轉和公轉的時間不是一個恒定的值。由此,以铯133的振蕩頻率這樣的實體屬性來定義秒實際上可以賦予秒一個恒定的時間長度。

對于linux kernel而言,它采用了第一種定義的秒,并且參考點(linux epoch)也是UTC時間,UTC這個時間标準就是采用第一種方式來定義時間的。曆史上,POSIX标準曾經将時間參考點設定為1970年1月1日0點0分0秒(GMT),GMT就是采用第二種方法定義時間的,後面修正為UTC時間。

定義了秒之後,我們可以進一步細分為毫秒、微秒、納秒等,也可以組織成分鐘、小時、日、月、年等概念。

2、如何處理閏秒(leap seconds)?

正是因為UTC時間和我們日常使用的月曆時間有差異,是以才有閏秒(leap second)這個概念。雖然你UTC時間使用嚴格的定義,使用原子鐘作為參考,秒的概念被精準的定義,但是,它還是要向現實低頭,因為廣大人民群衆喜聞樂見的是和地球自轉和公轉相關的月曆時間。為了讓UTC時間和人民群衆的月曆時間保持一緻,在适當的時間點(誤差累積到1秒的時候)對UTC時間進行增加或者減少一秒的的操作,這就是閏秒的概念了。

由于目前地球的自轉一直在變慢,是以我們的月曆時間(GMT)是在變長的,是以目前的閏秒調整都是+1秒的操作。未來是否地球自轉會變快,誰知道呢?

對于linux kernel而言,雖然epoch是UTC時間,不過核心不考慮閏秒問題。對于一個天文學意義上的較短的時間段(例如15個月)UTC和GMT是基本相同的,大部分的應用其實也基本是可以正常工作的。對這個問題的解決來自具體的應用場景和系統實作,核心隻是支援了NTP協定,可以和外部的時間伺服器進行同步,而外部的那些時間同步伺服器都是支援UTC的,讓它們去考慮閏秒吧,linux kernel隻需要保持和它們同步就OK了。

3、如何處理閏年

科學必須用來指導人類的生産和生活,否則就沒有意義了。在精确定義了時間的度量後,我們可以用科學的方法來組織時間。例如:地球繞太陽公轉的周期是365天6小時9分鐘10秒(或者說365.2564天)。不是整數天則需要特别處理,例如:考慮平年365天,閏年366天,每四年有一個閏年。如果這樣的話,0.2564 x 4 = 1.0256,也就是說,這樣的處理就多算了0.0256天,怎麼辦?累積長了也是一個不小的誤差。根據計算,每100次閏年計算(周期400年),就多了2.56天,是以,定義在能被100整除的年份中(如果計算周期是400年,那麼有4個這樣的年份),選取3個不進行閏年,隻有能被400整除的年份才閏年。這樣計算的結果就是每400年就少算了0.44天,這樣的計算可以無窮無盡計算下去。

4、如何定義基準?

時間有絕對和相對的概念。例如1小時候我們就去吃飯。這裡就是一個相對時間的概念,1小時是相對目前時間點而言。1949年10月1日,中華人民共和國成立,這裡的時間就是絕對時間。當然,實際上,這裡的絕對時間也是相對時間,隻不過所有地球上的人都使用同一個基準點的時候,這個時間表示就是一個普遍适用的絕對時間了。

對于一個系統而言,需要定義一個epoch,所有的時間表示是基于這個基準點的,對于linux而言,采用了和unix epoch一樣的時間點:1970年1月1日0點0分0秒(UTC)。NTP協定使用的基準點是:1900年1月1日0點0分0秒(UTC)。GPS系統使用的基準點是:1980年1月6日0點0分0秒(UTC)。每個系統都可以根據自己的邏輯定義自己epoch,例如unix epoch的基準點是因為unix作業系統是在1970年左右成型的。

三、Linux kernel中和時間相關的基本概念

1、系統時鐘(system clock)

我們在之前的文章中說過,時間就像是一條沒有起點、沒有終點的線段,除了要給出度量機關(例如:秒),還有給出參考點。對于linux kernel而言,這個參考點就是Linux Epoch。所謂linux Epoch其實就是1970年1月1日0點0分0秒(UTC)的時間點。雖然人類喜歡年、月、日、時、分、秒這些概念,不過對于計算機,更喜歡使用目前的時間點到linux epoch的秒數。一個符合POSIX标準的系統必須提供系統時鐘,以不小于秒的精度來記錄到linux epoch的時間值。

核心支援的system clock定義如下:

#define CLOCK_REALTIME            0 

#define CLOCK_MONOTONIC            1

……

CLOCK_REALTIME是描述真實世界的時鐘(這裡的英文realtime有兩個意思,一個表示真實的時間的意思,另外一個是實時性的意思,其實不止中文有歧義,英文也是一樣的,需要根據上下文判斷,我們這個場景當然不是講實時性),就類似挂在牆上的鐘表一樣,告知人類目前的時間。當然,家裡的鐘表你當然可以按照你的意願向前或者向後調整。CLOCK_MONOTONIC是一個禁止人為設定的真實世界的時鐘,你沒有辦法設定它,但是可以通過NTP協定進行調整。更多的system clock ID會在後續的文章中給出詳細定義

2、 broken-down POSIX time

在time line上以linux epoch為參考點,用到參考點的秒數來表示timeline上的某個時間點的方法對于計算機而言當然是友善的,不過對于使用計算機的人類而言,我們更習慣broken-down time,也就是将那個冰冷的到參考點的秒數值分解成年月日時分秒。在核心中用下面的資料結構表示(注釋非常詳細,這裡不再贅述):

struct tm { 

    /* 

     * the number of seconds after the minute, normally in the range 

     * 0 to 59, but can be up to 60 to allow for leap seconds 

     */ 

    int tm_sec; 

    /* the number of minutes after the hour, in the range 0 to 59*/ 

    int tm_min; 

    /* the number of hours past midnight, in the range 0 to 23 */ 

    int tm_hour; 

    /* the day of the month, in the range 1 to 31 */ 

    int tm_mday; 

    /* the number of months since January, in the range 0 to 11 */ 

    int tm_mon; 

    /* the number of years since 1900 */ -----以NTP epoch為基準點 

    long tm_year; 

    /* the number of days since Sunday, in the range 0 to 6 */ 

    int tm_wday; 

    /* the number of days since January 1, in the range 0 to 365 */ 

    int tm_yday; 

};

3、不同精度的時間表示

傳統的unix使用了基于秒的時間定義,相關的資料結構是time_t:

typedef long        time_t;

time_t是POSIX标準定義的一個以秒計的時間值。例如大家都比較熟悉的time函數,可以擷取一個從linux Epoch到目前時間點的秒值,該函數的傳回值類型就是time_t的。如果time_t被實作成一個signed 32-bit integer,那麼實際上在2038年的時候就會有溢出問題。

随着應用的發展,秒精度已經無法滿足要求,是以出現了微秒精度的時間表示:

struct timeval {  

    time_t        tv_sec;        /* seconds */  

    suseconds_t    tv_usec;    /* microseconds */ 

};

struct timeval 的概念和time_t是一樣的,隻不過多了一個微秒的成員,将時間的精度推進到微秒級别。在計算目前時刻到epoch時間點的微秒數值的時候可以使用公式:tv_sec x 10^6 + tv_usec。

由于實時應用程式的需求,POSIX标準最終将時間精度推進到納秒,納秒精度的時間表示如下:

struct timespec {  

    time_t    tv_sec;            /* seconds */ 

    long        tv_nsec;        /* nanoseconds */ 

};

根據POSIX标準,timeline上一個時間點的值用struct timespec來表示,它應該最少包括:

成員資料類型 成員的名字 描述
time_t tv_sec 該時間點上的秒值,僅在大于或者等于0的時候有效。
long tv_nsec 該時間點上的ns值,僅在大于或者等于0,并且小于10^9納秒的時候有效。

繼續閱讀