通常情況下,jiffies在linux系統啟動引導階段初始化為0,當系統完成了對時鐘中斷的初始化之後,在每個時鐘中斷處理例程中,該值都會被+1,因此該值儲存了系統自最近一次啟動以來的時鐘滴答數。除了時鐘中斷程式對該值有修改的權利,其它任何模組都只有讀的權利。
這裡再介紹乙個概念hz,該值在核心原始碼根目錄下.config檔案中。一般為1000,代表系統一秒中發生1000次時鐘中斷。
jiffies在核心原始碼中作為乙個全域性性變數被匯出,所以在驅動程式中如過想使用,必須包含標頭檔案:
#include
unsigned
long j = jiffies;
unsigned
long j+
1= jiffies+
1*hz/
1000
;未來一秒
linux驅動程式中,用到jiffies的場景分別有時間比較,時間轉換以及設定定時器。我們這裡先討論前兩個。
jiffies在32位作業系統中,最多五十天就會溢位,雖然在大多數情況下不會出現這種情況,但是為了防止這種情況的出現,linux核心為此專門提供了一組時間比較的巨集,來避免溢位。
time_after
(a,b); 若時間a,在時間b之後,返回true
time_before
(a,b); 若時間a,在時間b之前,返回true
time_after_eq
(a,b); 若時間a,在時間b之後,返回true,當兩個時間點相等時,也返回true
time_before_eq
(a,b);若時間a,在時間b之前,返回true,當兩個時間點相等時,也返回true
time_in_range
(a,b,c)
; 用來檢查時間點a,是否包含在時間間隔b,c中,包含臨界點。
有時候裝置驅動程式需要將jiffies轉換為ms或者us,這裡列出系統提供的函式:
#include
unsigned
intjiffies_to_msecs
(const
unsigned
long j)
;unsigned
intjiffies_to_usecs
(const
unsigned
long j)
;unsigned
long
msecs_to_jiffies
(const
unsigned
long m)
;unsigned
long
usecs_to_jiffies
(const
unsigned
long u)
;
時間轉換的另外一種情形發生在使用者態程序和裝置驅動程式的互動上,應用程式設計師更多的使用秒及毫秒等形式,為此,核心定義了struct timeval 和struct timespec兩種資料結構。核心同樣提供了jiffies變數與這兩個資料結構的例項轉換函式:
#include
unsigned
long
timespec_to_jiffies
(const
struct timespec *value)
;void
jiffies_to_timespec
(const
unsigned
long jiffise,
struct timespec *value)
unsigned
longtimeval_to_jiffies
(const
struct timeval *value)
;void
jiffies_to_timeval
(const
unsigned
long jiffise,
struct timeval *value)
延時操作在裝置驅動程式中也是經常使用。通常情況下cpu在對外設發出操作指令之後,需要延時一段時間,再去讀取結果。延時分為長延時與短延時,我們下面分析。
基於時鐘滴答jiffies的長延時函式有幾種實現辦法,主要圍繞在實現過程中是否讓出cpu來分為「忙等待」和「讓出cpu「兩大類。
忙等待
忙等待作為裝置驅動早起開發階段用來除錯裝置是否能像預期那樣工作經常使用,雖然可能因為忙等到而造成資源的浪費。最簡單的忙等待就是我們能立馬想到的延時如while迴圈之類:
unsigned
long i=
0xfffffff
;while
(i--
);
這是一種相當粗糙的實現方法,顯然在最終的發布中不應該有它,我們可以利用jiffies實現一種比較理想的忙等待,比如為了實現1s的等待,可以用如下**實現:
unsigned
long j=jiffies+hz;
while
(timer_before
(jiffies,j)
cpu_relax()
;//空指令
但是,上面的實現有兩個很直白的缺點:
(1)如果進入迴圈之前,處理器關閉了中斷,這時jiffies就不會改變了,所以while將一直滿足;
(2)在支援搶占的系統中,可能會在等待過程中被更高優先順序的程序中斷,然後再次排程回來的時候,可能已經超過了延時的時間。
問題(2)對於驅動程式來說不是什麼大問題,如果沒有cpu資源的浪費,那麼即便延時函式多延時了一會,也不會造成效能的實質性影響。但是對於問題(1)的改進,則導致了讓出處理器解決方法的出現。
讓出處理器
unsigned
long j=jiffies+hz;
while
(timer_before
(jiffies,j)
schedule();
看似很完美,會讓出cpu的資源,但是在實際執行中,該程序會被很快的再次排程,然後迴圈放棄cpu,迴圈排程,也是很費資源的。這裡根本的原因是呼叫schedule函式的程序還會放在程序排程的佇列中。所以我們想解決這一問題,應該使用下面的辦法:
delag_1s()
這種情況下,函式在schedule_timeout進而呼叫schedule的時候,會因為程序的狀態為task_uninterruptible把程序移除排程佇列,然後把該程序新增到定時器的資料節點上,時間到了之後,喚醒程序使其重新進入排程佇列。
事實上,linux核心提供了乙個基於schedule_timeout的延時函式msleep(unsigned int msecs),這裡不分析實現的原始碼了,msleep正常情況下返回0,表示系統延時了正常的時間,但是如果被訊號打斷,則返回了未延時的時間。所以,再讓出處理器的方法中,直接使用msleep是最簡單的方法了。
有時候需要更短的延時,比如微妙或者納秒,所以出現了短延時。
由於jiffies一般情況下hz都為1000,意味著1msjiffies就會增加1,所以對於us與ns,不能基於jiffies實現了。而且處理器的排程就在幾微妙到幾百微秒之間,所以不能才去讓出處理器的方式了,應該採用忙等待的方式實現。
linux核心提供了幾個巨集來進行短延時:
#inculde
void mdelay(unsigned long msecs);
void udelay(unsigned long usecs);
void ndelay(unsigned long nsecs);
最基礎的是udelay,另外兩個是基於它實現的。
如果驅動程式希望在將來某可度量的時間點到期後,去執行乙個任務,該任務一般都是驅動程式自己的函式,便可以使用定時器來完成。
定時器實現的原理其實跟我們之前分析的很多東西都比較類似,首先它是通過中斷實現的。我們在使用定時器的時候,會通過函式add_tiemr向核心中的定時器鍊錶中新增乙個定時器。當定時器時間到了之後,核心會呼叫該定製器繫結的函式。
驅動程式中使用例子:
1.定義物件
struct timer_list
;struct timer_list time;
2.初始化
init_timer(&time);
time.expires = jiffies + n
time.function = timer_function;
time.data = (unsigned logn )dev;
或者
setup_timer
(timer,function,data)
;time.expires = jiffies + n
3.新增定時器
void
add_timer
(struct timer_list *timer)
//新增定時器,直接啟動,執行一次
intmod_timer
(struct timer_list *timer,
unsigned
long expires)
//重新啟動定時器,只執行一次,定時的時間需要重新設定
4.刪除定時器
int
del_timer
(struct timer_list *timer)
linux驅動之時間管理
系統的時鐘頻率 時鐘頻率詳解和程式設計 一般系統都會有個預設的時鐘頻率hz,其實就是1秒內系統時鐘發生多少次中斷。假如 系統的hz為1000,則表示1秒內系統會產生1000次時鐘中斷 滴答數 系統核心會有個統計時鐘中斷發生的次數的計數器,該計數器從系統啟動引導被初始化為0後,只要發生一次時鐘中斷,該...
ucos iii學習之時間管理
與時間服務相關的api總結 任務呼叫這個函式後就會被掛起直到期滿。這個函式可以有三種模式 相對延時模式,週期性延時模式,絕對定時模式。1 相對延時模式 任務每次執行時都會被延時大約2毫秒。當任務在時基中斷將要到來時被掛起,那麼實際的延時時基會少乙個時基。2 週期性延時 任務設定匹配值決定了任務被喚醒...
超越的時間管理之時間盒子
簡單地說,時間盒是我所知道的最有效的時間管理方式。即使在某種程度上你已經知道如何使用它,但通過下面的策略你肯定會做得更好!對於新手來,簡單地說,時間盒就是把時間段分配到乙個或一組任務。在特定的時間去完成任務而不是直到它已經完成了才去做它。但不要讓這個簡單的概念欺騙你 它可比你眼中的要更有用。許多人已...