本文目录
Linux内核定时器是一种基于未来时间点的计时方式。基于未来时间点的计时是以当前时刻为计时开始的时间点,以未来的某一时刻为计时的终点。比如,现在是早上7点,我想在睡一会我就用手机定时五分钟,定时时间就是7点+5分钟=7点5分。这种定时方式就和手机闹钟非常类似。
一、普通定时器
普通定时器的精度不高,所以不能作为高精度定时器使用。并且内核定时器不是周期性运行的,到计时终点后会自动关闭。如果我们想要实现周期性定时,就需要定时处理函数中重新开启定时器。
1. 定时器结构体
struct timer_list {
struct hlist_node entry; //用于将定时器插入到内核的定时器链表中。
unsigned long expires; //定时器的到期时间(以 jiffies 为单位)。jiffies 是一个内核全局变量,表示自系统启动以来的滴答数。
void (*function)(struct timer_list *); //定时器到期时要执行的回调函数。
u64 slack;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
int start_pid;
void *start_site;
/* Timer-specific fields start here. */
struct timer_base *base;
};
2. 定时时间计算
timer_list结构体中,expires为计时终点的时间。单位是节拍数。节拍数怎么和时间关联上呢?
(1)宏定义HZ
Linux内核中有一个宏HZ,这个宏用来表示一秒钟对应的节拍的数量。利用Linux内核中的这个宏,我们就可以把时间转换成节拍数。比如,定时一秒钟换成节拍数就是expires=jiffies+1*HZ,其中宏定义jiffies为系统当前时间对应的节拍数。
宏HZ的值我们是可以设置的,也就是说一秒钟对应多少个节拍数我们是可以设置的。打开make menuconfig图形化配置界面进行设置,具体设置路径查看其它博客。
(2)全局变量jiffies
全局变量jiffies用来记录自系统启动以来产生的节拍的总数。启动时, 内核将该变量初始化为 0。此后,每次时钟中断处理程序都会增加该变量的值。 因为一秒内时钟中断的次数为HZ(节拍数),所以jiffies一秒内增加的值也就为HZ(节拍数),系统运行时间以秒为单位计算, 就等于 jiffies/HZ。jiffies=seconds*HZ。
jiffies定义在文件include/linux/jiffies.h 中,定义如下:
extern u64 jiffies_64; //64位系统使用该宏
extern unsigned long volatile jiffies; //32位系统使用该宏
●jiffies转换函数
//将 jiffies 转换为毫秒
unsigned int jiffies_to_msecs(const unsigned long j);
//将 jiffies 转换为微秒
unsigned int jiffies_to_usecs(const unsigned long j);
//将毫秒转换为 jiffies
unsigned long msecs_to_jiffies(const unsigned int m);
//将微秒转换为 jiffies
unsigned long usecs_to_jiffies(const unsigned int u);
举例:定时10ms(63位系统)。 jiffies_64+msecs_to_jiffies(10);
3.相关API函数
- 初始化内核定时器
static struct timer_list my_timer; //定义定时器
init_timer(&my_timer); //初始化定时器
//结构体赋值
my_timer.expires= jiffies_64+msecs_to_jiffies(2000); //定时2s
my_timer.function= time_function; //定时处理函数,当时间到时处理该函数。
my_timer.data=123; //传递给定时处理函数的参数
- 在驱动入口函数中,向内核注册定时器
void add_timer(struct timer_list *timer);
- 在驱动出口函数中,注销定时器
void del_timer(struct timer_list *timer);
- 若修改定时器的值
int mod_timer(struct timer_list *timer, unsigned long expires);
4. 代码实现
功能:先打印start,然后过5s后定时时间到处理定时函数,打印Timer expired with data: 1234,打印完成后定时器原本应该关闭,但是由于重新设置定时器时间,则每过2s打印一次Timer expired with data: 1234。
misc.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
static struct timer_list my_timer;
void my_timer_callback(unsigned long data)
{
printk(KERN_INFO "Timer expired with data: %lu\n", data);
// 重新设置定时器,定时器将在 2 秒后再次触发
mod_timer(&my_timer, jiffies + msecs_to_jiffies(2000));
}
static int __init my_module_init(void)
{
printk(KERN_INFO "start\n");
my_timer.function = my_timer_callback;
my_timer.data = 1234; // 传递给回调函数的数据
my_timer.expires = jiffies + msecs_to_jiffies(5000);
init_timer(&my_timer); // 初始化定时器
// 添加定时器
add_timer(&my_timer);
return 0;
}
static void __exit my_module_exit(void)
{
printk(KERN_INFO "Exiting module\n");
// 删除定时器
del_timer(&my_timer);
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
使用dmesg
查看内核打印信息:
5. 扩展:使用定时器实现秒字符设备
misc.c
#include <linux/module.h> //模块
#include <linux/kernel.h> //内核
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/atomic.h>
static struct timer_list my_timer; //定时器
atomic64_t counter; //定义原子
void my_timer_callback(unsigned long data) //定时处理函数
{
// 重新设置定时器,定时器将在 1 秒后再次触发
atomic64_inc(&counter);
mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
}
int misc_open(struct inode * node, struct file * fp)
{
my_timer.function = my_timer_callback;
my_timer.data = 1234; // 传递给回调函数的数据
my_timer.expires = jiffies + msecs_to_jiffies(1000);
init_timer(&my_timer); //初始化定时器
add_timer(&my_timer); // 添加定时器
atomic64_set(&counter, 0); //将原子的值设置为0。
return 0;
}
int misc_release(struct inode *node, struct file *fp)
{
return 0;
}
ssize_t misc_read(struct file *fp, char __user *ubuf , size_t size, loff_t *offset)
{
int ret;
unsigned int count=0;
count =atomic64_read(&counter);
ret=copy_to_user(ubuf, &count , sizeof(count));
if(ret !=0){
pr_err("copy_to_user error\r\n");
return -EINVAL;
}
return 0;
}
ssize_t misc_write(struct file *fp, const char __user *ubuf, size_t size, loff_t *offset)
{
return 0;
}
//描述一个文件操作集
const struct file_operations misc_fops={
.owner=THIS_MODULE, //文件操作集的拥有者是 本模块。
.open =misc_open, //应用层对本模块的驱动节点open时触发的函数
.release=misc_release, //应用层对本模块的驱动节点close时触发的函数
.read=misc_read, //应用层对本模块的驱动节点read时触发的函数
.write=misc_write, //应用层对本模块的驱动节点write时触发的函数
};
//描述一个杂项设备
struct miscdevice misc={
.minor=MISC_DYNAMIC_MINOR, //由内核自动分配次设备号,misc的主设备号为10.
.name ="qjl", //在/dev下生成的驱动节点的名称,不能有空格!
.fops =&misc_fops,
};
static int __init misc_init(void)
{
int ret;
ret=misc_register(&misc); //向内核注册一个杂项设备
if(ret <0){
pr_err("misc_register error\r\n");
return -1;
}
return 0;
}
static void __exit misc_exit(void)
{
// 删除定时器
del_timer(&my_timer);
misc_deregister(&misc); //从内核注销一个杂项设备
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
read.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd;
unsigned int counter=0;
fd = open("/dev/qjl", O_RDWR);
if (fd < 0) {
perror("open error");
return -1;
}
while(1){
read(fd, &counter, sizeof(counter) ); // 预留一个字节用于 null 终止符
printf("data: %d\n", counter);
sleep(1);
}
close(fd);
return 0;
}
实验现象:
二、高精度定时器
普通定时器的时钟频率可以设置在 100Hz 到 1000Hz之间,所以精度只能限制在亳秒级别。所以无法满足精度较高的场景当中,为此 Linux 提供了高精度定时器,可以提供纳秒级别的精度。
1. 高精度定时器结构体
struct hrtimer {
struct timerqueue_node node; // 定时器队列节点,负责管理该高精度定时器在队列中的位置。
//最重要的成员
ktime_t _softexpires; // 定时器的软到期时间,用于指定该定时器到期的具体时间点。
enum hrtimer_restart (*function)(struct hrtimer *); // 定时器到期时执行的回调函数,返回值决定定时器是否继续。
//------------
struct hrtimer_clock_base *base; // 定时器的时钟基准,用于选择是使用实时时钟还是其他时钟。
unsigned long state; // 定时器的状态,记录是否激活、挂起等状态信息。
#ifdef CONFIG_TIMER_STATS
int start_pid; // 定时器启动时的进程ID,用于统计信息和调试。
void *start_site; // 定时器启动时的调用位置,用于调试和分析性能。
char start_comm[16]; // 启动该定时器的进程名,用于统计和调试。
#endif
};
2. 相关API函数
- 初始化一个定时器
void hrtimer_init(struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode mode);
// struct hrtimer *timer: 指向要初始化的定时器结构体的指针。
// clockid_t clock_id: 指定时间点和时间的测量方式,可以使用以下几个选项:
//#define CLOCK_REALTIME 0 。基于系统实时时间的时钟,表示从 Epoch(1970年1月1日00:00:00 UTC)以来的秒数和纳秒数。 如果系统时间被用户更改,这个时钟也会改变。
// #define CLOCK_MONOTONIC 1 (常用)。 单调递增的时钟,表示从某个固定点(例如系统启动)以来的时间。这个时钟不受系统时间更改的影响,适合用于测量经过的时间。
// #define CLOCK_PROCESS_CPUTIME_ID 2 。 表示进程的 CPU 时间,用于测量当前进程消耗的 CPU 时间。
//mode: 指定定时器的模式,通常为 HRTIMER_MODE_REL(相对时间)或 HRTIMER_MODE_ABS(绝对时间)
- 设置定时时间
static inline ktime_t ktime_set(const s64 secs, const unsigned long nsecs)
/*
const s64 secs : 需要定时的秒数。
const unsigned long nsecs:需要定时的纳秒数。
*/
- 启动定时器
static inline void hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)
// timer 为要启动的定时器。
// tim 为定时时间。
//mode 为定时器模式,要和初始化时设置的模式一致。
- 重新设置定时时间
u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
/*
函数参数:structhrtimer*timer:要设置的定时器
ktime tnow:当前时间
ktime tinterval:定时时间
*/
- 关闭定时器
int hrtimer_cancel(struct hrtimer *timer)