07-Linux高分辨率定时器

本系列文章主要讲Linux中的中断和时间管理,文章机构如下:
01 - 驱动中的中断处理
02 - 中断下半部 tasklet
03 - 中断的下半部 workqueue
04 - Linux中的延时操作函数
05 - Linux硬件定时 jiffies
06 - Linux 低分辨率定时器
07 - Linux高分辨率定时器

 上一节讲了Linux 中的低分辨率定时器,因为低分辨率定时器以 jiffies 来定时,所以定时精度受到系统频率(Hz)的影响,例如系统频率为100Hz时,最小的定时精度是10ms,这样的精度不能满足需要高精度定时器的场合,所以本节来介绍内核中提供的另一种定时器——高精度分辨率定时器,高分辨率定时器的定时精度可以精确到 ns 。

1. 高分辨率定时器的定义

 高分辨率定时器的定义如下

struct hrtimer {
	struct timerqueue_node node;
	ktime_t _softexpires;
	enum hrtimer_restart (*function)(struct hrtimer *);	// 回调函数
	struct hrtimer_clock_base *base;
	u8 state;
	u8 is_rel;
#ifdef CONFIG_TIMER_STATS
	int	start_pid;
	void *start_site;
	char start_comm[16];
#endif
};

 其中ktime_t的定义如下

union ktime {
	s64	tv64;	// 表示时间
};
typedef union ktime ktime_t;

2. 高分辨率定时器的相关函数

2.1 设置超时时间

@原   型: ktime_t ktime_set(const s64 secs, const unsigned long nsecs)
@功   能: 设置超时时间
@param1: 秒
@param2: 纳秒
@retval: return (ktime_t) { .tv64 = secs * NSEC_PER_SEC + (s64)nsecs };
		 #define NSEC_PER_SEC 1000000000L

2.1 高分辨率定时器的初始化

@原   型: void hrtimer_init(struct hrtimer * timer, clockid_t clock_id, enum hrtimer_mode mode)
@功   能: 初始化 struct hrtimer 结构对象
@param1: 要初始化的结构对象
@param2: 时钟的类型,常用的有
		 CLOCK_REALTIME		// 系统的当前时间,从1970.1.1算起
		 CLOCK_MONOTONIC	// 系统的启动时间
		 CLOCK_PROCESS_CPUTIME_ID	// 进程运行时间
		 CLOCK_THREAD_CPUTIME_ID	// 线程运行时间
@param3: 时间的模式,用枚举来表示
  		 enum hrtimer_mode {
  		 	 HRTIMER_MODE_ABS = 0x0,			// 绝对时间 
  		 	 HRTIMER_MODE_REL = 0x1,			// 相对时间
  			 HRTIMER_MODE_ABS_PINNED = 0x02,
  			 HRTIMER_MODE_REL_PINNED = 0x03,
  		 };
@retval: 无返回值

2.3 回调函数

@原   型: enum hrtimer_restart (*function)(struct hrtimer *);	// 回调函数
@功   能: 高精度定时器的回调函数
@param1: 初始化的定时器
@retval: 枚举类型,定义如下
		 enum hrtimer_restart {
			 HRTIMER_NORESTART,	// Timer is not restarted
			 HRTIMER_RESTART,	// Timer must be restarted
		 };

2.4 定时器的开启

@原   型: void hrtimer_start(struct hrtimer * timer, ktime_t tim, const enum hrtimer_mode mode)
@功   能: 启动定时器 
@param1: 要启动的定时器
@param2: 设定的到期时间
@param3: 与 hrtimer_init 中的参数3意义相同
		 enum hrtimer_mode {
			 HRTIMER_MODE_ABS = 0x0,			// 绝对时间 
			 HRTIMER_MODE_REL = 0x1,			// 相对时间,则需要加上当前时间,因为内部是使用绝对时间 
			 HRTIMER_MODE_PINNED = 0x02,		// 定时器和CPU绑定
			 HRTIMER_MODE_ABS_PINNED = 0x02,
			 HRTIMER_MODE_REL_PINNED = 0x03,
		 };

@retval: 无返回值

2.5 定时器定时时间的修改

@原   型: u64 hrtimer_forward_now(struct hrtimer * timer, ktime_t interval)
@功   能: 修改到期时间为从现在开始之后的 interval 值
@param1: 要修改的定时器
@param2: 修改后的值
@retval: 超限次数

2.6 关闭定时器

@原   型: int hrtimer_cancel(struct hrtimer * timer)
@功   能: 取消关闭一个定时器
@param1: 要取消关闭的定时器
@retval: 0表示之前定时器是未开启状态,1表示之前定时器是开启状态

2.7 高分辨率定时器的使用流程

使用高精度定时器的一般流程如下:
 1. 首先定义并初始化一个 struct hrtimer 高精度定时器对象
 2. 定义高精度定时器对象的回调函数
  enum hrtimer_restart (*function)(struct hrtimer *);
  注意:在回调函数返回前要手动设置下一次超时时间。另外,回调函数执行时间不宜过长,因为是在中断上下文中,如果有什么任务的话,最好使用工作队列等机制。
 3. 启动定时器,并设定到期时间
 4. 修改到期时间(非必需)
 5. 取消关闭定时器

3. 示例代码

3.1 demo.c

 在 demo.c 的21行定义了一个高精度定时器,在110行对该高精度定时器进行初始化,并在111行对该定时器的回调函数进行了定义
 在114行开启了初始化好的高精度定时器
 在25~36行定义了高精度定时器的回调函数,每次调用该函数会打印出 count 的值,在33行对定时器的定时时间进行改变,由 demo_init 函数中的 1s+100ns 改为 3s+100ns,所以在加载模块后,观察到的现象应该是1s+100ns 后打印出 count 的值,然后过 3s+100ns 后再次打印。

#include <linux/module.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/atomic.h>
#include <linux/slab.h>				// kzalloc和kfree的头文件
#include <linux/interrupt.h>		// 中断相关的头文件
#include <linux/hrtimer.h>			// 定时器相关的头文件

#define DELAY_MS 1000

typedef struct 
{
	dev_t dev_no;
	char devname[20];
	char classname[20];
	struct cdev demo_cdev;
	struct class *cls;
	struct device *dev;
	struct mutex my_mutex;			// 定义一个互斥体,在init函数中进行初始化
	struct hrtimer timer;			// 定义一个高精度定时器
}demo_struct;
demo_struct my_demo_dev;			// 定义一个设备结构体

static enum hrtimer_restart timer_function(struct hrtimer *hrtimer)
{	
	static int count = 1;
	
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	printk("count = %d.\n", count);
	++count;

	hrtimer_forward_now(&my_demo_dev.timer, ktime_set(3, 100));

	return HRTIMER_RESTART;
}


static int demo_open(struct inode *inode, struct file *filp)
{
	/* 首先判断设备是否可用 */
	if( mutex_lock_interruptible(&my_demo_dev.my_mutex) )	// 访问共享资源之前获取互斥体,成功获取返回0
	{
		return -ERESTARTSYS;	// 不能获取返回错误码
	}
	
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	
	return 0;

}

static int demo_release(struct inode *inode, struct file *filp)
{
	/* 释放互斥量 */
	mutex_unlock(&my_demo_dev.my_mutex);

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	
	return 0;
}

struct file_operations demo_ops = {
	.open = demo_open,
	.release=  demo_release,
};

static int __init demo_init(void)
{
	int ret;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	strcpy(my_demo_dev.devname, "demo_chrdev");		// 给设备名字赋值
	strcpy(my_demo_dev.classname, "demo_class");	// 给设备类的名字赋值

	ret = alloc_chrdev_region(&my_demo_dev.dev_no, 0, 0, my_demo_dev.devname);
	if (ret)
	{
		printk("alloc_chrdev_region failed.\n");
		goto region_err;
	}

	cdev_init(&my_demo_dev.demo_cdev, &demo_ops);

	ret = cdev_add(&my_demo_dev.demo_cdev, my_demo_dev.dev_no, 1);
	if (ret < 0)
	{
		printk("cdev_add failed.\n");
		goto add_err;
	}

	my_demo_dev.cls = class_create(THIS_MODULE, my_demo_dev.classname);		/* 在目录/sys/class/.. */
	if ( IS_ERR(my_demo_dev.cls) )
	{
		ret = PTR_ERR(my_demo_dev.cls);
		printk("class_create failed.\n");
		goto cls_err;
	}

	my_demo_dev.dev = device_create(my_demo_dev.cls, NULL, my_demo_dev.dev_no, NULL, "chrdev%d", 0);	/* 在目录/dev/.. */
	if ( IS_ERR(my_demo_dev.dev) )
	{
		ret = PTR_ERR(my_demo_dev.dev);
		printk("device_create failed.\n");
		goto dev_err;
	}

	// 初始化一个高精度定时器
	hrtimer_init(&my_demo_dev.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	my_demo_dev.timer.function = timer_function;

	// 开启高精度定时器
	hrtimer_start(&my_demo_dev.timer, ktime_set(1, 100), HRTIMER_MODE_REL);
	
	mutex_init(&my_demo_dev.my_mutex);			// 初始化互斥体

	return 0;

dev_err:
	class_destroy(my_demo_dev.cls);	
cls_err:
	cdev_del(&my_demo_dev.demo_cdev);
add_err:
	unregister_chrdev_region(my_demo_dev.dev_no, 1);
region_err:
	return ret;
}


static void __exit demo_exit(void)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	hrtimer_cancel(&my_demo_dev.timer);			// 取消关闭一个高精度定时器
	
	free_irq(123, &my_demo_dev);				// 注销中断
	device_destroy(my_demo_dev.cls, my_demo_dev.dev_no);
	class_destroy(my_demo_dev.cls);
	cdev_del(&my_demo_dev.demo_cdev);
	unregister_chrdev_region(my_demo_dev.dev_no, 1);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

3.2 test.c

 与上一个示例一样主要是验证低分辨率定时器的使用,所以在加载模块后会自动开启定时器,而不需要执行应用程序。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fd;
	int i, count = 1;
	
	fd = open("/dev/chrdev0", O_RDWR, 0666);
	if (fd < 0)
	{
		perror("open");
		return -1;
	}

	sleep(5);

	close(fd);

	return 0;
}

3.3 Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)

all:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	arm-linux-gnueabihf-gcc test.c -o app
install:
	sudo cp *.ko  app /tftpboot
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += demo.o

3.4 测试结果

 从测试结果可以看出,加载模块后在 1s+100ns 后打印了 count 的值,然后 timer_function 对定时时间进行了修改,所以下次打印是在 3s+100ns 后,与程序的预想结果一致。

root@am335x-evm:~# insmod demo.ko 		// 加载模块
[   59.718749] demo_init -- 72.
root@am335x-evm:~# 
[   60.741047] timer_function -- 29.	// 1s+100ns 后打印
[   60.744434] count = 1.
[   63.740949] timer_function -- 29.	// 3s+100ns 后打印
[   63.744334] count = 2.
[   66.741041] timer_function -- 29.	// 3s+100ns 后打印
[   66.744429] count = 3.
[   69.741043] timer_function -- 29.	// 3s+100ns 后打印
[   69.744432] count = 4.
... ...
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值