1、内核定时器
1.1 Linux内核定时器概念
不同于单片机定时器,LInux内核定时器是一种基于未来时间点的计时方式,以当前时刻来启动的时间点,以未来的某一时刻为终止点。比如,现在是10点5分,我要定时5分钟,那么定时就是10点5分+5分钟=10点10分。这个和手机闹钟很相似。比如你要定一个第二天早晨8点的闹钟,就是当前时间定时到第二天早晨8点。
需要注意的是,内核定时器定时精度不高,不能作为高精度定时器使用。并且内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。
1.2 Linux内核定时器基础知识
Linux内核使用timer_list结构体表示内核定时器,timer_list定义在文件include/linux/timer.h中,定义如下:
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct hlist_node entry;
unsigned long expires;/*定时器超时时间,不是时长,单位是节拍数*/
void (*function)(unsigned long);/*定时处理函数*/
unsigned long data;/*要传递function函数的参数*/
u32 flags;
#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
在上面这个结构体中,有几个参数需要重点关注一下。一个是expires到期时间,单位是节拍数。等于定时的当前的始终节拍计数(存储在系统的全局变量和jiffies)+定时时长对应的时钟节拍数量。
那么如何把时间 转换成节拍数量呢?示例:假如从现在开始定时1秒,转换成节拍数量是多少呢? 内核中有一个宏HZ,表示一秒对应的时钟节拍数,那么我们就可以通过这个宏来把时间转换成节拍数。所以,定时1秒就是expires = jiffies + 1*HZ。
HZ的值我们是可以设置的,也就是一秒对应的时钟拍数我们是可以设置的,Linux内核会使用CONFIG_HZ来设置自己的系统时钟。打开include/asm-generic/param.h,有如下内容:
# undef HZ
# define HZ CONFIG_HZ /* Internal kernel timer frequency */
# define USER_HZ 100 /* some user interfaces are */
宏HZ就是CONFIG_HZ,因此HZ=100,表示一秒的节拍数是100,在编译Linux内核的时候可以通过图形化界面设置系统节拍率,按照如下路径打开配置界面。
通过上图我们可以发现可选的系统节拍率为100HZ,200HZ,250HZ,300HZ、500HZ和1000HZ.默认是100HZ。
第二个需要关心的参数是function超时能处理处理函数,这个并不是硬件中断 服务程序。原型:
void function(unsigned long data);
第三个参数是data传递给超时处理函数的参数,可以把一个变量的地址转换成unsigned long。
1.3 Linux内核定时器相关操作函数
1.3.1 时间转换函数
<1>ms转换成时钟节拍函数
在include/linux/jiffies.h
unsigned long msecs_to_jiffies(const unsigned int m);
举例:定时10ms 计算:jiffes+msecs_to_jiffies(10)
<2>us转换成时钟节拍函数
unsigned long usecs_to_jiffies(const unsigned int u);
举例:定时10us 计算:jiffes+usecs_to_jiffies(10)
1.3.2 宏DEFINE_TIMER
在include\linux\timer.h
#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)
参数:_name变量名;__function超时处理函数;_expires到点时间,一般在启动定时前需要重新初始化。
作用:静态定义结构体变量并且初始化function,expires,data成员。
1.3.3 add_timer函数
在include\linux\timer.h
void add_timer(struct timer_list *timer);
参数:timer要注册的定时器。
作用:add_timer函数用于向Linux内核注册定时器,使用add_timer函数向内核注册定时器以后,定时器就会开始运行。
1.3.4 del_timer函数
int del_timer(struct timer_list * timer);
timer:要删除的定时器。
返回值:0,定时器还没被激活;1,定时器已经激活
作用:del_timer函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器运行,因此在调用del_timer函数删除定时器之前要先等待其他处理器的定时处理其函数退出。
1.3.5 mod_timer 函数
int mod_timer(struct timer_list *timer, unsigned long expires);
参数:
timer:要修改超时时间(定时值)的定时器。
expires:修改后的超时时间。
返回值:0,调用mod_timer 函数前定时器违背激活;
1,调用mod_timer 函数前定时器已被激活
作用:mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器。
1.4 实验代码
#include <linux/init.h>
#include <linux/module.h>//最基本的文件,支持动态添加和卸载模块
#include <linux/miscdevice.h>//注册杂项设备头文件
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/timer.h>
static void timer_function(unsigned long data);
DEFINE_TIMER(test_timer, timer_function, 0, 0);
static void timer_function(unsigned long data)
{
printk("this is timer_function\n");
mod_timer(&test_timer, jiffies + 1*HZ);
}
static int hello_init(void)
{
test_timer.expires = jiffies + 1 * HZ;
add_timer(&test_timer);
return 0;
}
static void hello_exit(void)
{
del_timer(&test_timer);
printk("hello_exit \n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
2、实验:使用内核定时器实现按键消抖
在上面链接的按键中断实验代码上修改:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
#include <linux/input.h>
struct device_node *test_device_node;
struct property *test_node_property;
int gpio_num;
int irq = 0;
DEFINE_TIMER(test_timer, timer_funtion, 0, 0);
static void timer_funtion(ubsigned long data)
{
printk("this is timer_function\n");
}
static const of_device_id of_match_table_test[] = {//匹配表
{.compatible = "keys"},
};
static const platform_device_id beep_id_table ={
.name = "beep_test",
};
irqreturn_t test_key(int irq, void *args)
{
printk("test_key\n");
test_timer.expires = jiffies + msec_to_jiffies(20);
add_timer(&test_timer);
return IRQ_HANDLED;
}
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{
int ret = 0;
printk("beep_probe\n");
//查找要查找的节点
test_device_node = of_find_node_by_path("/test_key");
if (test_device_node == NULL) {
printk("test_device_node find error\n");
return -1;
}
printk("test_device_node name is %s\n",test_device_node->name);//test_key
gpio_num = of_get_named_gpio(test_device_node, "gpios", 0);
if (gpio_num < 0) {
printk("of_get_named_gpio error\n");
return -1;
}
printk("gpio_num name is %d\n",gpio_num);
gpio_direction_input(gpio_num);
//获取中断号
//irq = gpio_to_irq(gpio_num);
irq = irq_of_parse_and_map(test_device_node, 0);
printk("irq is %d\n",irq);
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);//
if (ret < 0) {
printk("request_irq error\n");
return -1;
}
return 0;
}
int beep_remove(struct platform_device *pdev)
{
pritnk("beep_remove \n");
return 0;
}
strcut platform_driver beep_device = {
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "123",
.of_match_table = of_match_table_test,//匹配表
},
.id_table = &beep_id_table,
};
static int beep_driver_init(void)
{
int ret = -1;
ret = platform_driver_register(&beep_device);
if(ret < 0) {
printk("platform_driver_register error \n");
}
printk("platform_driver_register ok\n");
return 0;
}
static void beep_driver_exit(void)
{
free_irq(irq, NULL);
platform_driver_unregister(&beep_device);
printk("beep_driver_exit \n");
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");
按下按键: