在linux内核里的设备驱动常常用到定时器定时器功能,而硬件上的定时器通常只有几个,如果每个设备驱动使用一个硬件定时器,那肯定是不够用,所以在linux内核里会把一个硬件定时器的功能扩展成多个软件定时器.
例如: 硬件定时器每秒钟触发检查一次,那么就可以设置如3秒,4秒,10秒的软件定时器.
内核里的HZ定时器就是基于一个硬件的定时器来实现的.
它与内核源码目录的”.config”里的”CONFIG_HZ”的值有关, 通常值为200, 服务器的值为1000.
在h3里它的值为”CONFIG_HZ=100”, 表示此HZ定时器每秒钟触发100次,每次间隔10ms.
HZ定时器每次触发都会把全局变量jiffies的值加一, 意味着我们可通过jiffies的值的变化可判断出经过多久时间.
在linux内核里,宏HZ就是CONFIG_HZ
#define HZ CONFIG_HZ
// 以jiffies时间为单位的处理函数
#include <linux/jiffies.h>
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
time_after(a,b) //如果时间a是在时间b之后,则返回真.而且此函数已处理好jiffies溢出的问题
#define time_before(a,b) time_after(b,a)
//如延时3秒
unsigned long time = jiffies;
while (!(time_after(jiffies, time+3*HZ)))
;
//如延时30ms
unsigned long time = jiffies
while (!(time_after(jiffies, time+HZ*30/1000)))
;
//
内核里基于HZ定时器扩展的软件定时器timer_list.
struct timer_list {
struct list_head entry; //指向下一个定时器对象
unsigned long expires; //表示定时器的到期时间,也就是当jiffies的值与expires的值相等时,触发调用function函数.
void (*function)(unsigned long); //定时器超时调用的函数
unsigned long data; // function函数的参数
...
};
用法:
1). 初始化timer_list对象的成员
init_timer(&timer);
timer.function = func;
timer.data = data;
2). 增加到内核里的定时器链表里.
add_timer(&timer);
或者用mod_timer(&timer, expires) //当定时器对象还没加入定时链表里会自动加入,并且可修改定时的到期时间.
3). 定时器对象使用完后,需要从定时器链表里移除.
del_timer(&timer);
/
测试代码:
test.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/jiffies.h>
#include <linux/timer.h>
struct timer_list mytimer;
void timer_func(unsigned long data)
{
printk("timer time out ...%ld\n", data);
//除了开始时3秒钟触发,后面每5秒钟触发
mytimer.data += 1;
mod_timer(&mytimer, jiffies+5*HZ);
}
static int __init test_init(void)
{
init_timer(&mytimer);
mytimer.function = timer_func;
mytimer.data = 0;
//开始时3秒钟后,定时
mod_timer(&mytimer, jiffies + 3*HZ);
return 0;
}
static void __exit test_exit(void)
{
del_timer(&mytimer);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
//
timer_list定时器可用于按键,红外接收头等设备的去抖动处理。
如红外遥控器在按一下一个按键时,在红外接收头这端有可能接收到两个同样的键数据帧,这种场合就可用timer_list定时器来处理.
处理方式:当红外接收头接收到一个键的数据帧后,暂时关闭数据引脚的中断功能,直到两秒钟后再重新打开中断功能。在关闭中断的两秒钟内,不处理接收到键数据帧.
测试代码:
test.c:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <mach/gpio.h>
#include <linux/gpio.h>
#include <linux/ktime.h>
#include <linux/timer.h>
// 红外接收头的数据脚接的是PL11
#define IR_IO GPIOL(11)
int flag = 0; //表示数据帧的开始
int num = 0; //表示数据帧里的第几位数据
static long long prev = 0; //记录上次的时间
unsigned int times[40]; //记录每位数据的时间
struct timer_list mytimer;
void timer_func(unsigned long data)
{
enable_irq(gpio_to_irq(IR_IO)); //恢复中断
}
irqreturn_t irq_func(int irqno, void *arg)
{
long long now = ktime_to_us(ktime_get());
unsigned int offset;
int i, j, tmp;
if (!flag) //数据开始
{
flag = 1;
prev = now;
num = 0;
return IRQ_HANDLED;
}
offset = now - prev;
prev = now;
if ((offset > 13000) && (offset < 14000)) //判断是否收到引导码,引导码13.5ms
{
num = 0;
return IRQ_HANDLED;
}
//不是引导码时间,数据位时间
if (num < 32)
times[num++] = offset;
if (num >= 32)
{
for (i = 0; i < 4; i++) //共4个字节
{
tmp = 0;
for (j = 0; j < 8; j++) //每字节8位
{
if (times[i*8+j] > 2000) //如果数据位的信号周期大于2ms, 则是二进制数据1
tmp |= 1<<j;
}
printk("%02x ", tmp);
}
printk("\n");
flag = 0; //重新开始帧
disable_irq_nosync(gpio_to_irq(IR_IO)); //关闭中断
mod_timer(&mytimer, jiffies+2*HZ); //定时器两秒钟后超时,也就是两秒钟后才重新打开中断功能.
}
return IRQ_HANDLED;
}
static int __init test_init(void)
{
int ret;
ret = request_irq(gpio_to_irq(IR_IO), irq_func, IRQF_TRIGGER_FALLING, "myir", NULL);
if (ret < 0)
goto err0;
init_timer(&mytimer);
mytimer.function = timer_func;
return 0;
err0:
return ret;
}
static void __exit test_exit(void)
{
free_irq(gpio_to_irq(IR_IO), NULL);
del_timer(&mytimer);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");