《Linux设备驱动开发详解》-- 内核定时器

时间:2022-04-26 10:54:01
10.5.1  内核定时器编程
        软件意义上的定时器最终依赖硬件定时器来实现,内核在时钟中断发生后检测各定时器是否到期,到期后的定时器处理函数将作为软中断在底半部执行。实质上,时钟中断处理程序执行 update_process_timers()函数, 该函数调用run_local_timers()函数,这个函数处理 TIMER_SOFTIRQ 软中断,运行当前处理器上到期的所有定时器。
在 Linux 设备驱动编程中,可以利用 Linux 内核中提供的一组函数和数据结构来完成定时触发工作或者完成某周期性的事务。 这组函数和数据结构使得驱动工程师多数情况下不用关心具体的软件定时器究竟对应着怎样的内核和硬件行为。
Linux 内核所提供的用于操作定时器的数据结构和函数如下。
1.timer_list 
在 Linux 内核中,timer_list 结构体的一个实例对应一个定时器,如代码清单 10.9所示。
码清单 10.9    timer_list 结构体
struct timer_list {
struct list_head entry; //定时器列表
unsigned long expires; //定时器到期时间
void (*function)(unsigned long); //定时器处理函数
unsigned long data; //作为参数被传入定时器处理函数
struct timer_base_s *base;
};
        当定时器期满后, 其中第 5 行的 function()成员将被执行, 而第 4 行的 data 成员则是传入其中的参数,第 3 行的 expires 则是定时器到期的时间(jiffies)。

如下代码定义一个名为 my_timer 的定时器:

struct timer_list my_timer; 

2.初始化定时器
void init_timer(struct timer_list * timer); 
上述 init_timer()函数初始化 timer_list 的 entry 的 next 为 NULL,并给 base 指针赋值。
TIMER_INITIALIZER(_function,  _expires,  _data)宏用于赋值定时器结构体的function、expires、data 和 base 成员,这个宏的定义如下所示:
#define TIMER_INITIALIZER(_function, _expires, _data){    \ 
        .function =(_function),              \ 
        .expires =(_expires),              \ 
        .data =(_data),                  \ 
        .base =&__init_timer_base,            \ 

DEFINE_TIMER(_ name , _function ,  _expires , _data)宏是定义并初始化定时器成员的“快捷方式” ,这个宏定义如下所示:
#define DEFINE_TIMER(_name, _function, _expires, _data)    \ 
        struct timer_list _name=                \ 
        TIMER_INITIALIZER(_function, _expires, _data) 

此外,setup_timer()也可用于初始化定时器并赋值其成员,其源代码如下:
static inline void setup_timer(struct timer_list * timer, void (*function)(unsignedlong), unsigned long data) 
{
timer->function = function;
timer->data = data;
init_timer(timer);
}
3.增加定时器
void add_timer(struct timer_list * timer); 
上述函数用于注册内核定时器,将定时器加入到内核动态定时器链表中。
4.删除定时器
int del_timer(struct timer_list * timer); 
上述函数用于删除定时器。
del_timer_sync()是 del_ti mer()的同步版,主要在多处理器系统中使用,如果编译内核时不支持 SMP,del_timer_sync()和 del_timer()等价。
5.修改定时器的 expire 
int mod_timer(struct timer_list *timer, unsigned long expires); 

上述函数用于修改定时器的到期时间,在新的被传入的 expires 到来后才会执行定时器函数。

代码清单 10.10 给出了一个完整的内核定时器使用模板,大多数情况下,设备驱动都如这个模板那样使用定时器。
代码清单 10.10  内核定时器使用模板

/*xxx 设备结体*/ 
struct xxx_dev
{
struct cdev cdev;
...
timer_list xxx_timer;/*设备要使用的定时器*/
};

