写点什么

Linux 驱动开发 - 内核定时器

作者:DS小龙哥
  • 2022 年 4 月 17 日
  • 本文字数:3163 字

    阅读完需:约 10 分钟

1. 内核定时器介绍

内核定时器是内核用来控制在未来某个时间点(基于 jiffies(节拍总数))调度执行某个函数的一种机制,相关函数位于 <linux/timer.h> 和 kernel/timer.c 文件中。


当内核定时器定时时间到达时,会进入用户指定的函数,相当于软中断。内核定时器注册开启后,运行一次就不会再运行(相当于自动注销),我们可以重新设置定时器的超时时间,让定时器重复运行。


每当时钟中断发生时,全局变量 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 修改定时器超时时间

2.2 初始化定时器

2.3 关闭定时器

2.4 关闭定时器

2.5 转换时间(微妙单位)

2.6 转换时间(毫秒为单位)


将 jiffies 单位转为 struct timespec 结构体表示:


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 = (_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 = 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 = 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=%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=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);     //转us    udelay(600);          //延时一段时间    my_time2=ktime_get();        //获取当前时间  j=ktime_to_us(my_time2);      //转us    printk("time_cnt=%ld\n",j-i);   //得出之间差值,正确值为: 600  return 0;}
复制代码


发布于: 刚刚阅读数: 2
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022.01.06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
Linux驱动开发-内核定时器_4月月更_DS小龙哥_InfoQ写作平台