Linux驱动学习—内核定时器

1、内核定时器

1.1 Linux内核定时器概念

不同于单片机定时器,LInux内核定时器是一种基于未来时间点的计时方式,以当前时刻来启动的时间点,以未来的某一时刻为终止点。比如,现在是10点5分,我要定时5分钟,那么定时就是10点5分+5分钟=10点10分。这个和手机闹钟很相似。比如你要定一个第二天早晨8点的闹钟,就是当前时间定时到第二天早晨8点。

需要注意的是,内核定时器定时精度不高,不能作为高精度定时器使用。并且内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。

1.2 Linux内核定时器基础知识

Linux内核使用timer_list结构体表示内核定时器,timer_list定义在文件include/linux/timer.h中,定义如下:

struct timer_list {
    /*
     * All fields that change during normal runtime grouped to the
     * same cacheline
     */
    struct hlist_node   entry;
    unsigned long       expires;/*定时器超时时间,不是时长,单位是节拍数*/
    void            (*function)(unsigned long);/*定时处理函数*/
    unsigned long       data;/*要传递function函数的参数*/
    u32         flags;
​
#ifdef CONFIG_TIMER_STATS
    int         start_pid;
    void            *start_site;
    char            start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map  lockdep_map;
#endif
};

在上面这个结构体中,有几个参数需要重点关注一下。一个是expires到期时间,单位是节拍数。等于定时的当前的始终节拍计数(存储在系统的全局变量和jiffies)+定时时长对应的时钟节拍数量。

那么如何把时间 转换成节拍数量呢?示例:假如从现在开始定时1秒,转换成节拍数量是多少呢? 内核中有一个宏HZ,表示一秒对应的时钟节拍数,那么我们就可以通过这个宏来把时间转换成节拍数。所以,定时1秒就是expires = jiffies + 1*HZ。

HZ的值我们是可以设置的,也就是一秒对应的时钟拍数我们是可以设置的,Linux内核会使用CONFIG_HZ来设置自己的系统时钟。打开include/asm-generic/param.h,有如下内容:

# undef HZ
# define HZ     CONFIG_HZ   /* Internal kernel timer frequency */
# define USER_HZ    100     /* some user interfaces are */

宏HZ就是CONFIG_HZ,因此HZ=100,表示一秒的节拍数是100,在编译Linux内核的时候可以通过图形化界面设置系统节拍率,按照如下路径打开配置界面。

通过上图我们可以发现可选的系统节拍率为100HZ,200HZ,250HZ,300HZ、500HZ和1000HZ.默认是100HZ。

第二个需要关心的参数是function超时能处理处理函数,这个并不是硬件中断 服务程序。原型:

void function(unsigned long data);

第三个参数是data传递给超时处理函数的参数,可以把一个变量的地址转换成unsigned long。

1.3 Linux内核定时器相关操作函数

1.3.1 时间转换函数
<1>ms转换成时钟节拍函数

在include/linux/jiffies.h

unsigned long msecs_to_jiffies(const unsigned int m);

举例:定时10ms 计算:jiffes+msecs_to_jiffies(10)

<2>us转换成时钟节拍函数
unsigned long usecs_to_jiffies(const unsigned int u);

举例:定时10us 计算:jiffes+usecs_to_jiffies(10)

1.3.2 宏DEFINE_TIMER

在include\linux\timer.h

#define DEFINE_TIMER(_name, _function, _expires, _data)     \
    struct timer_list _name =               \
        TIMER_INITIALIZER(_function, _expires, _data)
参数:_name变量名;__function超时处理函数;_expires到点时间,一般在启动定时前需要重新初始化。

作用:静态定义结构体变量并且初始化function,expires,data成员。

1.3.3 add_timer函数

在include\linux\timer.h

void add_timer(struct timer_list *timer);
参数:timer要注册的定时器。

作用:add_timer函数用于向Linux内核注册定时器,使用add_timer函数向内核注册定时器以后,定时器就会开始运行。

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

作用:del_timer函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器运行,因此在调用del_timer函数删除定时器之前要先等待其他处理器的定时处理其函数退出。

1.3.5 mod_timer 函数
int mod_timer(struct timer_list *timer, unsigned long expires);
参数:
timer:要修改超时时间(定时值)的定时器。
expires:修改后的超时时间。
返回值:0,调用mod_timer 函数前定时器违背激活;
       1,调用mod_timer 函数前定时器已被激活

