susplus头像
【Linux驱动开发】内核定时器与延时函数详解封面图

【Linux驱动开发】内核定时器与延时函数详解

请添加图片描述

前言

在【Linux驱动开发】中断、工作队列及中断子系统详解这片文章中,我们完成了非阻塞按键的驱动实现,但是会发现,按键的触发是有误触的,因为在按键按下和释放的过程中,会有很多“毛刺”,造成是否按下失误,避免这个问题有两种方法:硬件消抖和软件消抖;硬件通过加入电容来实现,软件就是通过延时来解决的。因此,在这片文章中将会介绍Linux内核中的定时器与延时函数。

一、内核定时器的概念

Linux 内核定时器timer_list是基于系统节拍jiffies实现的一次性延时机制,利用多级时间轮管理,到期后在软中断上下文执行回调函数,回调不能睡眠;本身不会自动重复触发,周期性定时需要在回调中重新调用mod_timer,精度受系统节拍限制,删除时优先用del_timer_sync等待回调执行完毕避免竞态,适合普通驱动延时,高精度场景则选用 hrtimer。

赫兹(Hz)在内核中表示系统一秒所对应的节拍数量,目前为100。在内核中的.config中定义, jiffies表示系统节拍,记录系统启动到当前累加的数字,如果想通过内核定时器计时则定时时间为: jiffers +(N*100)定时时间到达后可以调用回调函数。

二、内核定时器相关函数

函数名功能
init_timer初始化定时器
DEFINE_TIMER定义定时器
add_timer激活定时器
del_timer删除定时器
mod_timer修改定时器超时时间
time_before判断时间a是否在b之前
time_after判断时间a是否在b之后
jiffies_to_usecsjiffies转换为us
jiffies_to_msecsjiffies转换为ms
usecs_to_jiffiesus转换为jiffies
msecs_to_jiffiesms转换为jiffies

三、使用步骤

3.1 方法一(推荐)

  • 定义一个定时器变量
  • 使用init_timer,初始化。
  • void (*function)(unsigned long);,时间到了后的回调函数。
  • 定时器的成员变量,unsigned long expires,设置过期时间,在jiffies+HZ的基础上,可以配合使用msecs_to_jiffies。整个成员需要的是 jiffies,需要把 ms 转换为 jiffies。
  • add_timer();激活定时器,如果先前的定时器没有到期,就再次添加定时器到内核,可能会崩溃。这个时候需要使用mod_timer

3.2 方法二

  • 直接使用DEFINE_TIMER,定义变量。
  • 使用init_timer,初始化。
  • 设置回调函数
  • 定时器的成员变量,unsigned long expires,设置过期时间,在jiffies+HZ的基础上,可以配合使用msecs_to_jiffies。
  • 激活定时器 add_timer/mod_timer

注意:这是老内核init_timer的写法,新版内核推荐timer_setup,不再手动赋值 function。

四、Linux内核的延时函数

4.1 概念

Linux内核实现了延时函数, delay相关函数是忙等待,一直占用CPU资源。 sleep相关函数不会一直占用CPU资源,会在睡眠的过程中释放CPU,让进程挂起,但是Linux系统是非实时操作系统,所以当CPU切换出该进程到CPU切换回该进程中间的时间一般大于睡眠设定的时间。

相对来说, delay 的时间相对能准确些。 sleep回来调度的时间不一定准确。

4.2 相关函数接口

函数名功能
ndelay忙等待延时ns
udelay忙等待延时us
mdelay忙等待延时ms
msleep睡眠ms
msleep_interruptible可中断睡眠ms
ssleep睡眠s

五、定时器代码示例

/**
 * @file key_drv.c
 * @author suaplus
 * @brief
 gpio1_18
低电平,按键按下
 1.这个一个key 的测试的驱动
 2.加入自动创建设备节点,不用在mknod
 3. 使用misc 方式,编写驱动
 4. 加入dts的支持
 5. pin+gpio
 6. 加入阻塞功能
 7. 加入定时器,按键消抖

 * @version 0.1
 * @date 2026-10-09
 *
 * @copyright Copyright (c) 2026
 *
 */

#include "asm-generic/errno-base.h"
#include "asm-generic/gpio.h"
#include "asm/gpio.h"
#include "asm/uaccess.h"
#include "linux/export.h"
#include "linux/fs.h"
#include "linux/interrupt.h"
#include "linux/jiffies.h"
#include "linux/kdev_t.h"
#include "linux/kernel.h"
#include "linux/of_irq.h"
#include "linux/printk.h"
#include "linux/string.h"
#include "linux/workqueue.h"
#include "linux/timer.h"
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/workqueue.h>

#define DEV_NAME "key0"  // /dev/key0

extern void msleep(unsigned int msecs);

#define BITS_OFFSET 18
static int myopen(struct inode *node, struct file *f);
static int myrelease(struct inode *node, struct file *f);
static ssize_t myread(struct file *f, char __user *data, size_t size, loff_t *off);
static ssize_t mywrite(struct file *f, const char __user *data, size_t size, loff_t *off);
static ssize_t show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
static void task_handle(unsigned long arg);

static irqreturn_t myhandler(int, void *);
static void tm_handle(unsigned long data);

static struct device_node *pnode = NULL;

static struct device_attribute attr = __ATTR(key_state, 0664, show, store);
static int key_gpio = 0;
static wait_queue_head_t wait_que;
static int wait_que_cond = 0;
static int irq_no = 0;

