1. 內核中的時間節拍
內核時鐘頻率在arm中默認是100,即HZ=100,定義在arch/arm/include/asm/param.h,這個值是可以改變的,內核代碼儘量使用HZ宏,不要直接使用100。提高頻率雖然可以提高時鐘精確度,響應速度,但也會因頻繁中斷導致系統負擔增大。在時間轉化的時候,系統會強制HZ能被1000整除。
jiffies是內核全局變量,系統啓動是賦值爲0,即每秒時鐘中斷100次,每個節拍間隔爲0.1s,jiffies用來記錄系統自啓動以來的節拍總數。如果要記錄自系統啓動以後經過時秒數,可以這樣轉換jiffies/100。
2. jiffies
在linux/jiffies.h中定義
extern unsigned long volatile jiffies;//歷史原因的jiffies變量,32bits
extern u64 jiffies_64;//爲了防止溢出新定義的jiffies變量,64bits
在默認HZ=100的情況下,jiffies在497天后溢出,jiffes_64有生之年是看不到他溢出的。
在arch/arm/kernel/vmlinux.lds.S中有
jiffies = jiffies_64;
由於歷史的原因保留jiffies這個變量名稱,它是取64位的jiffies_64低32位,而在linux內核時間管理部分的代碼使用整個64位,這樣既可以保持兼容性,亦避免時間溢出。
獲取jiffies使用如下函數u64 get_jiffies_64(void),這個函數實際上是return (u64)jiffies;因jiffies有volatile修飾,而jiffies_64沒有,所以不要直接訪問jiffies_64。
2.1 jiffies和時間的轉換
這個轉換儘量使用內核提供的接口,不要手動去轉換,即使轉換很簡單。另外要注意在32位的jiffies乘時,可能會溢出。
jiffies到毫秒、微秒的互相轉換:
extern unsigned int jiffies_to_msecs(const unsigned long j);
extern unsigned int jiffies_to_usecs(const unsigned long j);
extern unsigned long msecs_to_jiffies(const unsigned int m);
extern unsigned long usecs_to_jiffies(const unsigned int u);
jiffies到struct timespec、struct timeval互相轉換:
<span style="font-size:12px;">struct timespec {
__kernel_time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /* microseconds */
};</span>
extern unsigned long timespec_to_jiffies(const struct timespec *value);
extern void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value);
extern unsigned long timeval_to_jiffies(const struct timeval *value);
extern void jiffies_to_timeval(const unsigned long jiffies, struct timeval *value);
獲取本地時間:
extern void do_gettimeofday(struct timeval *tv);
extern void getnstimeofday(struct timespec *tv);
3. 內核定時器
底半部機制就是將工作推後執行,但是沒有個量的概念,反正試不在當前執行就可以,而有時候我們需要將一些任務推後XX具體的時間後再執行,這個時候用內核定時器就是理想的解決方法,定時器處理函數在軟中斷執行。定時器和work_queue一樣調用完後自動停止,除非再次激活觸發。
相關文件:#include<linux/timer.h>
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;//定時器列表入口
unsigned long expires;//超時時間(jiffies爲單位)
struct tvec_base *base;//用戶不使用
void (*function)(unsigned long);//超時後處理函數
unsigned long data;//傳給超時處理函數的參數
int slack;
#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
3.1 定義:
如:static struct timer_list mytimer;
3.2 初始化:
如:init_timer(&mytimer);
初始化了entry,base,slack
3.3 超時處理函數
如:void mytimer_handle(unsigned long arg);
這個arg就是timer_list.data的值
3.4 進一步初始化timer_list
到此爲止還有超時時間expires和超時處理函數function還沒有賦值。
如:
mytimer.function = mytimer_handle;
mytimer.expires = jiffies+HZ;
mytimer.data = (unsigned long)NULL;
也可以靜態定義初始化一步搞定,替代上面4步:
#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)
3.5 激活
定時器初始化後,激活了才能工作。
如:add_timer(&mytimer);
或者mod_timer(&mytimer, jiffies+HZ);//1s後超時處理,可在定時器超時處理函數中再次調用達到定量週期性處理
/**
* add_timer - start a timer
* @timer: the timer to be added
*
* The kernel will do a ->function(->data) callback from the
* timer interrupt at the ->expires point in the future. The
* current time is 'jiffies'.
*
* The timer's ->expires, ->function (and if the handler uses it, ->data)
* fields must be set prior calling this function.
*
* Timers with an ->expires field in the past will be executed in the next
* timer tick.
*/
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);
}
3.6 停止
定時器到期之前停止定時器,其實是否到期都可以調用,如果調用時未激活返回0,否則返回1。定時器到期後,自動會停止的。
在SMP上,可能定時器中斷在其他定時器上運行了,所以刪除定時器時要等待其他處理器的定時器處理函數退出後,因此del_timer_sync()函數可能會阻塞,因此在中斷上下文不能用這個函數,del_timer可以在中斷上下文使用。
int del_timer(struct timer_list *timer);
int del_timer_sync(struct timer_list *timer);
如:del_timer(&mytimer);
4. 內核延時
內核延時有中斷底半部,推後一定時間工作的定時器,但是很多時候驅動需要一些硬件上的延時,而且這些延時都很短。
4.1 忙等待
如:下面的延時1s
unsigned long delay = jiffies + HZ;
//OTHER JOB
while(timer_before(jiffies, delay));
4.2 短延時
基本原理是基於忙等待,這個短延時,不是休眠,不像sleep長延時會有調度時間,獨佔當前的CPU後能立即得到執行,所以儘量不要延遲很長時間,特別是mdelay,。
void ndelay(unsigned long x);//納秒延時
void udelay(unsigned long x);//微秒延時
void mdelay(unsigned long x);//毫秒延時
4.3 休眠延時(schedule_timeout)
extern signed long schedule_timeout(signed long timeout);//休眠延時
extern signed long schedule_timeout_interruptible(signed long timeout);//可被信號中斷喚醒
extern signed long schedule_timeout_killable(signed long timeout);//可被SIGKILL中斷喚醒
extern signed long schedule_timeout_uninterruptible(signed long timeout);//不可提前被喚醒
void msleep(unsigned int msecs)//通過schedule_timeout_uninterruptible()實現的,還有usleep,ssleep是微秒,秒的休眠
如果不要求時間延時很準確,可以使用定時器和休眠延時的方式,如果既要時間短又要時間精確可以使用忙等待或短延時。