了解内核定时相关基础知识
文章目录
- 简要介绍
- [timer_list 特点](#timer_list 特点)
- [API 函数](#API 函数)
- 实验
-
- [测试程序 - timer_mod.c](#测试程序 - timer_mod.c)
- 编译文件-Makefile
- 实验验证
- 注意事项
- 总结
简要介绍
硬件为内核提供了一个系统定时器来计算流逝的时间(即基于未来时间点的计时方式, 以当前时刻为计时开始的起点, 以未来的某一时刻为计时的终点) , 内核只有在系统定时器的帮助下才能计算和管理时间, 但是内核定时器的精度并不高, 所以不能作为高精度定时器使用。并且内核定时器的运行没有周期性, 到达计时终点后会自动关闭。 如果要实现周期性定时, 就要在定时处理函数中重新开启定时器。
Linux 内核中使用 timer_list 结构体表示内核定时器, 该结构体定义在"内核源码/include/li
nux/timer.h"文件中, 具体内容如下所示
java
struct timer_list {
struct hlist_node entry;
unsigned long expires;/* 定时器超时时间,单位是节拍数 */
void (*function)(struct timer_list *);/* 定时处理函数 */
u32 flags;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
};
timer_list 特点
inux 内核定时器是基于 timer_list 结构的动态定时器,具有以下特点:
-
不是周期性的,超时后会自动失效
-
基于内核的 jiffies 计时
-
在中断上下文执行,因此不能睡眠
-
精度取决于 HZ 值(通常为 1ms 或 10ms)
API 函数
函数 | 作用 |
---|---|
void add_timer(struct timer_list *timer) | 向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行 |
int del_timer(struct timer_list * timer) | 删除一个定时器 |
int mod_timer(struct timer_list *timer,unsigned long expires) | 修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器 |
在使用add_timer()函数向 Linux 内核注册定时器之前,还需要设置定时时间,定时时间由timer_list结构体中的expires参数所确定,单位为节拍数
这里简要理解,节拍数 jiffies 和 时间之间的转换函数:
jiffies_64用于64位系统,而jiffies用于 32 位系统。为了方便开发,Linux 内核还提供了几个jiffies和ms、us、ns之间的转换函数,如下 所示:
函数 | 作用 |
---|---|
int jiffies_to_msecs(const unsigned long j) | 将 jiffies 类型的参数 j 分别转换为对应的毫秒 |
int jiffies_to_usecs(const unsigned long j) | 将 jiffies 类型的参数 j 分别转换为对应的微秒 |
u64 jiffies_to_nsecs(const unsigned long j) | 将 jiffies 类型的参数 j 分别转换为对应的纳秒 |
long msecs_to_jiffies(const unsigned int m) | 将毫秒转换为 jiffies 类型 |
long usecs_to_jiffies(const unsigned int u) | 将微秒转换为 jiffies 类型 |
unsigned long nsecs_to_jiffies(u64 n) | 将纳秒转换为 jiffies 类型 |
既然要定时,那么情形就是 把定时时间转换成节拍数,系统内核根据节拍数和节拍频率。内核里面只认节拍数,它对应的就是频率
例如:进行3秒钟的定时:
java
timer_test.expires = jiffies_64 +msecs_to_jiffies(3000)
实验
测试程序 - timer_mod.c
java
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
static void function_test(struct timer_list *t);//定义function_test定时功能的函数
DEFINE_TIMER(timer_test,function_test);
static void function_test(struct timer_list *t){
printk(" this is function test \n");
mod_timer(&timer_test,jiffies_64+msecs_to_jiffies(5000)); 使用mod_timer函数将定时时间设置为五秒后
}
static int __init time_module_init(void) //驱动入口函数
{
timer_test.expires = jiffies_64 + msecs_to_jiffies(5000);//将定时时间设置为五秒后
add_timer(&timer_test);//添加一个定时器
return 0;
}
static void __exit time_module_exit(void) //驱动出口函数
{
del_timer(&timer_test);//删除一个定时器
printk("module exit \n");
}
module_init(time_module_init);
module_exit(time_module_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("wang fang chen");
源码分析
-
这里用到了定时器timer_list 的三个api 函数:
定义定时器:DEFINE_TIMER(timer_test,function_test); 参数为定时器名称和回调方法
添加定时器:add_timer(&timer_test);
修改定时器:mod_timer(&timer_test,jiffies_64+msecs_to_jiffies(5000));
删除定时器:del_timer(&timer_test);
**注意点:**定时器的名称并不是作为变量定义的,作为方法参数定义了的 DEFINE_TIMER的方法参数。
回调函数 function_test 是先定义,然后实现 。
编译文件-Makefile
java
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += timer_mod.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
实验验证
加载驱动看打印信息
注意事项
-
定时器精度:内核定时器的精度受 HZ 值影响,不适合需要高精度的场合
-
执行上下文:回调函数在中断上下文执行,不能调用可能睡眠的函数
-
多核处理:del_timer() 可能在 SMP 系统上返回后定时器仍在运行,使用 del_timer_sync() 更安全
-
竞争条件:确保在模块退出时删除所有定时器
总结
这里了解的是内核的一个定时器timer_list 的使用。 了解基本知识即可。