您的位置:首页 > 运维架构 > Linux

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

2015-10-19 09:22 579 查看
转载自:http://blog.chinaunix.net/uid-20937170-id-3048501.html

我的环境:

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 < TVR_SIZE) {
int i = expires & TVR_MASK;
vec = base->tv1.vec + i;
} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
int i = (expires >> TVR_BITS) & TVN_MASK;
vec = base->tv2.vec + i;
} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
vec = base->tv3.vec + i;
} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
vec = base->tv4.vec + i;
} else if ((signed long) idx < 0) {
/*
* Can happen if you add a timer with expires == jiffies,
* or you set a timer to go off in the past
*/
vec = base->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<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/mm.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<asm/io.h>
#include<asm/uaccess.h>
#include<linux/device.h>

/*采用宏定义设置设备的主设备号*/
#define SECOND_MAJOR    0
/*静态的分别保存静态主设备号的变量*/
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 < 0)
{
return ret;
}

/*分配设备结构体的地址空间*/
second_devp = kmalloc(sizeof(struct second_dev),GFP_KERNEL);
/*检查是否分配正确*/
if(!second_devp)
{
ret = -ENOMEM;
goto fail_malloc;
}
/*清零分配的空间*/
memset(second_devp,0,sizeof(struct second_dev));
/*创建设备类,用于自动创建设备文件*/
second_devp->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,NULL,"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<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<linux/fs.h>
#include<unistd.h>
#include<fcntl.h>

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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: