硬件定时器提供时钟源,时钟源的频率设置完成后会产生周期性的定时中断,系统使用定时中断计时。
中断周期性产生的频率为系统频率,也称节拍率,单位HZ,HZ表示一秒的节拍数,即频率,频率大小包括100、200、250、300、500和1000。
高节拍率和低节拍率的优缺点
- 高节拍率会提高系统时间精度,如果采用 100HZ 的节拍率,时间精度就是 10ms,采用 1000HZ 的话时间精度就是 1ms,精度提高了 10 倍。高精度时钟的好处有很多,对于那些对时间要求严格的函数,能够以更高的精度运行,时间测量更准确。
- 高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担,1000Hz 和 100HZ的系统节拍率相比,系统要花费 10 倍的“精力”去处理中断。中断服务函数占用处理器的时间增加,但是现在的处理器性能都很强大,所以采用 1000Hz 的系统节拍率并不会增加太大的负载压力。
根据实际情况选择合适的系统节拍率。
jiffies
Linux内核使用全局变量jiffies记录系统从启动以来的系统节拍数,系统启动时,Jiffies会初始化为0。 jiffies_64用于64位系统,jiffies用于32位系统,jiffies就是jiffies_64的低32位,不管在32位还是64位系统都可以使用jiffies。
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
jiffies表示系统运行的节拍数,HZ表示每秒的节拍数,jiffies/HZ表示系统运行时间,单位秒。
不管是32位还是64位的 jiffies,都有溢出的风险,溢出以后会重新从0开始计数,这个现象成为绕回。假如 HZ 为最大值1000,32位的jiffies只需要49.7天绕回,对于64位的jiffies来说大概需要5.8亿年才能绕回,因此jiffies_64的绕回忽略不计,主要处理32 位jiffies 的绕回。
处理绕回的API函数
- unkown为jiffies,known为对比的值。
- unkown大于known,time_after返回真。
- unkown小于known,time_before返回真。
- time_after_eq和time_before_eq和上面两个函数类似,只是多了判断等于的条件。
time_after(unkown, known);
time_before(unkown, known);
time_after_eq(unkown, known);
time_before_eq(unkown, known);
jiffies和ms、us、ns之间的转换函数
// 将jiffies类型的参数分别转换为对应的毫秒、微秒、纳秒
int jiffies_to_msecs(const unsigned long j);
int jiffies_to_usecs(const unsigned long j);
u64 jiffies_to_nsecs(const unsigned long j);
// 将毫秒、微秒、纳秒转换为jiffies类型
long msecs_to_jiffies(const unsigned int m);
long usecs_to_jiffies(const unsigned int u);
unsigned long nsecs_to_jiffies(u64 n);
内核定时器
Linux内核定时器只需要提供超时时间和定时处理函数,当超时时间到了就会执行定时处理函数。
内核定时器不是周期运行的,超时后会自动关闭,要实现周期性定时,需要在定时处理函数中重新开启定时器。
LInux内核定义timer_list结构体表示内核定时器。
- expires表示超时时间,单位为节拍数,如果定义一个周期为2秒的定时器,则超时时间为expiers = jiffies + (2*HZ);。
- function表示定时处理函数。
- data表示要传递给function的参数。
struct timer_list {
struct list_head entry;
unsigned long expires;
struct tvec_base *base;
void (*function)(unsigned long);
unsigned long data;
int slack;
};
初始化定时器API函数
init_timer
初始化timer_list类型变量。
- timer:初始化的定时器
- 返回值:无
void init_timer(struct time_list Itimer);
add_timer
向Linux内核注册定时器,注册后定时器开始运行。
- timer:初始化的定时器
- 返回值:无
void add_timer(struct timer_list *timer);
del_timer
删除一个定时器,在多处理器系统中,定时器可能会在其他处理器上运行,删除定时器前要先等待其他处理器的定时处理函数退出。
- timer:要删除的定时器
- 返回值:0,定时器还没被激活;1,定时器已经激活
int del_timer(struct timer_list *timer);
del_timer_sync
det_timer函数的 同步版,等待其他处理器使用完定时器再删除,不能用于中断上下文中。
- timer:要删除的定时器
- 返回值:0,定时器还没被激活;1,定时器已经激活
int del_timer_sync(struct timer_list *timer);
mod_timer
修改定时器值,如果定时器还没有激活,mod_timer会激活定时器。
- tiemr:要修改的定时器
- expires:修改后的超市时间
- 返回值:0,调用mode_timer前定时器未被激活,调用mod_timer前定时器已经被激活
int mod_timer(struct timer_list *timer, unsigned long expires);
内核定时器使用流程
// 设备结构体
struct xxx_dev{
int timeperiod; // 定时周期
struct timer_list timer; // 定义定时器
};
struct xxx_dev xxxdev;
// 定时器回调函数
void xxx_function(unsigned long arg)
{
struct xxx_dev *dev = (struct xxx_dev *)arg;
/*
* 定时器处理代码
*/
// 定时器需要周期性运行,使用mod_timer函数设置超时时间并启动定时器
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
}
// 驱动入口函数
static int __init xxx_init(void)
{
init_timer(&xxxdev.timer); // 初始化定时器
xxxdev.timer.function = xxx_function; // 定时器处理函数
xxxdev.timer.expires = jiffies + msecs_to_jiffies(2000); // 超时时间2秒
xxxdev.timer.data = (unsigned long)&xxxdev; // 把设备结构体作为参数传递给function
add_timer(&timer); // 注册启动定时器
}
// 驱动出口函数
static void __exit xxx_exit(void)
{
del_timer(&xxxdev.timer); // 删除定时器,或使用del_timer_sync(&timer);
}
短延时函数
Linux内核提供毫秒、微妙和纳秒延时函数
// 纳秒延时
void ndelay(unsigned long nsecs);
// 微妙延时
void udelay(unsigned long usecs);
// 毫秒延时
void mdelay(unsigned long msecs);