文章目录
前言
本文的主要内容是Linux下的内核定时器介绍,包括基本概念和其相关的一些函数介绍,还有一个内核定时器的应用例子。
一、内核定时器
不同于单片机定时器,Linux内核定时器是一种基于未来时间点的计时方式,它以当前时刻为启动的时间点,以未来的某一时刻为终止点,类似于我们的闹钟。
内核定时器的精度不高,不能作为高精度定时器使用,其内核定时器不是周期性运行的,超时以后就会自动关闭,因此要想实现周期性的定时,就需要在定时处理函数中重新开启定时器。
二、相关的函数
1.timer_list结构体
timer_list结构体定义在/linux-4.1.15/include/linux/timer.h文件中。
struct timer_list {
struct list_head entry;
unsigned long expires; //定时器的超时时间,不是时长,单位是节拍数
struct tvec_base *base;
void (*function)(unsigned long); //定时处理函数
unsigned long data; //传递给function函数的参数
int slack;
};
expires为到期时间,单位是节拍数,其值等于定时的当前时钟节拍计数(存储在系统全局变量jiffies中)+ 定时时长对应的时钟节拍数量。
2.宏HZ
内核中有一个宏HZ,表示一秒所对应的节拍数量,可以通过这个宏来把时间转换成节拍数,1秒的表示如下:
expires = jiffies + 1*HZ;
在Linux根目录下打开.config文件,找到CONFIG_HZ所在的位置,如下图所示。
HZ的值可以自己设置,其定义在/linux-4.1.15/include/asm-generic/param.h文件中。
#ifndef __ASM_GENERIC_PARAM_H
#define __ASM_GENERIC_PARAM_H
#include <uapi/asm-generic/param.h>
# undef HZ
# define HZ CONFIG_HZ /* Internal kernel timer frequency */
# define USER_HZ 100 /* some user interfaces are */
# define CLOCKS_PER_SEC (USER_HZ) /* in "ticks" like times() */
#endif /* __ASM_GENERIC_PARAM_H */
也可以通过make menuconfig命令打开内核进行配置,依次进到Kernel Features——>Timer frequency下进行设置。
这里有几个频率供自己选择。
HZ表示1秒的节拍数,这里HZ如果是100的话,时间精度就是10ms。
高的节拍率会提高系统时间精度,与此同时,高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担,中断服务函数占用处理器的时间会增加。
Linux内核使用全局变量jiffies来记录系统从启动以来的系统节拍数,系统启动的时候会将jiffies初始化为0,jiffies定义在文件/include/linux/jiffies.h中。
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
jiffies_64和jiffies分别用于64位系统和32位系统,为了兼容不同的硬件,jiffies其实就是jiffies_64的低32位。
定时10ms:
jiffies + msecs_to_ jiffies(10);
定时10us:
jiffies + usecs_to_ jiffies(10);
定时10ns:
jiffies + nsecs_to_ jiffies(10);
3.宏DEFINE_TIMER
宏DEFINE_TIMER的原型如下:
DEFINE_TIMER(_name,_function,_expires,_data);
作用:静态定义结构体变量并且初始化function,expires,data成员。
参数介绍:
name:变量名
function:超时处理函数
expires:到期时间,一般在启动定时前需要重新初始化。
data:传递给超时处理函数的参数。
4.函数add_timer
add_timer函数原型如下:
void add_timer(struct timer_list *timer);
作用:向Linux内核注册定时器,注册之后,定时器才会开始运行。
参数介绍:
timer:要注册的定时器。
5.函数del_timer
del_timer函数原型如下:
void del_timer(struct timer_list *timer);
作用:删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用该函数删除定时器之前要先等待其他处理器的定时处理器函数退出。
参数介绍:
timer:要删除的定时器。
返回值:返回0表明定时器没有被激活,返回1表示定时器已经激活。
6.函数mod_timer
mod_timer函数原型如下:
int mod_timer (struct timer_list *timer,unsigned long expires);
作用:用于修改定时值,如果定时器还没有被激活,该函数可以激活定时器。
参数介绍:
timer:要修改超时时间的定时器。
expires:修改后的超时时间。
返回值:返回0表明调用mod_timer函数前定时器没有被激活,返回1表明调用mod_timer函数前定时器已经激活。
三、代码文件
1.timer.c文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
static void timer_function(unsigned long data);
DEFINE_TIMER(test_timer,timer_function,0,0); //静态定义结构体变量并且初始化function,expires,data成员
static void timer_function(unsigned long data)
{
printk("This is timer function!\n");
mod_timer(&test_timer,jiffies + 1*HZ); //1秒打印一次
//mod_timer(&test_timer,jiffies + 2*HZ); //2秒打印一次
//mod_timer为超时修改函数,jiffies相当于当前时间,HZ表示一秒对应的时钟节拍数,这里如果不使用该函数,则只打印一次
}
static int hello_init(void)
{
printk("hello world!\n");
test_timer.expires = jiffies + 1*HZ; //定义好未来时刻的时间点
add_timer(&test_timer); //向Linux内核注册定时器
return 0;
}
static void hello_exit(void)
{
printk("bye!\n");
del_timer(&test_timer); //删除定时器
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
2.Makefile文件
obj-m += timer.o
KDIR:=/linux/linux-4.1.15
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
四、运行结果
将上面的代码写好之后编译,然后将驱动发送至开发板,开发板加载驱动后的打印内容如下图所示。
由上图可以看到,打印语句每隔一秒执行一次。
修改代码,使其两秒打印一次,结果如下图所示。
总结
以上就是Linux下内核定时器的所有内容了,重点理解节拍数和时间之间的转换关系。
本文参考视频:https://www.bilibili.com/video/BV1Vy4y1B7ta?p=40。