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、实验:使用内核定时器实现按键消抖
Linux驱动学习—中断-CSDN博客
在上面链接的按键中断实验代码上修改:
按下按键: