linux驱动之内核定时器驱动设计

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

linux驱动之内核定时器驱动设计
我的环境:
Fedora 14 内核版本为2.6.38.1
开发板:ARM9 TQ2440
移植内核版本:linux-2.6.30.4
定时器在linux内核中主要是采用一个结构体实现的。

但是需要注意定时器是一个只运行一次的对象,也就是
当一个定时器结束以后,还需要重现添加定时器。

但是
可以采用mod_timer()函数动态的改变定时器到达时间。

这个驱动主要实现内核定时器的基本操作。

内核定时器
主要是是通过下面的结构体struct timer_list实现。

需要的头文件包括#include;,但是在实际开发过程中不需要包含该头文件,因为在sched.h中包含了该头文件。

struct timer_list {
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
struct tvec_base *base;
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
定时器的实现主要是该结构体的填充和部分函数的配合即可完成。

其中红色的部分是最主要的几个元素,1、expires主要是用来定义定时器到期的时间,通常采用jiffies这个全局变量和HZ这个全局变量配合设置该元素的值。

比如expires = jiffies + n*HZ,其中jiffies 是自启动以来的滴答数,HZ是一秒种的滴答数。

2、function可以知道是一个函数指针,该函数就是定
时器的处理函数,类似我们在中断中的中断函数,其实
定时器和中断有很大的相似性。

定时器处理函数是自己
定义的函数。

3、data通常是实现参数的传递,从function的参数类
型可以知道,data可以作为定时器处理函数的参数。

其他的元素可以通过内核的函数来初始化。

初始化函数为:
init_timer(struct timer_list * timer);
或者直接DEFINE_TIMER宏实现定义和初始化操作。

#define DEFINE_TIMER(_name, _function,
_expires, _data)
\
struct timer_list _name =
\
TIMER_INITIALIZER(_function, _expires, _data) 添加定时器到内核的函数:
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->;expires);
}
删除定时器函数,如果定时器的定时时间还没有到达,那么才可以删除定时器:
int del_timer(struct timer_list *timer)
修改定时器的到达时间,该函数的特点是,不管定时器是否到达时间,都会重现添加一个定时器到内核。


以可以在定时处理函数中可以调用该函数修改需要重新
定义的到达时间。