/*xxx 驱动中的某函数*/
xxx_func1(...)
{
struct xxx_dev *dev = filp->private_data;
...
/*初始化定时器*/
init_timer(&dev->xxx_timer);
dev->xxx_timer.function = &xxx_do_timer;
dev->xxx_timer.data = (unsigned long)dev;
/*设备结构体指针作为定时器处理函数参数*/
dev->xxx_timer.expires = jiffies + delay;
/*添加(注册)定时器*/
add_timer(&dev->xxx_timer);
...
}

/*xxx 驱动中的某函数*/
xxx_func2(…)
{
...
/*删除定时器*/
del_timer (&dev->xxx_timer);
...
}

/*定时器处理函数*/
static void xxx_do_timer(unsigned long arg)
{
struct xxx_device *dev = (struct xxx_device *)(arg);
...
/*调度定时器再执行*/
dev->xxx_timer.expires = jiffies + delay;
add_timer(&dev->xxx_timer);
...
}
        从代码清单第 19、 40 行可以看出, 定时器的到期时间往往是在目前 jiffies 的基础上添加一个时延,若为 Hz,则表示延迟 1s。在定时器处理函数中,在做完相应的工作后,往往会延后 expires 并将定时器再次添加到内核定时器链表,以便定时器能再次被触发。

10.5.2  实例:秒字符设备
        下面我们编写一个字符设备“second” (即“秒” )的驱动,它在被打开的时候初始化一个定时器并将其添加到内核定时器链表,每秒输出一次当前的 jiffies(为此,定时器处理函数中每次都要修改新的 expires),整个程序如代码清单 10.11。
代码清单 10.1 1  使用内核定时器的 second 字符设备驱动

#include ...

#define SECOND_MAJOR 252 /*预设的 second 的主设备号*/

static int second_major = SECOND_MAJOR;

/*second 设备结体*/
struct second_dev
{
struct cdev cdev; /*cdev 结体*/
atomic_t counter;/* 一共经历了多少秒?*/
struct timer_lists_timer; /*设备要使用的定时器*/
};

struct second_dev *second_devp; /*设备结构体指针*/

/*定时器处理函数*/
static void second_timer_handle(unsigned long arg)
{
mod_timer(&second_devp->s_timer,jiffies + HZ);
atomic_inc(&second_devp->counter);

printk(KERN_NOTICE "current jiffies is %ld\n", jiffies);
}

/*文件打开函数*/
int second_open(struct inode *inode, struct file *filp)
{
/*初始化定时器*/
init_timer(&second_devp->s_timer);
second_devp->s_timer.function = &second_timer_handle;
second_devp->s_timer.expires = jiffies +HZ;

add_timer(&second_devp->s_timer); /*添加(注册)定时器*/

atomic_set(&second_devp->counter,0); //计数清零

return 0;
}
/*文件释放函数*/
int second_release(struct inode *inode,struct file *filp)
{
del_timer(&second_devp->s_timer);

return 0;
}

/*globalfifo 读函数*/
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);
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,
};

/*初始化并注册 cdev*/
static void second_setup_cdev(struct second_dev *dev, int index)
{
int err, devno = MKDEV(second_major, index);

cdev_init(&dev->cdev, &second_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &second_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}

/*设备驱动模块加载函数*/
int 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_setup_cdev(second_devp, 0);

return 0;

fail_malloc: unregister_chrdev_region(devno, 1);
}

/*模块卸载函数*/
void second_exit(void)
{
cdev_del(&second_devp->cdev); /*注销 cdev*/
kfree(second_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(second_major, 0), 1); /*释放设备号*/
}

MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");

module_param(second_major, int,S_IRUGO);

module_init(second_init);
module_exit(second_exit);

        在 second 的 open()函数中,将启动定时器,此后每 1s 会再次运行定时器处理函数,在 second 的 release()函数中,定时器被删除。
        second_dev 结构体中的原子变量 counter 用于秒计数,每次在定时器处理函数中将被 atomic_inc()调用原子的增 1,second 的 read()函数会将这个值返回给用户空间。