linux kernel時間管理

    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是微秒,秒的休眠

 

如果不要求時間延時很準確,可以使用定時器和休眠延時的方式,如果既要時間短又要時間精確可以使用忙等待或短延時。

發佈了38 篇原創文章 · 獲贊 12 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章