linux
内核定时器是用来在未来某个时间点(基于jiffiles
),执行某个函数的一种机制,相关源码在linux/timer.c
文件中
linux内核定时器的超时函数运行一次后就不在运行了(相当于单次定时效果),但可以通过在超时函数中重新注册定时器来循环定时效果、在SMP
(多核CPU
)芯片的CPU
上,定时函数总是在注册它的同一CPU
上运行。
内核使用一个time_list
结构体来描述一个内核定时器,
路径:timer.h \linux-3.5\include\linux
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;
unsigned long expires; //未来超时时间
struct tvec_base *base;
void (*function)(unsigned long); //超时回调函数(参数为unsigned long型)
unsigned long data; //传递给超时调用函数的参数,也就是定时数据
int slack;
#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
,function
,data
几个成员,如果想定时多段时间,就定义多个定时器,实现必需的成员。然后向内核注册。
必须成员:expires
,function
,data
定是要素:定时时间、超时函数。这两个就是上面结构体中expires
,function
成员
function:是指向超时函数的函数指针,原型xxxfunction)(unsigned long data);
,当超时后内核会执行function
指向的函数,这个函数执行需要执行传递的实际参数,传入的实际参数就是结构体中的data
成员。
expires:未来的时间,基于jiffiles
变量时间。单位是时钟节拍,jiffiles
是内核全局变量,每个时钟节拍会自加一,表示系统开机到现在过去了多少个时钟节拍。
定时器定时就是:jiffiles
+
你要定时的时间对应的节拍数
定时2秒:jiffiles+2*HZ
->
HZ
表示1
秒钟对应的时钟节拍数
如果想定时ms级,怎么处理?
unsigned long msecs_to_jiffies(const unsigned int m)
功能:把毫秒转换成节拍数
参数:m
时间毫秒
返回:对应的时钟节拍数量
unsigned long usecs_to_jiffies(const unsigned int u)
功能:把微妙转换成节拍数
参数:u
时间毫秒
返回:对应的时钟节拍数量
示例:要定时从现在开始,3毫秒执行一个函数
expires:jiffiles+usecs_to_jiffies(3)
DEFINE_TIMER(_name, _function, _expires, _data)
功能:定义一个名为_name
的struct timer_list
结构体变量,并且初始化它的 _function
, _expires
, _data
成员。
参数:
_name:struct timer_list
结构体变量名
_function,_expires,_data:分别用来填充_name
结构体变量的expires
,function
,data
成员
init_timer(timer);
功能:只是对struct timer_list
结构成员进行一些初始化操作expires
,function
,data
这几个成员还是要用户自己填充。
参数:timer
应该是一个struct timer_list
结构变量的地址
setup_timer(timer, fn, data)
功能:设置定时器中的function
,data
成员和一些基础成员,expires
这个成员并没有初始化,需要用户自己进行初始化
参数:
timer:应该是一个struct timer_list
结构变量的地址
fn,data:分别是timer
结构function
,data
成员
void add_timer(struct timer_list *timer);
功能:向内核注册一个定时器,注册后会马上开始计时
参数:
timer:是一个 struct timer_list
结构变量地址,并且要求这个结构变量是已经初始化好必须成员
extern int del_timer(struct timer_list * timer);
功能:从内核定时链表上删除指定定时器,删除后不会再执行绑定的函数
参数:
timer:是一个 struct timer_list
结构变量地址,并且要求这个结构变量是已经初始化好必须成员
extern int mod_timer(struct timer_list *timer, unsigned long expires);
功能:修改定时器定时时间,并且重新注册,不管定时器的超时函数是否执行过。执行完马上启动定时
参数:
timer:要修改的定时器结构变量地址,要求这个结构变量function
,data
成员是已经初始化好的。
expires:用来填充结构体中expires
成员,也就是未来时间点。
#include
void timerlist_function(unsigned long data){···};
time_list
变量struct timer_list timelist
还需要自己去初始化
init_timer(&timelist);
setup_timer(&timelist,timerlist_function,123);
这里并没有初始化超时时间还需要自己初始化
timelist.expires=jiffies+2*HZ;
如果使用静态方式注册,可以把上面两步一次完成
DEFINE_TIMER(timelist,timelist_function,timelist_expires=jiffies+2*HZ,123);
add_timer
注册定时器或者使用mod_timer
修改定时器定时时间add_timer(&timelist);
或者
mod_timer(&timelist,jiffies+2*HZ);
del_timer
取消一个定时器del_timer(&timelist);
#include
#include
//添加头文件
#include
//实现一个超时函数
void timerlist_function(unsigned long data)
{
printk("%s is call!! data:%d\r\n",__FUNCTION__,data);
}
//定义一个time_list结构变量
struct timer_list timelist;
static int __init timerlist_init(void)
{
//对timer_list结构进行初始化
init_timer(&timelist);
setup_timer(&timelist,timerlist_function,123);
timelist.expires=jiffies+2*HZ;
printk("%s is call!!\r\n",__FUNCTION__);
//注册定时器,启动定时
add_timer(&timelist);
return 0;
}
static void __exit timerlist_exit(void)
{
//删除定时器
del_timer(&timelist);
printk("定时器已经删除\r\n");
}
module_init(timerlist_init);
module_exit(timerlist_exit);
MODULE_LICENSE("GPL");
[root@ZC/zhangchao]#insmod timer.ko
[ 191.700000] timerlist_init is call!!
[root@ZC/zhangchao]#[ 193.705000] timerlist_function is call!! data:123
#include
#include
//添加头文件
#include
//实现一个超时函数
void timerlist_function(unsigned long data)
{
printk("%s is call!! data:%d\r\n",__FUNCTION__,data);
mod_timer(&timelist,jiffies+2*HZ);
}
//定义一个time_list结构变量
struct timer_list timelist;
static int __init timerlist_init(void)
{
//对timer_list结构进行初始化
init_timer(&timelist);
setup_timer(&timelist,timerlist_function,123);
timelist.expires=jiffies+2*HZ;
printk("%s is call!!\r\n",__FUNCTION__);
//注册定时器,启动定时
add_timer(&timelist);
return 0;
}
static void __exit timerlist_exit(void)
{
//删除定时器
del_timer(&timelist);
printk("定时器已经删除\r\n");
}
module_init(timerlist_init);
module_exit(timerlist_exit);
MODULE_LICENSE("GPL");
KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += timer.o
[root@ZC/zhangchao]#insmod timer.ko
[ 533.390000] timerlist_init is call!!
[root@ZC/zhangchao]#[ 535.395000] timerlist_function is call!! data:123
[ 537.400000] timerlist_function is call!! data:123
[ 539.405000] timerlist_function is call!! data:123
[ 541.410000] timerlist_function is call!! data:123
rmmod[ 543.415000] timerlist_function is call!! data:123
[ 545.420000] timerlist_function is call!! data:123
[ 547.425000] timerlist_function is call!! data:123
[ 549.430000] timerlist_function is call!! data:123
[ 551.435000] timerlist_function is call!! data:123
[ 553.440000] timerlist_function is call!! data:123
[ 555.445000] timerlist_function is call!! data:123
[ 557.450000] timerlist_function is call!! data:123
实际中,按键按下时并不是简单的电平变化,而是在电平变化期间中间存在很多毛刺
单纯的读取电平可能因为干扰而造成按键状态读取错误与实际情况不相符的情况,比如按键按下,由于毛刺干扰的存在,读取的电平确是按键没有按下的状态,这样就给目标程序错误的指示而发生错误。但这样的情况可以最大限度的避免,一个是硬件电路上加上消抖电路,在软件上也能采取一定措施:
在裸机上的做法:
按键按下电平会发生变化,延时一段时间,再次读取按键电平,如果电平确实变化证明按键状态确实发生改变,如果没有变化证明是干扰,认为按键没有发生状态改变。延时时间根据实际情况,既不能太短(滤波不完全,也不能太长影响系统实时性)。
上面的方式有问题,由于实际中各个板子按键的材质等的不同,需要滤波的时间也不相同,因此延时时间也不相同,一份代码很难做到全部适用。
解决办法:
使用内核定时器的循环定时实现:
在按键中断程序中修改定时时间
实现原理:如果是抖动则两个边沿时间一般会很短所以在中断程序中,定时5ms一般可以涵盖两个相邻的边沿。定时未完成下一个中断又会触发中断,只要有抖动就会不断触发中断计时。最后一次抖动,IO电平就稳定了,在最后一次5ms就有机会产生超时中断,执行相应代码。
#include
struct key_info{
int key_gpio; //按键IO
int key_num; //按键编号
const char* name; //按键名称
struct timer_list timer;//添加定时器结构
};
for(i=0;i<4;i++)
{
//注册定时器 将结构体传入 在超时函数中还原
init_timer(&keys[i].timer);
setup_timer(&keys[i].timer,timerlist_function,(long)&keys[i]);
//获取按键键值 注册按键
irq=gpio_to_irq(keys[i].key_gpio);
err=request_irq(irq, key_handler,flags,keys[i].name,&keys[i]); //注册key1中断
//如果有注册失败的 跳出循环
if(err<0)
{
break;
}
}
//实现一个超时函数
//只有IO电平稳定了才会执行超时函数,执行函数可以直接读取IO电平状态
//程序要把按键的状态,保存起来,让用户通过用户变成API接口来读取按键状态
//所以,函数中完成1):判断按键按下还是松开;2)把按键状态存储起来
void timerlist_function(unsigned long data)
{
int dn; //存放按键状态
//注册的时候传递进来的是每个案件元素的首地址,类型是struct key_info*
//这里进行还原,还原后可以取得这个结构的所有元素信息
struct key_info *pdev_data=(struct key_info*)data;
//判断按下还是松开按键;可通过读取按键IO电平状态来判断
//硬件上,按下按键返回电平0 松开返回1
dn=gpio_get_value(pdev_data->key_gpio);
if(!dn)
printk("key%d down!!\r\n",pdev_data->key_num+1);
else
printk("key%d up!!\r\n",pdev_data->key_num+1);
//存储按键状态到按键缓冲区
//这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
kbuf[pdev_data->key_num]= '0'+!dn;
}
*key中断服务函数:
启动定时器
消除抖动
*/
irqreturn_t key_handler(int irq, void * dev_id)
{
struct key_info *pdev_data=(struct key_info*)dev_id;
//修改定时器定时50ms后超时
mod_timer(&pdev_data->timer,jiffies + msecs_to_jiffies(50));
return IRQ_HANDLED;
}
“
for(i=0;i<4;i++)
{
irq=gpio_to_irq(keys[i].key_gpio);
free_irq(irq,&keys[i]);
del_timer(&keys[i].timer); //删除定时器
}
“
#include
#include
#include
#include
#include
#include
#include
#include
#include //增加自动创建设备头文件
#include
#include //中断注册注销头文件
#include //gpio相关的头文件
#include //添加定时器头文件
//按键数量
#define KEY_SIZE (4)
//按键缓冲区
unsigned char kbuf[KEY_SIZE]={"0000"}; //储存按键状态 初始化为字符 0,0表示没有按下,1表示按下
struct key_info{
int key_gpio; //按键IO
int key_num; //按键编号
const char* name; //按键名称
struct timer_list timer;//添加定时器结构
};
struct key_info keys[]={
{EXYNOS4_GPX3(2),0,"key1"},
{EXYNOS4_GPX3(3),1,"key2"},
{EXYNOS4_GPX3(4),2,"key3"},
{EXYNOS4_GPX3(5),3,"key4"}
};
//定义字符设备结构体
static struct cdev *xxxdriver_cdev;
//定义设备号(包含主次)
static dev_t xxxdriver_num=0;
//定义设备类
static struct class *xxxdriver_class;
//定义设备结构体
static struct device *xxxdriver_device;
//定义设备名称
#define XXXDRIVER_NAME "mydevice"
ssize_t XXX_read(struct file *file, char __user *usr, size_t size, loff_t *loft)
{
if(size>KEY_SIZE)
{
size=KEY_SIZE;
}
if(copy_to_user(usr,kbuf,size))
{
printk("copy to user faild\r\n");
return -EFAULT;
}
return size;
}
ssize_t XXX_write (struct file *file, const char __user *usr, size_t size, loff_t *loft)
{
printk("file write success!!\r\n");
return 0;
}
int XXX_open (struct inode *node, struct file *pfile)
{
printk("files open success!!\r\n");
return 0;
}
loff_t XXX_llseek(struct file *pfile, loff_t loft, int whence){
printk("file lseek success!!\r\n");
return 0;
}
int XXX_release (struct inode *node, struct file *file)
{
printk("file close success!!\r\n");
return 0;
}
/*key中断服务函数:
启动定时器
消除抖动
*/
irqreturn_t key_handler(int irq, void * dev_id)
{
struct key_info *pdev_data=(struct key_info*)dev_id;
printk("key_handler is run!!\r\n"); //进入中断打印方便观察
//修改定时器定时50ms后超时
mod_timer(&pdev_data->timer,jiffies + msecs_to_jiffies(50));
return IRQ_HANDLED;
}
//实现一个超时函数
//只有IO电平稳定了才会执行超时函数,执行函数可以直接读取IO电平状态
//程序要把按键的状态,保存起来,让用户通过用户变成API接口来读取按键状态
//所以,函数中完成1):判断按键按下还是松开;2)把按键状态存储起来
void timerlist_function(unsigned long data)
{
int dn; //存放按键状态
//注册的时候传递进来的是每个案件元素的首地址,类型是struct key_info*
//这里进行还原,还原后可以取得这个结构的所有元素信息
struct key_info *pdev_data=(struct key_info*)data;
//判断按下还是松开按键;可通过读取按键IO电平状态来判断
//硬件上,按下按键返回电平0 松开返回1
dn=gpio_get_value(pdev_data->key_gpio);
if(!dn)
printk("key%d down!!\r\n",pdev_data->key_num+1);
else
printk("key%d up!!\r\n",pdev_data->key_num+1);
//存储按键状态到按键缓冲区
//这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
kbuf[pdev_data->key_num]= '0'+!dn;
}
//文件操作函数结构体
static struct file_operations xxxdriver_fops={
.owner=THIS_MODULE,
.open=XXX_open,
.release=XXX_release,
.read=XXX_read,
.write=XXX_write,
.llseek=XXX_llseek,
};
static __init int xxxdriver_init(void)
{
//定义错误返回类型
int err;
//定义中断编号
int irq,i;
//设置中断属性 非共享中断 上升下降沿触发
unsigned long flags=IRQF_DISABLED |
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING;
for(i=0;i<4;i++)
{
//注册定时器 将结构体传入 在超时函数中还原
init_timer(&keys[i].timer);
setup_timer(&keys[i].timer,timerlist_function,(long)&keys[i]);
//获取按键键值 注册按键
irq=gpio_to_irq(keys[i].key_gpio);
err=request_irq(irq, key_handler,flags,keys[i].name,&keys[i]); //注册key1中断
//如果有注册失败的 跳出循环
if(err<0)
{
break;
}
}
//有注册失败的 将前面注册成功的释放掉
if(err<0){
for(--i;i>0;i--)
{
irq=gpio_to_irq(keys[i].key_gpio);
free_irq(irq, &keys[i]);
}
}
//分配字符设备结构体,前面只是定义没有分配空间
xxxdriver_cdev=cdev_alloc();
//判断分配成功与否
if(xxxdriver_cdev==NULL)
{
err=-ENOMEM;
printk("xxxdriver alloc is err\r\n");
goto err_xxxdriver_alloc;
}
//动态分配设备号
err=alloc_chrdev_region(&xxxdriver_num, 0, 1, XXXDRIVER_NAME);
//错误判断
if(err<0)
{
printk("alloc xxxdriver num is err\r\n");
goto err_alloc_chrdev_region;
}
//初始化结构体
cdev_init(xxxdriver_cdev,&xxxdriver_fops);
//驱动注册
err=cdev_add(xxxdriver_cdev,xxxdriver_num,1);
if(err<0)
{
printk("cdev add is err\r\n");
goto err_cdev_add;
}
//创建设备类
xxxdriver_class=class_create(THIS_MODULE,"xxx_class");
err=PTR_ERR(xxxdriver_class);
if(IS_ERR(xxxdriver_class))
{
printk("xxxdriver creat class is err\r\n");
goto err_class_create;
}
//创建设备
xxxdriver_device=device_create(xxxdriver_class,NULL, xxxdriver_num,NULL, "xxxdevice");
err=PTR_ERR(xxxdriver_device);
if(IS_ERR(xxxdriver_device))
{
printk("xxxdriver device creat is err \r\n");
goto err_device_create;
}
printk("xxxdriver init is success\r\n");
return 0;
//硬件初始化部分
err_device_create:
class_destroy(xxxdriver_class);
err_class_create:
cdev_del(xxxdriver_cdev);
err_cdev_add:
unregister_chrdev_region(xxxdriver_num, 1);
err_alloc_chrdev_region:
kfree(xxxdriver_cdev);
err_xxxdriver_alloc:
return err;
}
static __exit void xxxdriver_exit(void)
{
int irq,i;
for(i=0;i<4;i++)
{
irq=gpio_to_irq(keys[i].key_gpio);
free_irq(irq,&keys[i]);
del_timer(&keys[i].timer); //删除定时器
}
device_destroy(xxxdriver_class,xxxdriver_num);
class_destroy(xxxdriver_class);
cdev_del(xxxdriver_cdev);
unregister_chrdev_region(xxxdriver_num, 1);
printk("xxxdriver is exit\r\n");
}
module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
int i,file_fp;
unsigned char btn[4]={"0000"},cur[4]={"0000"};
file_fp = open(argv[1],O_RDWR);
while(1)
{
read(file_fp,cur,4);
for(i=0;i<4;i++)
{
if(cur[i]!=btn[i])
{
btn[i]=cur[i];
if(cur[i]=='1')
printf("按键%d 按下\r\n",i+1);
else
printf("按键%d 弹起\r\n",i+1);
}
}
}
close(file_fp);
}
KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += btn.o
[root@ZC/zhangchao]#insmod timer.ko
[ 242.080000] xxxdriver init is success
[root@ZC/zhangchao]#./app /dev/xxxdevice
[ 254.155000] files open success!!
[ 258.150000] key_handler is run!!
[ 258.200000] key2 down!!
按键2 按下
[ 258.340000] key_handler is run!!
[ 258.390000] key2 up!!
按键2 弹起
[ 259.550000] key_handler is run!!
[ 259.600000] key3 down!!
按键3 按下
[ 259.685000] key_handler is run!!
[ 259.735000] key3 up!!
按键3 弹起
[ 260.580000] key_handler is run!!
[ 260.630000] key4 down!!
按键4 按下
[ 260.740000] key_handler is run!!
[ 260.790000] key4 up!!
按键4 弹起