18_内核定时器
内核定时器
一.Linux内核定时器概念
不同于单片机定时器,Linux内核定时器是一种基于未来时间点的计时方式,以当前时刻为启动的时间点,以未来的某一时刻为终止点。比如,现在是10点5分,我要定时5分钟,那么定时就是10点5分+5分钟=10点10分。这个和咱们的手机闹钟很类似。比如你要定一个第二天早晨的8点的闹钟,就是当前时间定时到第二天早晨8点。
需要注意的是,内核定时器定时精度不高,不能作为高精度定时器使用。并且内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。
二.Linux内核定时器基础知识
Linux 内核使用 timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中,定义如下:
struct timer_list {
struct list_head entry;
unsigned long expires; /* 定时器超时时间,不是时长,单位是节拍数 */
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数 */
unsigned long data; /* 要传递给 function 函数的参数 */
int slack;
};
在这个结构体中,有几个参数我们需要重点关注一下。
一个是expires到期时间,单位是节拍数。等于定时的当前的时钟节拍计数(存储在系统的全局变量jiffies)+定时时长对应的时钟节拍数量。
那么我怎么把时间转换成节拍数量呢?
示例:从现在开始定时1秒:
内核中有一个宏HZ,表示一秒对应的时钟节拍数,那么我们就可以通过这个宏来把时间转换成节拍数。
所以,定时1秒就是:
expires = jiffies + 1*HZ
HZ的值我们是可以设置的,也就是说一秒对应的时钟拍数我们是可以设置的,Linux 内核会使用 CONFIG_HZ 来设置自己的系统时钟。打开文件 include/asm-generic/param.h,有如下内容:
# undef HZ
# define HZ CONFIG_HZ
# define USER_HZ 100
宏HZ就是CONFIG_HZ,因此HZ=100,表示一秒的节拍数是100,们在编译 Linux 内核的时候可以通过图形化界面设置系统节拍率,按照如下路径打开配置界面。
通过上图我们可以发现可选的系统节拍率为 100Hz、 200Hz、 250Hz、 300Hz、 500Hz 和1000Hz。默认是100Hz。
第二个我们需要关心的参数是function超时处理函数,这个并不是硬件中断服务程序。原型:void function(unsigned long data);
第三个参数是data 传递给超时处理函数的参数,可以把一个变量的地址转换成unsigned long 。
三.Linux内核定时器相关操作函数
头文件
#include <linux/timer.h>
1.时间转换函数
<1>ms转换为时钟节拍函数
unsigned long msecs_to_jiffies(const unsigned int m);
<2>us转换为时钟节拍函数
unsigned long usecs_to_jiffies(const unsigned int u);
举例:
<1>定时10ms
计算:jiffies +msecs_to_jiffies(10)
<2>定时10us
计算:jiffies +usecs_to_jiffies(10)
<3>宏DEFINE_TIMER
原型:
DEFINE_TIMER(_name, _function, _expires, _data);
作用:静态定义结构体变量并且初始化function, expires, data 成员。
参数:
_name 变量名;
_function 超时处理函数 ;
void (*function)(unsigned long);
_expires到点时间,一般在启动定时前需要重新初始化。
_data 传递给超时处理函数的参数
<4>add_timer 函数
原型:
void add_timer(struct timer_list *timer);
作用:add_timer 函数用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行
参数:
timer:要注册的定时器。
<5>del_timer 函数
原型:
int del_timer(struct timer_list * timer);
作用:del_timer 函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时器之前要先等待其他处理器的定时处理器函数退出
timer:要删除的定时器。
返回值: 0,定时器还没被激活; 1,定时器已经激活。
<6>mod_timer 函数
函数原型:
int mod_timer(struct timer_list *timer, unsigned long expires);
作用:mod_timer 函数用于修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器!
参数:
timer:要修改超时时间(定时值)的定时器。
expires:修改后的超时时间节拍数。
返回值:0,调用 mod_timer 函数前定时器未被激活; 1,调用 mod_timer 函数前定时器已被激活
内核定时器示例
helloworld.c
#include<linux/init.h> //包含宏定义的头文件
#include<linux/module.h> //包含初始化加载模块的头文件
#include <linux/timer.h> //内核定时器相关头文件
void timer_function(unsigned long data); //声明超时处理函数
DEFINE_TIMER(test_timer, timer_function, 0, 0); //静态定义内核定时器并且初始化
void timer_function(unsigned long data) //超时处理函数
{
printk("This is timer_function\n");
mod_timer(&test_timer, jiffies + 1*HZ); //修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器!
}
static int hello_init(void)
{
printk("hello world\n");
test_timer.expires = jiffies + 1*HZ; //把时间转换成节拍数 定时1秒
add_timer(&test_timer); //注册定时器
return 0;
}
static void hello_exit(void)
{
printk("byby\n");
del_timer(&test_timer);
}
module_init(hello_init); //驱动模块的入口
module_exit(hello_exit); //驱动模块的出口
MODULE_LICENSE("GPL"); //声明模块拥有开源许可证
Makefile
obj-m +=helloworld.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
运行结果
按键定时器消抖实验
driver.c
#include <linux/init.h> //包含宏定义的头文件
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/platform_device.h> //平台设备所需要的头文件
#include <linux/of.h> //of函数
#include <linux/of_address.h> //of_iomap函数
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
void timer_function(unsigned long data); //声明超时处理函数
struct device_node *test_device_node; // 节点
int gpio_nu; // GPIO 编号
int irq; // GPIO 对应的中断号
DEFINE_TIMER(test_timer, timer_function, 0, 0); //静态定义内核定时器结构体并且初始化
void timer_function(unsigned long data) //超时处理函数
{
int value = 0;
printk("This is timer_function\n");
value = __gpio_get_value(gpio_nu); //获取按键gpio值
if(value == 0) //当按钮被按下,消抖20ms后
{
printk("gpio is 0\n");
}else if(value == 1)
{
printk("gpio is 1\n");
}
}
irqreturn_t test_key(int irq, void *args) // 中断处理函数
{
printk("test_key is ok\n");
test_timer.expires = jiffies + msecs_to_jiffies(20); //将20ms的时间转换成节拍数,并定时
add_timer(&test_timer); //注册定时器
return IRQ_RETVAL(IRQ_HANDLED); // 正常处理
}
int beep_probe(struct platform_device *pdev)
{
int ret = 0;
printk("beep_probe\n");
test_device_node = of_find_node_by_path("/test_key"); // 查找根节点下的test_key节点
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error\n");
return -1;
}
printk("test_device_node name is %s\n", test_device_node->name);
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0); // 获取GPIO标号
if (gpio_nu < 0)
{
printk("of_get_named_gpio is error\n");
return -1;
}
printk("of_get_named_gpio is ok\n");
ret = gpio_request(gpio_nu, "key0"); //申请一个 GPIO 管脚
if(ret != 0)
{
printk("gpio_request is error\n");
return -1;
}
printk("gpio_request is ok\n");
ret = gpio_direction_input(gpio_nu); // 设置GPIO为输入
if (ret < 0)
{
printk("gpio_direction_input is error\n");
return -1;
}
printk("gpio_direction_input is ok\n");
// irq = gpio_to_irq(gpio_nu); // 获取 gpio 对应的中断号
irq = irq_of_parse_and_map(test_device_node, 0); //从 interupts 属性中提取到对应的设备号
if (irq < 0)
{
printk("gpio_to_irq is error\n");
return -1;
}
printk("irq is %d\n", irq);
ret = request_irq(irq, test_key, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "test_key", NULL); //申请中断, 双边沿触发
if (ret < 0)
{
printk("request_irq is error\n");
return -1;
}
printk("request_irq is ok\n");
return 0;
}
int beep_remove(struct platform_device *platform_device)
{
printk("beep_remove\n");
return 0;
}
struct platform_device_id beep_id_table = {
.name = "123"};
struct of_device_id of_match_table_test[] = { // 与设备树的 compatible 匹配
{
.compatible = "keys"},
{
}};
/* platform 驱动结构体 */
struct platform_driver beep_platform_driver = {
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "beep_test", // 匹配优先级3
.of_match_table = of_match_table_test, // 中的.compatible匹配优先级1
},
.id_table = &beep_id_table // 中的.name匹配优先级2
};
static int beep_driver_init(void)
{
int ret = 0;
printk("hello world\n");
ret = platform_driver_register(&beep_platform_driver); // 注册平台驱动
if (ret < 0)
{
printk("platform_driver_register is error\n");
return ret;
}
return 0;
}
static void beep_driver_exit(void)
{
printk("byby\n");
gpio_free(gpio_nu); //释放GPIO
free_irq(irq, NULL); // 释放中断
platform_driver_unregister(&beep_platform_driver); // 卸载 platform 驱动
del_timer(&test_timer); //删除定时器
}
module_init(beep_driver_init); // 驱动模块的入口
module_exit(beep_driver_exit); // 驱动模块的出口
MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证
Makefile
obj-m +=driver.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!