文章目錄
- 1 內核中時間概念
- 2 標準定時器
- jiffies和HZ
- 定時器API
- 標準定時器案例
- 3 高精度定時器(HRT)
- 高精度定時器案例
- 4 內核中延遲和睡眠
- 原子上下文
- 非原子上下文
1 內核中時間概念
時間概念對計算機來說有些模糊,事實上內核必須在硬件的幫助下才能計算和管理時間。硬件為內核提供了一個系統定時器用于計算流失的時間。系統定時器以某種頻率自發觸發時鐘中斷,該頻率可以通過編程預定,稱為節拍率(系統定時器的頻率)。
因為節拍率對內核來說是可知的,所以內核知道連續兩次時鐘中斷的間隔時間,這個間隔時間就是節拍。內核就是靠這種已知的時鐘中斷間隔來計算實際時間(絕對時間)和系統運行時間。實際時間,就是某一天的日期和時間,內核通過控制時鐘中斷維護實際時間,另外內核也為用戶空間提供了一組系統調用以獲取實際日期和實際時間。系統運行時間:就是系統啟動開始所經過的時間,對用戶空間和內核都很用,因為許多程序都必須清楚流失的時間,通過兩次讀取運行時間再計算它們的差,就可以得到相對的流逝過的時間了。
系統定時器(內核定時器)是我們接下來討論的內容。
2 標準定時器
標準定時器是內核定時器,以jiffies為粒度運行
jiffies和HZ
jiffies是在<linux/jiffies.h>
中聲明的內核時間單元。為了理解jiffies,需要引入一個新的常量HZ,注意,這是個常量,系統定時器頻率(節拍率)是通過靜態預處理定義的,也就是HZ(赫茲),在系統啟動時按照HZ值對硬件進行設置。它是jiffies在1s內增加的次數,HZ的大小取決于硬件和內核版本,決定了時鐘中斷觸發的頻率。
全局變量jiffies用來記錄自系統啟動以來產生的節拍總數。啟動時,內核將該變量初始化為0,此后,每次時鐘中斷處理程序都會增加該變量的值。因為一秒內時鐘中斷的次數等于HZ,所以jiffies一秒內增加的值也就為HZ。
jiffies變量總是無符號長整數(unsigned long),因此,在32位體系結構上是32位,在64位體系結構上是64位。32位的jiffies變量,可能會溢出,如果頻率為1000Hz,49.7天后就會溢出。而使用64位的jiffies,不會看到它溢出。為了解決這個問題,<linux/jiffies.h>引入和定義了另一個變量:
extern u64 jiffies_64;
32位系統上采用這種方式時,jiffies取整個64位jiffies_64變量的低32位。jiffies_64將指向高位。在64位平臺上,jiffies = jiffies_64
。
定時器API
定時器由結構timer_list表示,定義在文件linux/timer.h中:
struct timer_list {struct list_head entry; /* 定時器鏈表的入口 */unsigned long expires; /* 以jiffies為單位的定時值 */spinlock_t lock; /* 保護定時器的鎖 */unsigned long magic; void (*function)(unsigned long); /* 定時器處理函數 */unsigned long data; /* 傳給處理函數的長整型參數 */struct tvec_t_base_s *base; /* 定時器內部值,用戶不要使用 */
};
定時器的使用很簡單,你只需要執行一些初始化工作,設置一個超時時間,指定超時發生后執行的函數,然后激活定時器就可以了。指定的函數將在定時器到期時自動執行,注意定時器并不周期運行,它在超時后就自動銷毀,這也是這種定時器被稱為動態定時器的一個原因,動態定時器不斷創建和銷毀,而且它的運行次數也不受限制。
- 設置定時器
設置定時器,可以用setup_timer函數:
void setup_timer(struct timer_list *timer,void (*function)(unsigned long),unsigned long data)
也可以使用init_time函數:
init_timer(struct timer_list *timer);
setup_timer是對init_timer的包裝。
- 激活定時器
函數add_timer( )根據參數struct timer_list變量的expires值將定時器插入到合適的動態定時器的鏈表中,并激活定時器。函數首先檢測定時器是否處于掛起狀態,如果掛起給出警告信息并退出,否則插入合適的定時器鏈表。
void add_timer(struct timer_list *timer)
- 設置過期時間
int mod_timer(struct timer_list *timer, unsigned long expires);
函數mod_timer( )主要用于更改動態定時器的到期時間,從而可更改定時器的執行順序,相當于執行如下代碼序列:
del_timer(timer);
timer->expires=expires;
add_timer(timer);
mod_timer會修改過期時間并激活定時器
- 如果需要在定時器超時前停止定時器,可以使用del_timer()函數
int del_timer(struct timer_list * timer);
int del_timer_sync(struct timer_list * timer);
函數del_timer( )返回整數,可能的取值是0和1,對于活動定時器,返回1,對于不活動定時器返回0。del_timer_sync等待定時器處理函數執行完成函數。
當del_timer返回后,可以保證的只是:定時器不會再被激活,但是在多處理器機器上定時器可能已經在其他處理器上運行了,所以刪除定時器是需要等待可能在其他處理器上運行的定時器處理程序都退出,這時就要使用del_timer_sync()函數執行刪除工作。
應該在模塊清理例程中釋放定時器,可以單獨檢查定時器是否正在運行:
int timer_pending(struct timer_list *timer);
這個函數檢查是否有觸發的定時器回調函數掛起。
標準定時器案例
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/timer.h>static struct timer_list my_timer;void my_timer_callback(unsigned long data)
{printk("%s callled (%ld).\n",__FUNCTION__,jiffies );
}static int __init my_init(void)
{int retval;printk("Timer module loaded\n");setup_timer(&my_timer,my_timer_callback,0);printk("Setup timer to fire in 300ms (%ld)\n",jiffies);retval = mod_timer(&my_timer,jiffies+msecs_to_jiffies(300));if(ret){printk("Timer firing failed\n");}return 0;
}static void my_exit(void)
{int retval;retval = del_timer(&my_timer);if(retval){printk("The timer is still in use..\n");}pr_info("Tiner module unloaded.\n");
}module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
3 高精度定時器(HRT)
標準定時器不夠準確,其準確度為毫秒,內核2.6.16引入了高精度定時器,其精度達到微秒(取決于平臺,最高可達納秒)。標準定時器區取決于HZ(因為它們依賴于jiffies),而HRT實現基于ktime。
在系統上使用HRT時,要確認內核和硬件支持它。換句話說,必須用于平臺相關的代碼來訪問硬件HRT。
需要頭文件:
#include <linux/hrttimer.h>
在內核中HRT表示為hrttimer的實例:
*** struct hrtimer - the basic hrtimer structure* @node: timerqueue node, which also manages node.expires,* the absolute expiry time in the hrtimers internal* representation. The time is related to the clock on* which the timer is based. Is setup by adding* slack to the _softexpires value. For non range timers* identical to _softexpires.* @_softexpires: the absolute earliest expiry time of the hrtimer.* The time which was given as expiry time when the timer* was armed.* @function: timer expiry callback function* @base: pointer to the timer base (per cpu and per clock)* @state: state information (See bit values above)* @is_rel: Set if the timer was armed relative** The hrtimer structure must be initialized by hrtimer_init()*/
struct hrtimer {struct timerqueue_node node;ktime_t _softexpires;enum hrtimer_restart (*function)(struct hrtimer *);struct hrtimer_clock_base *base;u8 state;u8 is_rel;
};
HRT的初始化的步驟如下:
- 初始化hrttimer。hrttimer初始化之前,需要設置ktime,它代表持續時間。hrttimer_init初始化高精度定時
extern void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,enum hrtimer_mode mode);
- 啟動hrttimer,使用hrttimer_start函數:
/*** hrtimer_start - (re)start an hrtimer* @timer: the timer to be added* @tim: expiry time* @mode: timer mode: absolute (HRTIMER_MODE_ABS) or* relative (HRTIMER_MODE_REL), and pinned (HRTIMER_MODE_PINNED);* softirq based mode is considered for debug purpose only!*/
static inline void hrtimer_start(struct hrtimer *timer, ktime_t tim,const enum hrtimer_mode mode)
mode代表到期模式,對于實際時間,它應該是HRTIMER_MODE_ABS,對于相對于現在的時間,是HRTIMER_MODE_REL
- 取消hrtimer。
extern int hrtimer_cancel(struct hrtimer *timer);
extern int hrtimer_try_to_cancel(struct hrtimer *timer);
如果定時器處于激活狀態或者回調函數正在運行,hrtimer_try_to_cancel會失敗,返回-1,hrtimer_cancel會等待回調函數完成。
為了防止定時器自動重啟,hrtimer回調函數必須返回HRTIMER_NORESTART
高精度定時器案例
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>#define MS_TO_NS(x) (x * 1E6L)static struct hrtimer hr_timer;enum hrtimer_restart my_hrtimer_callback( struct hrtimer *timer )
{pr_info( "my_hrtimer_callback called (%ld).\n", jiffies );return HRTIMER_NORESTART;
}static int hrt_init_module( void )
{ktime_t ktime;unsigned long delay_in_ms = 200L;pr_info("HR Timer module installing\n");/** ktime = ktime_set(0, 200 * 1000 * 1000);* 200 ms = 10 * 1000 * 1000 ns*/ktime = ktime_set( 0, MS_TO_NS(delay_in_ms) );hrtimer_init( &hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL );hr_timer.function = &my_hrtimer_callback;pr_info( "Starting timer to fire in %ldms (%ld)\n", \delay_in_ms, jiffies );hrtimer_start( &hr_timer, ktime, HRTIMER_MODE_REL );return 0;
}static void hrt_cleanup_module( void )
{int ret;ret = hrtimer_cancel( &hr_timer );if (ret)pr_info("The timer was still in use...\n");pr_info("HR Timer module uninstalling\n");return;
}module_init(hrt_init_module);
module_exit(hrt_cleanup_module);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_DESCRIPTION("Standard timer example");
4 內核中延遲和睡眠
延遲有兩種類型,取決于代碼運行的上下文:原子的或非原子的。處理內核延遲要包含的頭文件是#include <linux/delay.h>
。
原子上下文
原子上下文中的任務(如ISR)不能進入睡眠狀態,無法進行調度。這就是原子上下文延遲必須使用循環-等待循環的原因。內核提供Xdelay系列函數,在繁忙循環中消耗足夠長的時間(基于jiffies),得到所需的延遲。
ndelay(unsigned long nsecs)
udelay(unsigned long usecs)
mdelay(unsigned long msecs)
應該時鐘使用udelay(),因為ndelay()的精度取決于硬件定時器的精度。不建議使用mdelay()。
非原子上下文
在非原子上下文中,內核提供sleep系列函數,使用那個函數取決于需要延遲多長時間。
udelay(unsigned long usecs)
:基于繁忙-等待循環。如果需要睡眠數微秒(小于等于10us左右),則應該使用該函數。usleep_range(unsigned long min,unsigned long max)
:依賴于hrttimer,睡眠數微秒到數毫秒(10us-20ms)時建議使用它,避免使用udelay()的繁忙-等待循環msleep(unsigned long msecs)
:由jiffies傳統定時器支持,對于數毫秒以上的長睡眠(10ms+)請使用該函數。