
前言
在【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_usecs | jiffies转换为us |
| jiffies_to_msecs | jiffies转换为ms |
| usecs_to_jiffies | us转换为jiffies |
| msecs_to_jiffies | ms转换为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