作用:mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器。

1.4 实验代码

#include <linux/init.h>
#include <linux/module.h>//最基本的文件,支持动态添加和卸载模块
#include <linux/miscdevice.h>//注册杂项设备头文件
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/timer.h>
​
 static void timer_function(unsigned long data);
 
 DEFINE_TIMER(test_timer, timer_function, 0, 0);
 
 static void timer_function(unsigned long data)
 {
    printk("this is timer_function\n");
    mod_timer(&test_timer, jiffies + 1*HZ);
 }
 
​
static int hello_init(void)
{
    test_timer.expires = jiffies + 1 * HZ;
    add_timer(&test_timer);
    return 0;
}
 
static void hello_exit(void)
{
    del_timer(&test_timer);
    printk("hello_exit \n");
}
 
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

2、实验:使用内核定时器实现按键消抖

Linux驱动学习—中断-CSDN博客

在上面链接的按键中断实验代码上修改:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h> 
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
#include <linux/input.h>

struct device_node *test_device_node;
struct property *test_node_property;
int gpio_num;
int irq = 0;

DEFINE_TIMER(test_timer, timer_funtion, 0, 0);

static void timer_funtion(ubsigned long data)
{
	printk("this is timer_function\n");
}

static const of_device_id of_match_table_test[] = {//匹配表
    {.compatible = "keys"},
};

static const platform_device_id beep_id_table ={
    .name = "beep_test",
};

irqreturn_t test_key(int irq, void *args)
{
    printk("test_key\n");
    test_timer.expires = jiffies + msec_to_jiffies(20);
    add_timer(&test_timer);
    
    return IRQ_HANDLED;
}

/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{
    int ret = 0;
    printk("beep_probe\n");
    
    //查找要查找的节点
    test_device_node = of_find_node_by_path("/test_key");
    if (test_device_node == NULL) {
        printk("test_device_node find error\n");
        return -1;
    }
    printk("test_device_node name is %s\n",test_device_node->name);//test_key
    
    gpio_num = of_get_named_gpio(test_device_node, "gpios", 0);
    if (gpio_num < 0) {
        printk("of_get_named_gpio error\n");
        return -1;
    }
    printk("gpio_num name is %d\n",gpio_num);
    
    gpio_direction_input(gpio_num);
    //获取中断号
    //irq = gpio_to_irq(gpio_num);
    irq = irq_of_parse_and_map(test_device_node, 0);
    printk("irq is %d\n",irq);
    
    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);//
    if (ret < 0) {
        printk("request_irq error\n");
        return -1;
    }
    
    return 0;
}
int beep_remove(struct platform_device *pdev)
{
    pritnk("beep_remove \n");
    return 0;
}

strcut platform_driver beep_device = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name  = "123",
        .of_match_table = of_match_table_test,//匹配表 
    },
    .id_table = &beep_id_table,
};

static int beep_driver_init(void)
{
    int ret = -1;
    ret = platform_driver_register(&beep_device);
    if(ret < 0) {
        printk("platform_driver_register error \n");
    }
    printk("platform_driver_register ok\n");
    return 0;
}

static void  beep_driver_exit(void)
{
    free_irq(irq, NULL);
    platform_driver_unregister(&beep_device);
    printk("beep_driver_exit \n");
}

module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL"); 

按下按键:

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux内核定时器内核用于在未来某个时间点或者特定时间段内调度执行某个函数的一种机制。它是一个软定时器,最终依赖于CPU的硬件定时器实现。对于Linux内核来说,它依赖于系统时钟节拍。内核定时器的处理函数在软中断中执行。它有几个特点:依赖于系统时钟节拍、只执行一次,超时后即退出。如果需要周期性的定时器,需要在超时处理函数中重新开启定时器。在Linux内核编程中常常会使用定时器,例如在驱动程序中使用定时器解决按键消抖、延时等待硬件就绪等问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【Linux驱动编程】如何使用内核定时器](https://blog.csdn.net/qq_20553613/article/details/106028620)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [【嵌入式Linux驱动开发】十四、了解Linux内核定时器使用流程,实现LED闪烁](https://download.csdn.net/download/weixin_38664427/14883898)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值