struct tasklet_struct mytasklet;
static struct timer_list mytimer;

DECLARE_TASKLET(mytasklet, task_handle, 0);

typedef enum
{
    MYkey_OFF,
    MYkey_ON
} MYkey_STATE;
MYkey_STATE mykey_state;

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = myopen,
    .release = myrelease,
    .write = mywrite,
    .read = myread,
};

static struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,  // 动态分配次设备号
    .name = DEV_NAME,             // 在/dev目录下设备名,-> open
    .fops = &fops,
};

// sys 目录下 key状态文件被读出调用
// cat /sys/class/key/key0/key_state
// key_OFF  key_ON
ssize_t show(struct device *dev, struct device_attribute *attr, char *buf)
{
    if (gpio_get_value(key_gpio))
    {
        // key up
        // key_status = 0;
        strcpy(buf, "key_up");
    }
    else
    {
        // key press
        // key_status = 1;
        strcpy(buf, "key_down");
    }
    return strlen(buf);
}
// sys 目录下 key状态文件被写入调用
// echo "1\n" > /sys/class/key/key0/key_state
// echo "0\n" > /sys/class/key/key0/key_state
ssize_t store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    pr_info("store call...\n");
    return count;
}

static int myopen(struct inode *node, struct file *f)
{
    pr_info("myopen call...\n");
    return 0;
}
static int myrelease(struct inode *node, struct file *f)
{
    pr_info("close call...\n");
    return 0;
}

static void tm_handle(unsigned long data)
{
    if (gpio_get_value(key_gpio))
    {
        // 10ms高电平,按键受到干扰
    }
    else
    {
        wait_que_cond = 1;
        wake_up(&wait_que);
    }

    return;
}

static void task_handle(unsigned long arg)
{
    //开启定时器,过期时间是10ms后
    mytimer.expires = jiffies + msecs_to_jiffies(10);
    mod_timer(&mytimer, mytimer.expires);
    return;
}

irqreturn_t myhandler(int irq_no, void *arg)
{
    //需要调度 tasklet
    tasklet_schedule(&mytasklet);
    return IRQ_HANDLED;
}
static ssize_t myread(struct file *f, char __user *data, size_t size, loff_t *off)
{
    int ret = 0;
    u32 key_status = 0;
    wait_que_cond = 0;
    wait_event_interruptible(wait_que, wait_que_cond);
    if (gpio_get_value(key_gpio))
    {
        // key up
        key_status = 0;
    }
    else
    {
        // key press
        key_status = 1;
    }

    ret = copy_to_user(data, &key_status, sizeof(key_status));
    if (ret)
    {
        pr_err(" myread copy_to_user error\n");
        return -EFAULT;
    }

    return sizeof(key_status);
}

// 1 -> key on   0 -> key_off

static ssize_t mywrite(struct file *f, const char __user *data, size_t size, loff_t *off)
{
    pr_info("mywrite call...\n");

    return size;
}

static int __init mykey_init(void)
{
    int ret = 0;
    // 设备号的申请(10),自动创建设备节点,杂项设备,属于字符设备的一种
    ret = misc_register(&misc_dev);
    if (ret < 0)
    {
        pr_err("misc_register error\n");
        return ret;
    }
    // 从设备树中,找到对应的节点。
    pnode = of_find_node_by_path("/putekey");
    if (!pnode)
    {
        pr_err("of_find_node_by_path error\n");
        ret = -ENODEV;
        goto del_misc;
    }
    //获取gpio号
    key_gpio = of_get_named_gpio(pnode, "gpio-key", 0);
    if (key_gpio < 0)
    {
        pr_err("of_get_named_gpio error\n");
        ret = -ENODEV;
        goto del_pnode;
    }
    //申请使用gpio
    ret = gpio_request(key_gpio, "key");
    if (ret)
    {
        pr_err("gpio_request error\n");
        goto del_pnode;
    }

    gpio_direction_input(key_gpio);
    gpio_set_value(key_gpio, 1);

    // 初始化  等待队列
    init_waitqueue_head(&wait_que);
    init_timer(&mytimer);
    mytimer.function = tm_handle;

    // 从设备树中,取出 中断号
    irq_no = of_irq_get(pnode, 0);
    if (irq_no < 0)
    {
        pr_err("of_irq_get error\n");
        ret = irq_no;
        goto del_gpio;
    }

    ret = request_irq(irq_no, myhandler, IRQF_TRIGGER_FALLING, "key_irq", NULL);
    if (ret < 0)
    {
        pr_err("request_irq error\n");
        // return ret;
        goto del_gpio;
    }

    init_timer(&mytimer);

    ret = device_create_file(misc_dev.this_device, &attr);
    if (ret < 0)
    {
        pr_err("device_create_file error\n");
        // return ret;
        goto del_irq;
    }
    pr_info("key drv ok\n");

    return 0;
del_irq:
    free_irq(irq_no, NULL);
del_gpio:
    gpio_free(key_gpio);
del_pnode:
    of_node_put(pnode);
del_misc:
    misc_deregister(&misc_dev);

    return ret;
}
static void __exit key_exit(void)
{
    device_remove_file(misc_dev.this_device, &attr);
    free_irq(irq_no, NULL);
    gpio_free(key_gpio);
    of_node_put(pnode);
    misc_deregister(&misc_dev);

    return;
}

module_exit(key_exit);
module_init(mykey_init);
MODULE_LICENSE("GPL");

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2403_89702754/article/details/168336286

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--