內(nèi)核會使用CONFIG_HZ來配置自己的系統(tǒng)頻率。CONFIG_HZ可以在make menuconfig中配置,配置完的.config文件會有CONFIG_HZ。然后在include/asm-generic/param.h中
# define HZ CONFIG_HZ
一、系統(tǒng)節(jié)拍
linux用全局變量jiffies表示系統(tǒng)從開啟計算起的節(jié)拍數(shù),貌似系統(tǒng)上電后并不會把jiffies初始化成0,而是一個負數(shù)。64位的jiffies和32 位的jiffies都存儲在.data段,32 位的jiffies 在1000HZ下只需要49.7 天就發(fā)生了繞回。繞回的意思是溢出重新計算。因此在32位的系統(tǒng)中需要處理繞回。
1.1獲取節(jié)拍數(shù)
使用get_jiffies_64()在32位系統(tǒng)上面獲取64位的jiffies,因為如果直接讀取的話,讀的時候jiffies便發(fā)生了變化,因此這個函數(shù)是加上了順序鎖保證讀取的原子性。
1.2時間對比
1.time_after(a,b)
2.time_before(a,b)
3.time_after_eq(a,b)
4.time_before_eq(a,b)
以上第一個參數(shù)都是未知的時間點,第二個參數(shù)是已知的時間點,返回值依據(jù)名字看。比如time_after
a > b,time_after返回真。
5.time_in_range(a,b,c) //計算a時間點是否在[b,c]這個區(qū)間
1.3 時間轉換API
還有jiffies打交道的兩個時間結構體:struct timespec,struct timeval。
struct timespec是秒和納秒的集合,因此精度高一點。
struct timeval是秒和微秒的集合。
jiffies和他們轉換的方法:
/*jiffies互轉timespec*/
unsigned long timespec_to_jiffies(const struct timespec *value);
jiffies_to_timespec(const unsigned long jiffies,struct timespec *value);
/*jiffies互轉timeval*/
timeval_to_jiffies(const struct timeval *value);
jiffies_to_timeval(const unsigned long jiffies,
struct timeval *value);
二、內(nèi)核定時器
linux使用timer_list結構體表示內(nèi)核定時器,定義在include/linux/timer.h中
exipires:表示超時時間點,單位是節(jié)拍。比如定義一個2s的定時器,超時時間就是jiffies+(2*HZ)
function:時間到了要執(zhí)行的函數(shù)
data:將私有數(shù)據(jù)傳遞給定時器回調,因為定時器回調是正在中斷上下文執(zhí)行。
2.1初始化定時器
- init_timer(struct timer _list*timer)
實際上是一個宏定義,要求傳入一個指針。只初始化entry→next = NULL和base。
2.TIMER_INITIALIZER
要求傳入定時器回調func,超時時間expires,私有數(shù)據(jù)data,同時初始化以上字段,但是注意并沒有timer實例
3.setup_timer()
- DEFINE_TIMER(_name, _function, _expires, _data)
以上可以知道,初始化都比較混亂。因此往后我只使用init_timer+自定義字段,
超時時間設置:expires = jiffes + 需要推后的時間。比如expires = jiffes + HZ,定時一秒。無論如何設置HZ都表示一秒。
2.2啟動定時器
當使用這個函數(shù)的時候,定時器便開始計時了。
2.3刪除定時器
返回值:0定時器還沒被激活,1定時器已經(jīng)激活
del_timer_sync函數(shù)是 del_timer函數(shù)的同步版,只在在多核系統(tǒng)上面才會有區(qū)別, 會等待其他處理器運行中的定時器回調運行完再刪除定時器, del_timer_sync不能使用在中斷上下文。
2.4修改定時值
用于修改定時器的定時值,如果定時器沒被激活,就會激活
timer:要修改的定時器
expires:修改的超時時間點
當一個內(nèi)核定時器不會周期定時,需要在回調中重新調用這個函數(shù)激活
三、linux短延時函數(shù)
以上函數(shù)本質是忙等待,是會阻塞的。因此不適合在毫秒級別以上的延時使用這些。
四、長延時
time_after()、time_before()實際上也是忙等待,只不過等待時間可以很長
用法:
不推薦這種用法,長延時不要忙等待。如果需要長延時,請睡眠等待!
五、睡眠等待
msleep
msleep_interruptible
ssleep
等待的時間,是在睡眠CPU可以去執(zhí)行別的進程,類似于RTOS的延遲
六、使用定時器的例子
#include < linux/init.h >
#include < linux/module.h >
#include < linux/platform_device.h >
#include < linux/kernel.h >
#include < linux/device.h >
#include < linux/cdev.h >
#include < linux/timer.h >
#include < linux/fs.h >
#include < linux/types.h >
#include < linux/jiffies.h >
static struct second_dev{
dev_t dev_num;
struct cdev cdev;
struct device *dev;
struct class *class;
struct timer_list second_timer;
atomic_t cnt;
struct timeval timval;
}sec_dev;
static int sec_open (struct inode *inode, struct file *filp)
{
printk("open a kernel timer!\\n");
return 0;
}
static ssize_t sec_read (struct file *filp, char __user *buf, size_t size, \\
loff_t *ppos)
{
return 0;
}
static const struct file_operations sec_fops = {
.owner = THIS_MODULE,
.open = sec_open,
.read = sec_read,
};
static void *second_timer_handler(unsigned long data)
{
jiffies_to_timespec(jiffies, &sec_dev.timval);
printk("current jiffies : %d timer : %ld\\n",jiffies,sec_dev.timval.tv_usec);
mod_timer(&sec_dev.second_timer, jiffies + msecs_to_jiffies(1));
}
static int second_timer_probe(struct platform_device *pdev)
{
printk("second_timer_probe jiffies:%d,timer:%ld\\n",jiffies,jiffies/HZ);
init_timer(&sec_dev.second_timer);
sec_dev.second_timer.expires = jiffies + HZ;
sec_dev.second_timer.data = 0;
sec_dev.second_timer.function = second_timer_handler;
add_timer(&sec_dev.second_timer);
alloc_chrdev_region(&sec_dev.dev_num, 0, 1, "sec_timer");
cdev_init(&sec_dev.cdev, &sec_fops);
cdev_add(&sec_dev.cdev, sec_dev.dev_num, 1);
sec_dev.class = class_create(THIS_MODULE, "sec_timer");
sec_dev.dev = device_create(sec_dev.class, NULL, sec_dev.dev_num, NULL, "sec_timer");
return 0;
}
static int second_timer_release(struct platform_device *pdev)