天天看點

linux核心時間管理,linux核心時間管理

前言:

Linux中如何對時間進行管理?時鐘節拍的概念及延時函數的用法很多同學都用不好,下面我給大家總結一下。

一,linux時鐘運作機制

1,linux時鐘運作機制

• 大部分PC機中有兩個時鐘源,分别是實時時鐘(RTC)和 作業系統(OS)時鐘

• 實時時鐘也叫CMOS時鐘,它靠電池供電,即使系統斷電,也可以維持日期和時間。

• RTC和OS時鐘之間的關系通常也被稱作作業系統的時鐘運作機制

• 不同的作業系統,其時鐘運作機制也不同

linux中的時鐘機制大緻如下圖所示

linux核心時間管理,linux核心時間管理

linux中時鐘機制

由上圖可知:

RTC是硬體時鐘,它為整個計算機提供一個計時标準,是原始底層的時鐘資料,由紐扣電池供電,系統斷電後仍然在工作

OS時鐘産生于PC主機闆上的定時/計數晶片,由作業系統控制這個晶片的工作,OS時鐘的基本機關就是該晶片的計數周期,開機時作業系統取得RTC中的時間資料來初始化OS時鐘,是以它隻是在開機有效,由作業系統控制,已被稱為軟時鐘或系統時鐘。作業系統通過OS時鐘提供給應用程式和時間有關的服務。

擴充:OS時鐘其本質是一個計數器,計數器從計數初值開始,每收到一次脈沖信号,計數器減1,當減至0時,就會輸出高電平或低電平,然後擷取重載值重新從初值開始計數,不斷循環,這樣就得到一個輸出脈沖,這個脈沖作用中斷控制器上,産生中斷信号,觸發時鐘中斷。

2,OS時鐘中斷

• OS時鐘是由可程式設計定時/計數器産生的輸出脈沖觸發中斷而産生的,而輸出脈沖的周期叫做一個“時鐘節拍”(Tick,又稱滴答),(中斷觸發時會進入中斷處理函數,使jiffies+1)

• 作業系統的“時間基準” 由設計者決定,Linux的時間基準是1970年1月1日淩晨0點

• OS時鐘記錄的時間就是系統時間。系統時間以“時鐘節拍”為機關

•時鐘中斷觸發的頻率,由核心HZ來确定,系統啟動時會按照定義的HZ值對硬體進行設定

比如對HZ的定義如下:

#define  Hz100

核心時間頻率:表示每秒鐘觸發100次時鐘中斷,即每10ms觸發一次,

每次中斷jiffies+1,,則每秒jiffies增加了100,

• Linux中用全局變量 jiffies表示系統自啟動以來的時鐘節拍數目(時鐘中斷觸發的次數)

是以系統運作的時間以s為機關計數,  就等于 jiffies/HZ

核心啟動時将該變量初始化為0,此後,每次時鐘中斷處理程式都會增加該變量的值,每秒鐘觸發中斷的次數為Hz,

3、實際時間

實際時間就是現實中鐘表上顯示的時間,其實核心中并不常用這個時間,主要是使用者空間的程式有時需要擷取目前時間,是以核心中也管理着這個時間。

實際時間的擷取是在開機後,核心初始化時從RTC讀取的。

核心讀取這個時間後就将其放入核心中的 xtime 變量中,并且在系統的運作中不斷更新這個值。

目前實際時間(牆上時間):  xtime.tv_sec以秒為機關,存放着自1970年7月1日(UTC)以來經過的時間,1970年1月1日被稱為紀元。多數Unix系統的牆上時間都是基于該紀元而言的。xtime.tv_nsec記錄自上一秒開始經過的納秒數。

在中

extern struct timespec xtime;

#ifndef _STRUCT_TIMESPEC

#define _STRUCT_TIMESPEC

struct timespec {      

time_t  tv_sec;    

long    tv_nsec;    

};

#endif

從使用者空間取得牆上時間的主要接口是gettimeofday(),在核心中對應的系統調用為sys_gettimeofday():

雖然核心也實作了time()系統調用,但是gettimeofday()幾乎完全取代了它。C庫函數也提供了牆上時間相關的庫調用,比如ftime(),ctime()。

除了更新xtime時間外,核心不會想使用者空間程式那樣頻繁的使用xtime。但是,在檔案系統的實作代碼中存放通路時間戳(建立,存取,修改等)需要使用xtime。

4,時鐘中斷處理程式----作業系統的脈搏

每一次時鐘中斷的産生都觸發下列幾個主要的操作:

– 給jiffies變量加 1

– 更新時間和日期,既更新xtime牆上時間

– 确定目前程序在CPU 上已運作了多長時間,如果已經超過了配置設定給它的時間,則搶占它

– 更新資源使用統計數

– 檢查定時器時間間隔是否已到,如果是,則執行它注冊的函數(運作于底半部軟中斷中)

以上工作每秒要發生 Hz次,也就是說PC上的時鐘中斷處理程式執行的頻率為Hz

5、時間系統總結

1、節拍----->jiffies

又稱時鐘滴答,是一個全局變量,它的值在系統引導的時候初始化為0,在時鐘中斷初始化完成後,每次時鐘中斷發生,在時鐘中斷處理例程中都會将jiffies的值 +1。