int mode_timer(struct timer_list *timer,unsigned long expires)
int mod_timer(struct timer_list *timer, unsigned long expires)
{
/*
* This is a common optimization triggered by the
* networking code - if the timer is re-modified
* to be the same thing then just return:
*/
if (timer->;expires == expires &&
timer_pending(timer))
return 1;
/*注意调用的条件,也就是说明当前的定时器为链
表的最后一个*/
return __mod_timer(timer, expires, false); }
static inline int
__mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
{
struct tvec_base *base, *new_base;
unsigned long flags;
int ret;
ret = 0;
timer_stats_timer_set_start_info(timer);
BUG_ON(!timer->;function);
base = lock_timer_base(timer, &flags); if (timer_pending(timer)) {
detach_timer(timer, 0);
ret = 1;
} else {
if (pending_only)
goto out_unlock;
}
debug_timer_activate(timer);
new_base = __get_cpu_var(tvec_bases);
if (base != new_base) {
/*
* We are trying to schedule the timer on the local CPU.
* However we can't change timer's base while it is running,
* otherwise del_timer_sync() can't detect that the timer's
* handler yet has not finished. This also guarantees that
* the timer is serialized wrt itself.
*/
if (likely(base->;running_timer != timer)) { /* See the comment in lock_timer_base() */ timer_set_base(timer, NULL);
spin_unlock(&base->;lock);
base = new_base;
spin_lock(&base->;lock);
timer_set_base(timer, base);
}
}
timer->;expires = expires;
internal_add_timer(base, timer);
out_unlock:
spin_unlock_irqrestore(&base->;lock, flags); return ret;
}
static void internal_add_timer(struct
tvec_base *base, struct timer_list *timer)
{
unsigned long expires = timer->;expires;
unsigned long idx = expires -
base->;timer_jiffies;
struct list_head *vec;
if (idx ;tv1.vec + i;
} else if (idx ;>; TVR_BITS) & TVN_MASK;
vec = base->;tv2.vec + i;
} else if (idx ;>; (TVR_BITS + TVN_BITS)) & TVN_MASK;
vec = base->;tv3.vec + i;
} else if (idx ;>; (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
vec = base->;tv4.vec + i;
} else if ((signed long) idx ;tv1.vec +
(base->;timer_jiffies & TVR_MASK);
} else {
int i;
/* If the timeout is larger than 0xffffffff on 64-bit
* architectures then we use the maximum timeout:
*/
if (idx >; 0xffffffffUL) {
idx = 0xffffffffUL;
expires = idx + base->;timer_jiffies;
}
i = (expires >;>; (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
vec = base->;tv5.vec + i;
}
/*
* Timers are FIFO:
*/
/*添加到链表的最后,这说明mod_timer实现了重新注册一个定时器的操作*/
list_add_tail(&timer->;entry, vec);
}
从上面的分析可以看出,mod_timer的实现过程比较复杂,但是基本上说明了mod_timer函数重新注册定时器的操作过程。

一般而言定时器的基本操作主要是上面的几个函数。

我的基于内核定时器的驱动函数如下,参考了宋宝华的Linux设备驱动开发详解(第二版)。

驱动程序:
#include;
#include;
#include;
#include;
#include;
#include;
#include;
#include;
#include;
#include;
#include;
/*采用宏定义设置设备的主设备号*/
#define SECOND_MAJOR
/*静态的分别保存静态主设备号的变量*/
static int second_major = SECOND_MAJOR;
/*设备结构体,通常在设备中包含需要的设备,比如字符、块等类型*/
struct second_dev{
/*添加设备类型,
我认为可以采用一个联合体,
包含块设备或者字符设备,类似inode的实现方法,这样可以提高结构体的通用性
*/
struct cdev cdev;
/*原子变量,用来统计*/
atomic_t counter;
/*添加内核定时器结构体变量*/
struct timer_list s_timer;
/*用于动态创建设备文件的设备类*/
struct class *myclass;
};
/*结构体指针或者采用全局变量直接定义结构都可以*/
struct second_dev *second_devp;
/*如果定时时间到了,定时器的处理函数*/
static void second_timer_handler(unsigned long arg)
{
/*
修改定时器中的到期时间,增加时间为1s,
需要注意的是mod_timer函数是重新注册定时器到
内核
而不管定时器是否被运行过
*/
mod_timer(&second_devp->;s_timer,jiffies + HZ); /*原子变量的增加*/
atomic_inc(&second_devp->;counter);
/*输出jiffies值*/
printk(KERN_NOTICE "Current jiffies
is %d\n",jiffies);
}
/*open函数实现*/
static int second_open(struct inode
*inode,struct file *filp)
{
/*初始化定义的内核定时器*/
init_timer(&second_devp->;s_timer);
/*指定内核定时器的处理函数是上面定义好的函数*/
second_devp->;s_timer.function =
second_timer_handler;
/*指定定时间隔是1s*/
second_devp->;s_timer.expires = jiffies + HZ; /*将定时器添加到内核*/
add_timer(&second_devp->;s_timer);
/*同时设备相关的统计值为0*/
atomic_set(&second_devp->;counter,0);
return 0;
}
/*release函数的实现*/
static int second_release(struct inode
*inode,struct file *filp)
{
/*如果没有到时间就关闭设备,直接删除定时器*/
del_timer(&second_devp->;s_timer);
return 0;
}
/*read函数的实现*/
static ssize_t second_read(struct file
*filp,char __user *buf,size_t count,loff_t *ppos) {
int counter;
/*读当前的值*/
counter = atomic_read(&second_devp->;counter); /*
采用put_user实现数值的传送
put_user函数存在对指针变量的检查,
因此不需要检测指针是否正确
*/
if(put_user(counter,(int *)buf))
return -EFAULT;
else
/*返回数据大小*/
return sizeof(unsigned int);
}
/*具体的文件操作集合*/
static const struct file_operations second_fops =
{
/*这是拥有者*/
.owner = THIS_MODULE,
.open = second_open,
.release = second_release,
.read = second_read,
};
/*初始化函数*/
static int __init second_init(void) {
int ret;
/*设备号的申请,创建*/
dev_t devno = MKDEV(second_major,0); /*静态申请设备号*/
if(second_major)
{
ret =
register_chrdev_region(devno,1,"second" );
}
/*动态申请设备号*/
else
{
ret =
alloc_chrdev_region(&devno,0,1,"second" );
second_major = MAJOR(devno);
}
if(ret ;myclass =
class_create(THIS_MODULE,"second_timer_class
");
/*字符设备初始化,绑定相关操作到设备*/
cdev_init(&second_devp->;cdev,&second_fops); /*设备的拥有者*/
second_devp->;cdev.owner = THIS_MODULE,
/*添加设备到内核*/
ret = cdev_add(&second_devp->;cdev,devno,1); /*错误处理*/
if(ret)
{
printk(KERN_NOTICE "ERROR %d\n
",ret);
goto fail_malloc;
}
/*依据以前创建的设备类,创建设备*/
device_create(second_devp->;myclass,NULL,devno,NU LL,"second%d",0);
return 0;
/*错误操作*/
fail_malloc:
unregister_chrdev_region(devno,1);
return ret;
}
/*退出函数*/
static void __exit second_exit(void)
{
/*释放设备*/
device_destroy(second_devp->;myclass,MKDEV(second _major,0));
/*删除字符设备*/
cdev_del(&second_devp->;cdev);
/*释放设备类*/
class_destroy(second_devp->;myclass);
/*释放分配的内存空间大小*/
kfree(second_devp);
/*释放设备号*/
unregister_chrdev_region(MKDEV(second_major,0),1); }
/*卸载和加载*/
module_init(second_init);
module_exit(second_exit);
/*LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GP-;");
应用程序:
#include;
#include;
#include;
#include;
#include;
#include;
#include;
int main()
{
int fd;
int counter = 0;
int old_counter = 0;
fd = open("/dev/second0",O_RDONLY); if(fd != -1)
{
while(1)
{
read(fd,&counter,sizeof(unsigned int));
if(counter != old_counter)
{
printf("second after open
/dev/second0 : %d\n",counter);
old_counter = counter;
}
}
}
else
{
printf("Device open failure\n");
exit(1);
}
exit(0);
}
实验效果:
[root@EmbedSky Test]# ./app-timer Current jiffies is 2137721
second after open /dev/second0 : 1 Current jiffies is 2137921
second after open /dev/second0 : 2 Current jiffies is 2138121
second after open /dev/second0 : 3 Current jiffies is 2138321
second after open /dev/second0 : 4 Current jiffies is 2138521
second after open /dev/second0 : 5 Current jiffies is 2138721
second after open /dev/second0 : 6
以上的结果表明内核定时器基本实现了效果,但从实验结果看好像为每两秒实现一次显示。

具体的原因还有
待于再次分析,因为arm中的HZ应该为100,而不是200。

相关文档
最新文档