10.5 内核定时器编程

        和Linux中断一样,内核定时器的编程只需要调用内核提供的API函数。内核在时钟中断发生以后会检查各定时器是否到期,到期后将会把其绑定的定时器处理函数放到底半部执行。
        内核定时器本质上还是依赖硬件定时器实现。

一、定时器API函数

1、timer_list结构体

在Linux内核中,如果需要使用一个定时器,则必须先定义一个timer_list结构体,结构体对应如下:

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 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
};

        在该结构体中,通常只需要关注三个参数分别是:

① unsigned long expires;        //定时器的到期时间
② void (*function)(unsigned long);        //定时器到期会执行的函数
③ unsigned long data;        //私有数据,会传给定时器处理函数

2、初始化定时器

init_timer(struct timer_list *timer);

        init_timer是一个宏,当你定义一个timer_list结构体以后,需要使用此宏进行初始化

3、添加定时器 

void add_timer(struct timer_list *timer)
{
	BUG_ON(timer_pending(timer));
	mod_timer(timer, timer->expires);
}

        初始化完一个定时器以后,需要使用add_timer函数,将定时器添加到内核的动态定时器链表中。

4、删除定时器 

int del_timer(struct timer_list *timer)
{
	struct tvec_base *base;
	unsigned long flags;
	int ret = 0;

	debug_assert_init(timer);

	timer_stats_timer_clear_start_info(timer);
	if (timer_pending(timer)) {
		base = lock_timer_base(timer, &flags);
		ret = detach_if_pending(timer, base, true);
		spin_unlock_irqrestore(&base->lock, flags);
	}

	return ret;
}

        在卸载驱动的时候,需要在出口函数中,将内核动态定时器链表上的定时器进行删除。

5、修改定时器的到期时间expire

int mod_timer(struct timer_list *timer, unsigned long expires)
{
	expires = apply_slack(timer, 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_pending(timer) && timer->expires == expires)
		return 1;

	return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
}

        此函数可以修改原来定时器的到期时间。

定时器使用模板:

 struct timer_list timer; /* 定义定时器 */
/* 定时器回调函数 */
void function(unsigned long arg){
/*
* 定时器处理代码
*/
 
 /* 如果需要定时器周期性运行的话就使用 mod_timer
 * 函数重新设置超时值并且启动定时器。
 */
 mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
 }
 
 /* 初始化函数 */
void init(void){
 init_timer(&timer); /* 初始化定时器 */
 timer.function = function; /* 设置定时处理函数 */
 timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
 timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */
 
 add_timer(&timer); /* 启动定时器 */
}
 
 /* 退出函数 */
 void exit(void){
 del_timer(&timer); /* 删除定时器 */
 /* 或者使用 */
 del_timer_sync(&timer);
}

二、秒字符设备

        该设备驱动实现的功能:每秒输出一次当前的jiffies

驱动代码:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define SECOND_MAJOR 248

static int second_major = SECOND_MAJOR;
module_param(second_major, int, S_IRUGO);

struct second_dev {
	struct cdev cdev;
	atomic_t counter;
	struct timer_list s_timer;
};

static struct second_dev *second_devp;

static void second_timer_handler(unsigned long arg)
{
	mod_timer(&second_devp->s_timer, jiffies + HZ); /* 触发下一次定时 */
	atomic_inc(&second_devp->counter); /* 增加秒计数 */

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

static int second_open(struct inode *inode, struct file *filp)
{
	init_timer(&second_devp->s_timer);
	second_devp->s_timer.function = &second_timer_handler;
	second_devp->s_timer.expires = jiffies + HZ;

	add_timer(&second_devp->s_timer);

	atomic_set(&second_devp->counter, 0);  /* 初始化秒计数为0 */                   

	return 0;
}

static int second_release(struct inode *inode, struct file *filp)
{
	del_timer(&second_devp->s_timer);

	return 0;
}

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)) /* 拷贝counter到userspace */
		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 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;
	err = cdev_add(&dev->cdev, devno, 1);
	if (err)
		printk(KERN_ERR "Failed to add second device\n");
}

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 = kzalloc(sizeof(*second_devp), GFP_KERNEL);
	if (!second_devp) {
		ret = -ENOMEM;
		goto fail_malloc;
	}

	second_setup_cdev(second_devp, 0);

	return 0;

fail_malloc:
	unregister_chrdev_region(devno, 1);
	return ret;
}
module_init(second_init);

static void __exit second_exit(void)
{
	cdev_del(&second_devp->cdev);	
	kfree(second_devp);	
	unregister_chrdev_region(MKDEV(second_major, 0), 1);
}
module_exit(second_exit);

MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL v2"); 

        当驱动被应用打开的时候会执行xxx_open函数,初始化定时器,并将定时器添加到内核。
        随后每过1秒会将当前的jiffies上传给用户

应用代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
	int fd;
	int counter = 0;
	int old_counter = 0;
	fd = open("/dev/second",O_RDONLY);//打开设备
	if(fd != -1)
	{
		 while(1)
		 {
			  read(fd,&counter,sizeof(unsigned int));//从fd指向描述符中读取sizeof(unsigned int)字节到counter
			  if(counter != old_counter) //是否等于old_counter
			  {
				   printf("seconds after open /dev/second : %d\n",counter);
				   old_counter = counter;//这样的话就同步了
			  }
		 }
	}
	else
	{
		printf("Device open failure\n");
	}
}

 在开发板上验证驱动: 

         加载驱动和创建设备节点

        运行应用,发现每秒会打印当前的jiffies,可以发现当前内核设置的频率为100HZ

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值