linux內核時間管理
時間:2017-11-23 來源(yuan):未(wei)知
前言:
Linux中如何對時間進(jin)行管理?時鐘節拍(pai)的概(gai)念及(ji)延時函(han)數的用法(fa)很多同學都用不好,下面我(wo)給大家總結一下。
一,linux時(shi)鐘運作機(ji)制(zhi)
1,linux時鐘運作機制
• 大部分(fen)PC機(ji)中有兩個時(shi)鐘(zhong)(zhong)源,分(fen)別(bie)是實時(shi)時(shi)鐘(zhong)(zhong)(RTC)和 操作系統(OS)時(shi)鐘(zhong)(zhong)
• 實時時鐘也叫(jiao)CMOS時鐘,它靠電(dian)池供電(dian),即(ji)使系統(tong)斷(duan)電(dian),也可以(yi)維持日期和時間。
• RTC和OS時鐘(zhong)之間的關系通(tong)常也被稱(cheng)作操作系統的時鐘(zhong)運作機(ji)制
• 不同(tong)的操作系統,其時(shi)鐘運作機制也不同(tong)
linux中的(de)時鐘機制大致如下圖所(suo)示

linux中時鐘(zhong)機制
由上圖可知:
RTC是硬件時(shi)(shi)鐘,它為整個計(ji)算機提(ti)供(gong)一個計(ji)時(shi)(shi)標(biao)準(zhun),是原始底層的時(shi)(shi)鐘數據(ju),由紐扣電(dian)池供(gong)電(dian),系統斷電(dian)后(hou)仍然在工作(zuo)
OS時鐘產生于PC主板上的(de)定時/計(ji)數(shu)(shu)芯片(pian),由操(cao)(cao)作(zuo)(zuo)系統控(kong)制這(zhe)個芯片(pian)的(de)工(gong)作(zuo)(zuo),OS時鐘的(de)基本單位(wei)就是該芯片(pian)的(de)計(ji)數(shu)(shu)周期,開(kai)機(ji)時操(cao)(cao)作(zuo)(zuo)系統取得RTC中的(de)時間(jian)數(shu)(shu)據來初(chu)始化OS時鐘,所以它只是在開(kai)機(ji)有效(xiao),由操(cao)(cao)作(zuo)(zuo)系統控(kong)制,已被稱為軟時鐘或系統時鐘。操(cao)(cao)作(zuo)(zuo)系統通過OS時鐘提供(gong)給應用(yong)程序和時間(jian)有關的(de)服務。
擴展:OS時(shi)(shi)鐘其本質是一個(ge)(ge)計(ji)(ji)數(shu)(shu)器(qi)(qi),計(ji)(ji)數(shu)(shu)器(qi)(qi)從計(ji)(ji)數(shu)(shu)初(chu)值開(kai)始,每收到一次脈(mo)沖信(xin)號,計(ji)(ji)數(shu)(shu)器(qi)(qi)減1,當減至0時(shi)(shi),就會輸(shu)出高電平或低電平,然后獲取(qu)重(zhong)載值重(zhong)新從初(chu)值開(kai)始計(ji)(ji)數(shu)(shu),不斷(duan)循環,這樣就得(de)到一個(ge)(ge)輸(shu)出脈(mo)沖,這個(ge)(ge)脈(mo)沖作用中(zhong)斷(duan)控(kong)制器(qi)(qi)上,產(chan)生中(zhong)斷(duan)信(xin)號,觸發時(shi)(shi)鐘中(zhong)斷(duan)。
2,OS時(shi)鐘(zhong)中斷
• OS時鐘是由可編程定時/計(ji)數(shu)(shu)器產(chan)生的(de)輸出脈沖(chong)觸(chu)發(fa)中(zhong)斷(duan)而產(chan)生的(de),而輸出脈沖(chong)的(de)周期叫(jiao)做一個“時鐘節拍”(Tick,又稱滴(di)答),(中(zhong)斷(duan)觸(chu)發(fa)時會進入中(zhong)斷(duan)處理函數(shu)(shu),使jiffies+1)
• 操作系統(tong)的“時間基準” 由設(she)計(ji)者(zhe)決定,Linux的時間基準是(shi)1970年(nian)1月1日凌晨0點
• OS時(shi)鐘(zhong)記錄的時(shi)間(jian)就是系統時(shi)間(jian)。系統時(shi)間(jian)以(yi)“時(shi)鐘(zhong)節拍”為單(dan)位(wei)
•時(shi)鐘(zhong)中(zhong)斷觸發的頻(pin)率,由內核HZ來確(que)定(ding),系統啟動時(shi)會按照定(ding)義的HZ值對硬件進(jin)行設置
比如(ru)對(dui)HZ的定義如(ru)下:
#define Hz 100
內(nei)核(he)時(shi)間(jian)頻率:表示(shi)每秒鐘(zhong)觸發100次時(shi)鐘(zhong)中斷,即(ji)每10ms觸發一(yi)次,
每次(ci)中斷jiffies+1,,則每秒jiffies增加了100,
• Linux中用全局變量 jiffies表示系統自啟(qi)動以來的時鐘節拍數目(時鐘中斷觸發(fa)的次數)
因此系(xi)統運行的時(shi)間以(yi)s為單位(wei)計數, 就等于 jiffies/HZ
內核啟動時將該(gai)變(bian)量初始化為0,此后,每(mei)次(ci)時鐘中斷(duan)處(chu)理(li)程序都會增加該(gai)變(bian)量的(de)值,每(mei)秒鐘觸發(fa)中斷(duan)的(de)次(ci)數為Hz,
3、實際時間
實(shi)際時(shi)間就是現實(shi)中鐘(zhong)表上顯示的時(shi)間,其實(shi)內核中并不常用這個(ge)時(shi)間,主(zhu)要(yao)是用戶空間的程(cheng)序有(you)時(shi)需要(yao)獲(huo)取當前時(shi)間,所以內核中也管理著這個(ge)時(shi)間。
實際(ji)時(shi)間的(de)獲取是在開機后(hou),內(nei)核初始化時(shi)從RTC讀取的(de)。
內核讀取這(zhe)個(ge)時間后(hou)就將其放(fang)入內核中的(de) xtime 變量中,并且在系統的(de)運行中不斷(duan)更新(xin)這(zhe)個(ge)值。
當(dang)前實際時間(jian)(墻(qiang)(qiang)上時間(jian)): xtime.tv_sec以秒(miao)為(wei)單位,存放著(zhu)自1970年(nian)7月1日(UTC)以來(lai)經過(guo)的(de)時間(jian),1970年(nian)1月1日被稱為(wei)紀元。多數(shu)Unix系統的(de)墻(qiang)(qiang)上時間(jian)都是基于該紀元而言(yan)的(de)。xtime.tv_nsec記錄(lu)自上一秒(miao)開始(shi)經過(guo)的(de)納(na)秒(miao)數(shu)。
在<Time.h(incluce/linux)>中
extern struct timespec xtime;
#ifndef _STRUCT_TIMESPEC
#define _STRUCT_TIMESPEC
struct timespec { /*高精度(du)*/
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds 納秒*/
};
#endif
從用戶(hu)空間取(qu)得墻上時間的主要接口是gettimeofday(),在內核中對(dui)應的系統調用為sys_gettimeofday():
雖然內核也(ye)實(shi)現(xian)了(le)time()系統(tong)調用,但是gettimeofday()幾乎(hu)完全(quan)取(qu)代了(le)它。C庫(ku)函數也(ye)提供了(le)墻上時(shi)間相關的庫(ku)調用,比(bi)如ftime(),ctime()。
除了更新(xin)xtime時(shi)間外,內核不(bu)會想用戶空間程序那樣頻繁(fan)的使(shi)用xtime。但是,在(zai)文件系統(tong)的實現代碼中(zhong)存放(fang)訪問時(shi)間戳(創建,存取,修改等)需(xu)要使(shi)用xtime。
4,時鐘中斷(duan)處(chu)理程序----操(cao)作系統的脈搏
每一次時鐘中(zhong)斷的(de)產生都觸發下列幾個主要的(de)操作:
– 給(gei)jiffies變量加 1
– 更新時間和日期,既更新xtime墻上時間
– 確(que)定當前進(jin)程(cheng)在CPU 上已運行(xing)了(le)多長時(shi)間,如果已經(jing)超(chao)過了(le)分配給它的(de)時(shi)間,則搶占(zhan)它
– 更(geng)新資(zi)源使用統計(ji)數
– 檢查(cha)定(ding)時器時間間隔是否已到(dao),如果是,則執行它注(zhu)冊的函數(運行于底半部(bu)軟中斷中)
以上工作(zuo)每秒要發生 Hz次,也就(jiu)是說PC上的時鐘中斷處理(li)程序執行的頻率為Hz
5、時間(jian)系統(tong)總結
1、節拍----->jiffies
又稱(cheng)時(shi)鐘(zhong)滴答,是一個全局變量,它的值在系統引導的時(shi)候初(chu)始(shi)化為(wei)0,在時(shi)鐘(zhong)中斷初(chu)始(shi)化完成后,每次時(shi)鐘(zhong)中斷發生,在時(shi)鐘(zhong)中斷處理例程中都會將jiffies的值 +1。
jiffies_64:為了解(jie)決jiffies溢出問(wen)題,更(geng)重要的是(shi)通過jiffies_64可以知道自開機以來的時間(jian)間(jian)隔。
2、節(jie)拍率---->HZ
HZ表示時(shi)鐘中(zhong)斷發生的(de)頻率。可(ke)以在.config的(de)配置文件中(zhong)改(gai)寫(xie)。1/HZ是每個jiffies+1的(de)時(shi)間(jian)(jian)間(jian)(jian)隔。
3、通過jiffies可以(yi)進行(xing)時(shi)間的比較(jiao)和(he)時(shi)間轉換
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以及(ji)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的轉換
在(zai)用戶空間,應(ying)用程序更(geng)多(duo)的使用秒以及毫秒等時(shi)間形式,而在(zai)內核中多(duo)使用jiffes。
內核定義了(le)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;
}
相互(hu)轉換函數(shu):
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_tim;
6、要(yao)注意的是(shi)jiffies的精度(du)問題(ti)。如果HZ = 1000,則(ze)jiffies增加1代(dai)表1ms。
如果要(yao)用到更高精(jing)度的(de)始終(zhong),要(yao)用其他的(de)硬件機制。
二、內核短延時
Linux內核中提供了下列3個函數以分別進行納秒(miao)、微(wei)秒(miao)和毫秒(miao)延遲(chi):
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
上述延(yan)遲(chi)的實現(xian)原理本質上是忙等待,它根據(ju)CPU頻(pin)率進行一(yi)定次數的循(xun)環(huan)。如果沒有特殊的理由(比如在(zai)中斷上下文中獲(huo)取自旋(xuan)鎖的情況),不(bu)推薦(jian)使用這(zhe)些函數延(yan)遲(chi)較(jiao)長(chang)的時間,浪費CPU。
注:ndelay 和 mdelay都是基于udelay,將udelay的(de)次(ci)數除1000就是ndelay,因(yin)此ndelay的(de)次(ci)數為(wei)1000的(de)整數倍才準確(que)。
有時候(hou),人們在軟件中(zhong)進行(xing)下面的延遲:
void delay(unsigned int time)
{
while(time--);
}
ndelay()、udelay()和mdelay()函數的(de)實(shi)現方(fang)式原理(li)與(yu)此(ci)類似。
內核在啟動時,會(hui)運行一個(ge)延(yan)遲(chi)循環校準(Delay Loop Calibration),計算(suan)出lpj(Loops Per Jiffy)即處理器在一個(ge)jiffy時間內運行一個(ge)內部的延(yan)遲(chi)循環的次數(shu),內核啟動時會(hui)打印如下類似(si)信息:
Calibrating delay loop... 530.84 BogoMIPS (lpj=1327104)
如果我(wo)們直接在bootloader傳遞給內核的(de)(de)bootargs中(zhong)設(she)置lpj=1327104,則可以省掉這個校準的(de)(de)過程節省約百(bai)毫秒級的(de)(de)開(kai)機時間。
睡著延時
毫秒(miao)(miao)時(shi)(shi)延(以及更大的秒(miao)(miao)時(shi)(shi)延)已經比(bi)較大了(le),在內核中,好不要直(zhi)接使用mdelay()函(han)(han)數,這將耗費CPU資源(yuan),對(dui)于毫秒(miao)(miao)級以上的時(shi)(shi)延,內核提供(gong)了(le)下述函(han)(han)數:
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);
上(shang)述函數將使得調用(yong)它(ta)的進程睡眠參數指定的時間為millisecs,msleep()、ssleep()不(bu)能被(bei)打(da)斷,而msleep_interruptible()則可以被(bei)打(da)斷。
受系統Hz以(yi)及進(jin)程調度的影(ying)響,msleep()類似函數的精度是(shi)有限的。
三、內核長延時
在內核(he)中(zhong),一個(ge)直觀的(de)(de)延(yan)時(shi)(shi)的(de)(de)方法是(shi)將所要(yao)延(yan)遲的(de)(de)時(shi)(shi)間設置(zhi)的(de)(de)當前的(de)(de)jiffies加上要(yao)延(yan)遲的(de)(de)時(shi)(shi)間,這樣就可以簡單的(de)(de)通過比較當前的(de)(de)jiffies和設置(zhi)的(de)(de)時(shi)(shi)間來判(pan)斷(duan)延(yan)時(shi)(shi)的(de)(de)時(shi)(shi)間時(shi)(shi)候到來。針對此方法,內核(he)中(zhong)提供了簡單的(de)(de)宏用于判(pan)斷(duan)延(yan)時(shi)(shi)是(shi)否完成(cheng)。
time_after(a,b); /*如果(guo)時間a在b之后 (a>b),則(ze)返回(hui)真,否則(ze)返回(hui)0*/
time_before(a,b); /*如(ru)果時間(jian)a在b之(zhi)前 (a<b),則(ze)(ze)返回真,否則(ze)(ze)返回0*/
長延(yan)時實(shi)現舉例:
/* 延遲 100 個(ge) jiffies */
unsigned long delay = jiffies + 100;
while(time_before(jiffies, delay));
/* 再延(yan)遲 2s */
unsigned long delay = jiffies + 2*Hz;
while(time_before(jiffies, delay));
與time_before()對應的(de)還(huan)有一個time_after(),它們在內核(he)中定義(yi)為(實際上(shang)只(zhi)是(shi)將傳入(ru)的(de)未來時間(jian)jiffies和被調用時的(de)jiffies進行一個簡單的(de)比較):
#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)
為了防止(zhi)在time_before()和time_after()的比較過(guo)程中編譯器(qi)對jiffies的優化(hua),內(nei)核將其定義為
volatile變(bian)量(liang),這將保證每次都(dou)會重新讀取這個變(bian)量(liang)。因此volatile更(geng)多(duo)的作(zuo)用(yong)還是(shi)避免這種讀合并。
四、讓進程(cheng)睡固定的時間(jian)
下面兩個函數可以將當(dang)前(qian)進程(cheng)添加到等(deng)(deng)待隊(dui)列中,從而在等(deng)(deng)待隊(dui)列上睡眠,當(dang)超時(shi)(shi)發生時(shi)(shi),進程(cheng)將被喚醒:
sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);
interrupt_sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);

