本系列文章主要讲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.
... ...