博观而约取,厚积而薄发。这篇文章主要讲述Linux驱动开发-内核定时器相关的知识,希望能为你提供帮助。
1. 内核定时器介绍内核定时器是内核用来控制在未来某个时间点(基于jiffies(节拍总数))调度执行某个函数的一种机制,相关函数位于 <
linux/timer.h>
和 kernel/timer.c 文件中。
当内核定时器定时时间到达时,会进入用户指定的函数,相当于软中断。内核定时器注册开启后,运行一次就不会再运行(相当于自动注销),我们可以重新设置定时器的超时时间,让定时器重复运行。
【Linux驱动开发-内核定时器】每当时钟中断发生时,全局变量jiffies(一个32位的unsigned long 变量)就加1,因此jiffies记录了linux系统启动后时钟中断发生的次数,驱动程序常利用jiffies来计算不同事件间的时间间隔。内核每秒钟将jiffies变量增加HZ次。因此,对于HZ值为100的系统,jiffy+1等于隔了10ms,而对于HZ为1000的系统,jiffy+1仅为1ms。
内核定时器结构体:
下面列出了需要关心的成员
struct timer_list
unsigned long expires;
//设置超时时间,用jiffies作为基准值
void (*function)(unsigned long);
//类似中断服务函数,设置定时器到时后处理的函数
unsigned long data;
//中断服务函数的参数expires设置:以当前时间为基准加上延时时间,时间基准用jiffies变量表示,延时时间可以使用以下两个宏转换成jiffies单位。
2. 内核定时器相关API函数 2.1 修改定时器超时时间
函数原型 | *int mod_timer(struct timer_list timer, unsignedlong expires)** |
---|---|
函数功能 | 修改定时器超时时间 |
函数参数 | timer:对应的定时器结构体expires:超时时间 |
函数返回值 | 成功返回 :修改成功的时间值 |
函数定义文件 | \\linux-3.5\\kernel\\timer.c |
函数原型 | #define init_timer(timer)\\ |
---|---|
函数功能 | 初始化定时器结构 |
函数参数 | timer:对应的定时器结构体 |
函数定义文件 | \\linux-3.5\\include\\linux\\timer.h |
函数原型 | int del_timer(struct timer_list *timer) |
---|---|
函数功能 | 关闭定时器,停用一个定时器。 |
函数参数 | timer:对应的定时器结构体 |
函数返回值 | 返回0:成功 |
函数定义文件 | \\linux-3.5\\include\\linux\\timer.h |
函数原型 | int del_timer_sync(struct timer_list *timer) |
---|---|
函数功能 | 关闭定时器,停用一个定时器,多处理器使用。如果编内核时不支持 SMP(多处理器), del_timer_sync()和 del_timer()等价 |
函数参数 | timer:对应的定时器结构体 |
函数返回值 | 返回0:成功 |
函数定义文件 | \\linux-3.5\\include\\linux\\timer.h |
函数原型 | unsigned long usecs_to_jiffies(const unsigned intm) |
---|---|
函数功能 | 转换时间(微妙单位),用于填充定时器结构体,设置超时时间 |
函数参数 | m:要转换的时间值(微妙为单位) |
函数返回值 | 成功返回转换成功的时间。用于填充定时器结构体,设置超时时间 |
函数定义文件 | \\linux-3.5\\kernel\\timer.c |
函数原型 | unsigned long msecs_to_jiffies(const unsigned intm) |
---|---|
函数功能 | 转换时间(毫秒为单位),用于填充定时器结构体,设置超时时间 |
函数参数 | m:要转换的时间值(毫秒为单位) |
函数返回值 | 成功返回转换成功的时间。用于填充定时器结构体,设置超时时间 |
函数定义文件 | \\linux-3.5\\kernel\\timer.c |
Void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value);示例:
jiffies_to_timespec(jiffies,&
value);
printk("value.ts_sec=%d\\n",value.tv_sec);
printk("value.tv_nsec=%d\\n",value.tv_nsec);
2.7 初始化定时器的结构体成员
TIMER_INITIALIZER( _function, _expires, _data) 宏用于赋值定时器结构体的function、 expires、 data 和 base 成员, 这个宏的定义如下所示:(被DEFINE_TIMER宏调用)
#define TIMER_INITIALIZER(_function, _expires, _data)\\
.entry =.prev = TIMER_ENTRY_STATIC ,\\
.function = (_function),\\
.expires = (_expires),\\
.data = https://www.songbingjia.com/android/(_data),//
.base = &
boot_tvec_bases,//
.slack = -1,//
__TIMER_LOCKDEP_MAP_INITIALIZER(//
__FILE__":" __stringify(__LINE__)) \\
2.8 初始化定时器并且赋值
DEFINE_TIMER( _na me ,_functi o n, _e x pires, _data) 宏是定义并初始化定时器成员的“快捷方式”, 这个宏定义如下所示:
/*初始化定时器,并进行赋值*/
#define DEFINE_TIMER(_name, _function, _expires, _data)\\
struct timer_list _name =\\
TIMER_INITIALIZER(_function, _expires, _data)
2.9 定时器初始化赋值
setup_timer()也可用于初始化定时器并赋值其成员, 其源代码如下:
//初始化定时器并进行赋值
#define setup_timer(timer, fn, data)\\
do\\
static struct lock_class_key __key;
\\
setup_timer_key((timer), #timer, &
__key, (fn), (data));
\\
while (0)static inline void setup_timer_key(struct timer_list * timer,
const char *name,
struct lock_class_key *key,
void (*function)(unsigned long),
unsigned long data)timer->
function = function;
timer->
data = https://www.songbingjia.com/android/data;
init_timer_key(timer, name, key);
3. 使用定时器的步骤(1) 定义定时器结构体timer_list。
/*定义一个内核定时器配置结构体*/
static struct timer_list mytimer ;
(2) 设置超时时间,定义定时器处理函数和传参。
mytimer.expires=jiffies+ msecs_to_jiffies(1000);
/*设置定时器的超时时间,1000毫秒*/
//或者
//mytimer.expires=jiffies+HZ;
/*设置定时器的超时时间,1000毫秒*/mytimer.function = time_fun;
/*定时器超时的回调函数,类似中断服务函数*/
mytimer.data = https://www.songbingjia.com/android/12;
/*传给定时器服务函数的参数*/
(3) 开启定时器。
init_timer(&
mytimer);
/*初始化定时器*/
add_timer(&
mytimer);
/*启动定时器*/
完整示例代码:
#include <
linux/kernel.h>
#include <
linux/module.h>
#include <
linux/timer.h>
static struct timer_list timer;
static void timer_function(unsigned long data)printk("data=https://www.songbingjia.com/android/%ld//n",data);
mod_timer(&
timer,msecs_to_jiffies(3000)+jiffies);
static int __init tiny4412_linux_timer_init(void)timer.expires=HZ*3+jiffies;
/*单位是节拍*/
timer.function=timer_function;
timer.data=https://www.songbingjia.com/android/666;
/*1. 初始化定时器*/
init_timer(&
timer);
/*2. 添加定时器到内核*/
add_timer(&
timer);
printk("驱动测试: 驱动安装成功\\n");
return 0;
static void __exit tiny4412_linux_timer_cleanup(void)/*3. 删除定时器*/
del_timer_sync(&
timer);
printk("驱动测试: 驱动卸载成功\\n");
module_init(tiny4412_linux_timer_init);
/*驱动入口--安装驱动的时候执行*/
module_exit(tiny4412_linux_timer_cleanup);
/*驱动出口--卸载驱动的时候执行*/MODULE_LICENSE("GPL");
/*设置模块的许可证--GPL*/
4. 内核提供的延时函数
Linux 内核中提供了进行纳秒、微秒和毫秒延迟。
void ndelay(unsigned long nsecs) ;
void udelay(unsigned long usecs) ;
void mdelay(unsigned long msecs) ;
上述延迟的实现原理本质上是忙等待,根据 CPU 频率进行一定次数的循环。在内核中,最好不要直接使用mdelay()函数, 这将无谓地耗费CPU资源。void msleep(unsigned int millisecs) ;
unsigned long msleep_interruptible(unsigned int millisecs) ;
void ssleep(unsigned int seconds) ;
上述函数将使得调用它的进程睡眠参数指定的时间, msleep()、 ssleep()不能被打断,而 msleep_interruptible()则可以被打断。
5. 精度较高的时间获取方式高精度定时器通常用ktime作为计时单位。
获取内核高精度时间单位:
ktime_t ktime_get(void)
下面是一些时间辅助函数用于计算和转换:
ktime_t ktime_set(const long secs, const unsigned long nsecs);
ktime_t ktime_sub(const ktime_t lhs, const ktime_t rhs);
ktime_t ktime_add(const ktime_t add1, const ktime_t add2);
ktime_t ktime_add_ns(const ktime_t kt, u64 nsec);
ktime_t ktime_sub_ns(const ktime_t kt, u64 nsec);
ktime_t timespec_to_ktime(const struct timespec ts);
ktime_t timeval_to_ktime(const struct timeval tv);
struct timespec ktime_to_timespec(const ktime_t kt);
//转换的时间通过timespec结构体保存
struct timeval ktime_to_timeval(const ktime_t kt);
//转换的时间通过timeval结构体保存
s64 ktime_to_ns(const ktime_t kt);
//转换为ns单位
int ktime_equal(const ktime_t cmp1, const ktime_t cmp2);
s64 ktime_to_us(const ktime_t kt);
//转换为us单位
s64 ktime_to_ms(const ktime_t kt);
//转换为ms单位
ktime_t ns_to_ktime(u64 ns);
示例: 计算经过的一段时间
static int hello_init(void)ktime_t my_time,my_time2;
unsigned int i,j;
unsigned int time_cnt=0;
my_time=ktime_get();
//获取当前时间
i=ktime_to_us(my_time);
//转usudelay(600);
//延时一段时间my_time2=ktime_get();
//获取当前时间
j=ktime_to_us(my_time2);
//转usprintk("time_cnt=%ld\\n",j-i);
//得出之间差值,正确值为: 600
return 0;
推荐阅读
- 微信小游戏开发实战5:重复执行和逻辑循环的区别
- 基于 K8S 的 DolphinDB 部署教程
- MySQL MHA高可用集群部署及故障切换
- Vue基础知识总结:快速构建Vue项目
- vue3和vue2的比较
- 第十六周学习作业
- k8s部署-36-k8s的健康检查带你了解下
- k8s部署-37-带你了解并自定义pod调度策略(上)
- linux网卡bond以及交换机对接