Linux驱动开发定时器

Posted XXX_UUU_XXX

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux驱动开发定时器相关的知识,希望对你有一定的参考价值。

硬件定时器提供时钟源,时钟源的频率设置完成后会产生周期性的定时中断,系统使用定时中断计时。

中断周期性产生的频率为系统频率,也称节拍率,单位HZ,HZ表示一秒的节拍数,即频率,频率大小包括100、200、250、300、500和1000。

高节拍率和低节拍率的优缺点

  • 高节拍率会提高系统时间精度,如果采用 100HZ 的节拍率,时间精度就是 10ms,采用 1000HZ 的话时间精度就是 1ms,精度提高了 10 倍。高精度时钟的好处有很多,对于那些对时间要求严格的函数,能够以更高的精度运行,时间测量更准确。
  • 高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担,1000Hz 和 100HZ的系统节拍率相比,系统要花费 10 倍的“精力”去处理中断。中断服务函数占用处理器的时间增加,但是现在的处理器性能都很强大,所以采用 1000Hz 的系统节拍率并不会增加太大的负载压力。

根据实际情况选择合适的系统节拍率。

jiffies

Linux内核使用全局变量jiffies记录系统从启动以来的系统节拍数系统启动时,Jiffies会初始化为0。 jiffies_64用于64位系统,jiffies用于32位系统,jiffies就是jiffies_64的低32位,不管在32位还是64位系统都可以使用jiffies

extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

jiffies表示系统运行的节拍数,HZ表示每秒的节拍数,jiffies/HZ表示系统运行时间,单位秒。

不管是32位还是64位的 jiffies,都有溢出的风险,溢出以后会重新从0开始计数,这个现象成为绕回。假如 HZ 为最大值1000,32位的jiffies只需要49.7天绕回,对于64位的jiffies来说大概需要5.8亿年才能绕回,因此jiffies_64的绕回忽略不计主要处理32 位jiffies 的绕回

处理绕回的API函数

  • unkown为jiffies,known为对比的值。
  • unkown大于known,time_after返回真。
  • unkown小于known,time_before返回真。
  • time_after_eq和time_before_eq和上面两个函数类似,只是多了判断等于的条件。
time_after(unkown, known);
time_before(unkown, known);
time_after_eq(unkown, known);
time_before_eq(unkown, known);

jiffies和ms、us、ns之间的转换函数

// 将jiffies类型的参数分别转换为对应的毫秒、微秒、纳秒
int jiffies_to_msecs(const unsigned long j);
int jiffies_to_usecs(const unsigned long j);
u64 jiffies_to_nsecs(const unsigned long j);

// 将毫秒、微秒、纳秒转换为jiffies类型
long msecs_to_jiffies(const unsigned int m);
long usecs_to_jiffies(const unsigned int u);
unsigned long nsecs_to_jiffies(u64 n);

内核定时器

Linux内核定时器只需要提供超时时间定时处理函数,当超时时间到了就会执行定时处理函数。

内核定时器不是周期运行的,超时后会自动关闭,要实现周期性定时,需要在定时处理函数中重新开启定时器

LInux内核定义timer_list结构体表示内核定时器。

  • expires表示超时时间,单位为节拍数,如果定义一个周期为2秒的定时器,则超时时间为expiers = jiffies + (2*HZ);。
  • function表示定时处理函数。
  • data表示要传递给function的参数。
struct timer_list 
    struct list_head entry;
    unsigned long expires;
    struct tvec_base *base;
    void (*function)(unsigned long);
    unsigned long data;
    int slack;
;

初始化定时器API函数

init_timer

初始化timer_list类型变量。

  • timer:初始化的定时器
  • 返回值:无
void init_timer(struct time_list Itimer);

add_timer

向Linux内核注册定时器,注册后定时器开始运行。

  • timer:初始化的定时器
  • 返回值:无
void add_timer(struct timer_list *timer);

del_timer

删除一个定时器,在多处理器系统中,定时器可能会在其他处理器上运行,删除定时器前要先等待其他处理器的定时处理函数退出。

  • timer:要删除的定时器
  • 返回值:0,定时器还没被激活;1,定时器已经激活
int del_timer(struct timer_list *timer);

del_timer_sync

det_timer函数的 同步版,等待其他处理器使用完定时器再删除,不能用于中断上下文中。

  • timer:要删除的定时器
  • 返回值:0,定时器还没被激活;1,定时器已经激活
int del_timer_sync(struct timer_list *timer);

mod_timer

修改定时器值,如果定时器还没有激活,mod_timer会激活定时器。

  • tiemr:要修改的定时器
  • expires:修改后的超市时间
  • 返回值:0,调用mode_timer前定时器未被激活,调用mod_timer前定时器已经被激活
int mod_timer(struct timer_list *timer, unsigned long expires);

内核定时器使用流程

// 设备结构体
struct xxx_dev
    int timeperiod;          // 定时周期
    struct timer_list timer; // 定义定时器
;

struct xxx_dev xxxdev;

// 定时器回调函数
void xxx_function(unsigned long arg)

    struct xxx_dev *dev = (struct xxx_dev *)arg;
    /*
     * 定时器处理代码
     */ 
    
    // 定时器需要周期性运行,使用mod_timer函数设置超时时间并启动定时器
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));


// 驱动入口函数
static int __init xxx_init(void)

    init_timer(&xxxdev.timer);  // 初始化定时器

    xxxdev.timer.function = xxx_function; // 定时器处理函数
    xxxdev.timer.expires = jiffies + msecs_to_jiffies(2000); // 超时时间2秒
    xxxdev.timer.data = (unsigned long)&xxxdev; // 把设备结构体作为参数传递给function

    add_timer(&timer); // 注册启动定时器


// 驱动出口函数
static void __exit xxx_exit(void)

    del_timer(&xxxdev.timer); // 删除定时器,或使用del_timer_sync(&timer);

短延时函数

Linux内核提供毫秒、微妙和纳秒延时函数

// 纳秒延时
void ndelay(unsigned long nsecs);
// 微妙延时
void udelay(unsigned long usecs);
// 毫秒延时
void mdelay(unsigned long msecs);

以上是关于Linux驱动开发定时器的主要内容,如果未能解决你的问题,请参考以下文章

Linux驱动开发-内核定时器

BSP开发学习4Linux 内核时间管理

《Linux设计与实现》学习笔记——定时器和时间管理

嵌入式实时操作系统10——系统时钟节拍

Linux内核时钟系统和定时器实现

Hasen的linux设备驱动开发学习之旅--时钟