jiffies_64:為了解決jiffies溢出問題,更重要的是通過jiffies_64可以知道自開機以來的時間間隔。

2、節拍率---->HZ

HZ表示時鐘中斷發生的頻率。可以在.config的配置檔案中改寫。1/HZ是每個jiffies+1的時間間隔。

3、通過jiffies可以進行時間的比較和時間轉換

4、時間比較

32位                                                    64位

time_after(a,b)                                    time_after64(a,b)

time_before(a,b)                                 time_before64(a,b)

time_after_eq(a,b)                              time_after_eq64(a,b)

time_before_eq(a,b)                           time_before_eq64

time_in_range(a,b,c)                           time_in_range(a,b,c)

5、時間轉換

a、jiffies和msecs以及usecs的轉換:

unsigned int jiffies_to_msecs(const unsigned long);

unsigned int jiffies_to_usecs(const unsigned long);

unsigned long msecs_to_jiffies(const unsigned int m);

unsigned long usecs_to_jiffies(const unsigned int u);

b、jiffies和timespec以及timeval的轉換

在使用者空間,應用程式更多的使用秒以及毫秒等時間形式,而在核心中多使用jiffes。

核心定義了struct timeval 和 struct timespec 兩種資料結構

struct timespec {

__kernel_time_t tv_sec;

long              tv_nsec;

}

struct timeval {

__kernel_time_t          tv_sec;

__kernel_suseconds_t  tv_usec;

}

互相轉換函數:

unsigned long timespec_to_jiffies(const struct timespec *value);

void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value);

unsigned long timeval_to_jiffies(const struct timeval *value);

void jiffies_to_timeval(const unsigned long jiffies, struct timeval *value);

6、要注意的是jiffies的精度問題。如果HZ = 1000,則jiffies增加1代表1ms。

如果要用到更高精度的始終,要用其他的硬體機制。

二、核心短延時

Linux核心中提供了下列3個函數以分别進行納秒、微秒和毫秒延遲:

void ndelay(unsigned long nsecs);

void udelay(unsigned long usecs);

void mdelay(unsigned long msecs);

上述延遲的實作原理本質上是忙等待,它根據CPU頻率進行一定次數的循環。如果沒有特殊的理由(比如在中斷上下文中擷取自旋鎖的情況),不推薦使用這些函數延遲較長的時間,浪費CPU。

注:ndelay 和 mdelay都是基于udelay,将udelay的次數除1000就是ndelay,是以ndelay的次數為1000的整數倍才準确。

有時候,人們在軟體中進行下面的延遲:

void delay(unsigned int time)

{

while(time--);

}

ndelay()、udelay()和mdelay()函數的實作方式原理與此類似。

核心在啟動時,會運作一個延遲循環校準(Delay Loop Calibration),計算出lpj(Loops Per Jiffy)即處理器在一個jiffy時間内運作一個内部的延遲循環的次數,核心啟動時會列印如下類似資訊:

Calibrating delay loop... 530.84 BogoMIPS (lpj=1327104)

如果我們直接在bootloader傳遞給核心的bootargs中設定lpj=1327104,則可以省掉這個校準的過程節省約百毫秒級的開機時間。

睡着延時

毫秒時延(以及更大的秒時延)已經比較大了,在核心中,好不要直接使用mdelay()函數,這将耗費CPU資源,對于毫秒級以上的時延,核心提供了下述函數:

void msleep(unsigned int millisecs);

unsigned long   msleep_interruptible(unsigned int millisecs);

void ssleep(unsigned int seconds);

上述函數将使得調用它的程序睡眠參數指定的時間為millisecs,msleep()、ssleep()不能被打斷,而msleep_interruptible()則可以被打斷。

受系統Hz以及程序排程的影響,msleep()類似函數的精度是有限的。

三、核心長延時

在核心中,一個直覺的延時的方法是将所要延遲的時間設定的目前的jiffies加上要延遲的時間,這樣就可以簡單的通過比較目前的jiffies和設定的時間來判斷延時的時間時候到來。針對此方法,核心中提供了簡單的宏用于判斷延時是否完成。

time_after(a,b);        

time_before(a,b);      

長延時實作舉例:

unsigned long delay = jiffies + 100;

while(time_before(jiffies, delay));

unsigned long delay = jiffies + 2*Hz;

while(time_before(jiffies, delay));

與time_before()對應的還有一個time_after(),它們在核心中定義為(實際上隻是将傳入的未來時間jiffies和被調用時的jiffies進行一個簡單的比較):

#define time_after(a,b) \

(typecheck(unsigned long, a) && \

typecheck(unsigned long, b) && \

((long)(b) - (long)(a) < 0))

#define time_before(a,b) time_after(b,a)

為了防止在time_before()和time_after()的比較過程中編譯器對jiffies的優化,核心将其定義為

volatile變量,這将保證每次都會重新讀取這個變量。是以volatile更多的作用還是避免這種讀合并。

四、讓程序睡固定的時間

下面兩個函數可以将目前程序添加到等待隊列中,進而在等待隊列上睡眠,當逾時發生時,程序将被喚醒:

sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);

interrupt_sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);