开发者

Linux内核定时器使用及说明

开发者 https://www.devze.com 2025-12-07 10:23 出处:网络 作者: 卿相诉七弦
价值2999元 Java视频教程限时免费下载
专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
立即下载
目录1.linux内核定时器特征2.Linux内核定时器核心数据结构3.Linux内核时间相关转换函数4.Linux内核定时器操作相关API4.1 静态定义结构体变量并且初始化(宏):DEFINE_TIMER4.2 动态初始化定时器(宏):setup_timer4.3
目录
  • 1.linux内核定时器特征
  • 2.Linux内核定时器核心数据结构
  • 3.Linux内核时间相关转换函数
  • 4.Linux内核定时器操作相关API
    • 4.1 静态定义结构体变量并且初始化(宏):DEFINE_TIMER
    • 4.2 动态初始化定时器(宏):setup_timer
    • 4.3 注册定时器到内核:add_timer
    • 4.4 注销内核定时器:del_timer,del_timer_sync
    • 4.5 修改定时器定时时间:mod_timer
  • 5.Linux内核定时器示例
    • 5.1 内核定时器编程步骤
    • 5.2 定时2s
    • 5.3 循环定时
    • 5.4 全局驱动数据
    • 5.5 Linux内核定时器应用:实现按键消抖
      • 5.5.1 按键的抖动分析
      • 5.5.2 按键驱动程序改进
  • 总结

    1.Linux内核定时器特征

    1. 依赖Linux系统心跳节拍发生器的定时器;
    2. 基于未来的时间点的一种定时方式;
    3. 支持多段定时,互不干扰:一段定时还没有完成,可以再启动新一段定时;
    4. 内核定时器支持的是单次定时方式,不是循环定时;
    5. 内核定时器定时精度不高,如果需要高精度定时需要使用硬件定时器或使用内核高精度定时器;
    6. 定时器的超时处理函数只会在注册它的CPU上运行,不会存在并发问题。

    2.Linux内核定时器核心数据结构

    内核使用struct timer_list表示一个内核定时器,定义如下:

    struct timer_list {
           struct hlist_node entry;                                    //实现定时器结构链表化管理
           unsigned long             expires;                          //到期时间点      不是时长
           void                     (*function)(unsigned long);        //超时处理函数
           unsigned long             data;
           u32               flags;
           int                 slack;
    };

    重要成员:

    到期时间,基于未来时间点的时钟节拍数。等于定时时刻当前的时钟节拍计数(存储在系统的全局expires变量jiffies)+定时时长对应的时钟节拍数量。

    示例:从现在开始定时1秒:expires = jiffies + 1*Hz

    Hz:表示一秒对应的时钟节拍数

    • function超时处理函数,并不是硬件中断服务程序。
    • data传递给超时处理函数的参数,可以把一个变量的地址转换成unsigned long

    3.Linux内核时间相关转换函数

    定时使用的不是时长,是基于未来时间点,单位是时钟节拍。

    HZ宏:1秒对应时钟节拍。

    unsigned long msecs_to_jiffies(const unsigned int m)             //把ms转换为时钟节拍
    unsigned long usecs_to_phpjiffies(const unsigned int u)             //把us转换为时钟节拍

    示例:定时2s+200ms+500us

    到期时间:jiffies + 2*HZ+msecs_to_jiffies(200)+msecs_to_jiffies(500)

    4.Linux内核定时器操作相关API

    4.1 静态定义结构体变量并且初始化(宏):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到期时间,一般在启动定时前需要重新初始化。

    _data传递给超时处理函数的参数

    功能

    定义一个定时器结构并且初始化function, expires, data成员。

    4.2 动态初始化定时器(宏):setup_timer

    头文件

    #include <linux/timer.h>

    原型

    #define setup_timer(timer, fn, data)      __setup_timer((timer), (fn), (data), 0)

    参数

    timer定时器结构指针

    n超时处理函数

    data超时处理函数参数

    功能

    运行时初始化定时器

    struct timer_list btn_timer;
    setup_timer(&btn_timer, fn, data)
    btn_timer.expires = jiffies+定时时长对应的时钟节拍数;
    启动定时器。。。

    4http://www.devze.com.3 注册定时器到内核:add_timer

    头文件

    #include <linux/timer.h>

    原型

    void add_timer(struct timer_list *timer)

    返回值

    功能

    向内核注册一个定时器,同步启动定时。本质上把定时器结构添加到定时链表

    注意:同一个结构变量在启动定时后,没有超时前重复注册没有用的。

    struct timer_list timer,
    ……
    add_timer(&timer);
    add_timer(&timer);
    ……
    

    4.4 注销内核定时器:del_timer,del_timer_sync

    头文件

    #include <linux/timer.h>

    原型

    int del_timer(struct timer_list *timer)

    int del_timer_sync(struct timer_list *timer)

    功能

    从内核中注销一个定时器。(本质上把定时器结构从定时链表取走)

    区别:  del_timer 单处理器系统安全删除定时器函数,del_timer_sync SMP系统安全删除定时器的函数。

    4.5 修改定时器定时时间:mod_timer

    头文件

    #include <linux/timer.h>

    原型

    int mod_timer(struct timer_list *timer, unsigned long expires)

    参数

    timer定时器指针;

    expires未来的到期时钟节拍。

    功能

    修改定时器时间以及启动定时。

    5.Linux内核定时器示例

    5.1 内核定时器编程步骤

    • 编写一个定时器超时处理函数。
    • 定义struct timer_list结构变量。
    • 初始化上一步定义的结构变量。
    • 重新初始化到期时间,马上启动定时器(注册定时器)。
    • 如果不需要定时器,可以删除定时器(注销定时器),这一步是可选。

    5.2 定时2s

    #include<linux/module.h>
    #include<linux/init.h>
    #include<linux/timer.h>
    
    static void timer_func(unsigned long data);
    
    //静态定义:定义并且初始化
    DEFINE_TIMER(my_timer,timer_func,0,(unsigned long)"LIU");
    
    //定时器超时函数
    static void timer_func(unsigned long data)
    {
    	printk("data:%s\r\n",(char *)data);
    	//启动新一次定时,实现循环定时
    	mod_timer(&my_timer,jiffies + 2*HZ);
    }
    
    static int __init timer_list_init(void)
    {
    	//重新初始化到期时间
    	my_timer.expires = jiffies + 2*HZ;	//2s
    	
    	//马上启动定时器
    	printk("-----begin------\r\n");
    	add_timer(&my_timer);
    	printk("------end-------\r\n");
        return 0; 
    }
    
    static void __exit timer_list_exit(void)
    {
    	//删除定时器
    	del_timer_sync(&my_timer);
    }
    
    module_init(timer_list_init);			// 指定模块初始化函数
    module_exit(timer_list_exit);			// 指定模块清理函数
    MODULE_LICENSE("GPL");					// 声明模块许可证(GPL v2) 
    

    Linux内核定时器使用及说明

    5.3 循环定时

    #include<linux/module.h>
    #include<linux/init.h>
    #include<linux/timer.h>
    
    static void timer_func(unsigned long data);
    
    //静态定义:定义并且初始化
    DEFINE_TIMER(my_timer,timer_func,0,(unsigned long)"Time out");
    
    //定时器超时函数
    static void timer_func(unsigned long data)
    {
    	printk("data:%s\r\n",(char *)data);
    	//启动新一次定时,实现循环定时
    	mod_timer(&my_timer,jiffies + 2*HZ);
    }
    
    static int __init timer_list_init(void)
    {
    	//重新初始化到期时间
    	my_timer.expires = jiffies + 2*HZ;	//2s
    	
    	//马上启动定时器
    	printk("-----begin------\r\n");
    	add_timer(&my_timer);
    	printk("------end-------\r\n");
        return 0; 
    }
    
    static void __exit timer_list_exit(void)
    {
    	//删除定时器
    	del_timer_sync(&my_timer);
    }
    
    module_init(timer_list_init);			// 指定模块初始化函数
    module_exit(timer_list_exit);			// 指定模块清理函数
    MODULE_LICENSE("GPL");					// 声明模块许可证(GPL v2) 
    

    Linux内核定时器使用及说明

    5.4 全局驱动数据

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/timer.h>
    
    //全局驱动数据
    struct drv_data{
    	struct timer_list timer;
    	char *data;
    	u64 expires;
    };
    static struct drv_data gvar;
    
    //定时器超时处理函数
    static void timer_func(unsigned long data)
    {
    	struct drv_data *pdata = (struct drv_data *)data;
    	printk("data:%s\r\n",pdata->data);
    	//启动新一次定时,实现循环定时
    	mod_timer(&pdata->timer,jiffies + pdata->expires);
    }
    
    static int __init timer_list_init(void)
    {
    	//普通成员初始化
    	gvar.data    = "Time out";
    	gvar.expires = 2*HZ;
    	
    	//初始化定时器
    	setup_timer(&gvar.timer,timer_func, (unsigned long)&gvar);
    	
    	//重新初始化到期时间
    	gvar.timer.expires = jiffies + gvar.expires;	//2s
    	
    	//马上启动定时器
    	printk("-----begin------\r\n");
    	add_timer(&gvar.timer);
    	printk("------end-------\r\n");
        return 0; 
    }
    
    static void __exit timer_list_exit(void)
    {
    	//删除定时器
    	del_timer_sync(&gvar.timer);
    }
    module_init(timer_list_init);			// 指定模块初始化函数
    module_exit(timer_list_exit);			// 指定模块清理函数
    MODULE_LICENSE("GPL");					// 声明模块许可证(GPL v2) 
    

    Linux内核定时器使用及说明

    5.5 Linux内核定时器应用:实现按键消抖

    5.5.1 按键的抖动分析

    Linux内核定时器使用及说明

    Linux内核定时器使用及说明

    裸机按键:检测,   延时,  再检测,确认按键

    传统的防抖方法用户体验较差,因编程为延时时间难以精确设定,还会占用较多的CPU资源。

    早期的按键驱动程序设计:当按键中断触发时,直接读取按键状态来判断按键动作,这种方法容易受到机械抖动的影响。

    引入内核定时器机制后,可以在按键中断触发时启动一个定时任务。待定时器超时时,在其回调函数中读取按键的状态值。利用这一机制,可以每次在未超时时重新设置定时器,从而确保每次从当前时刻重新开始计算延时,有效避免了由于抖动带来的误判。这种方式不仅提高了检测的准确性,还优化了系统资源的使用效率。

    中断程序启动定时器,每次从当前时间开始定时10ms,中断程序返回

    1. 当出现第一个下降沿,进入中断,启动10ms定时,退出中断
    2. 定时10ms过程中,出现抖动,上升沿出现,进入中断,启动10ms定时,退出中断
    3. 定时10ms过程中,出现抖动,下降沿出现,进入中断,启动10ms定时,退出中断
    4. 重复2,3步骤……直到最后一次抖动边沿,10ms后就有机会超时。
    5. 在超时函数读取按键状态

    松开检测方法相同。

    5.5.2 按键驱动程序改进

    drv-btn.c

    //增加内核定时器功能
    #include<linux/module.h>
    #include<linux/fs.h>
    #include<linux/miscdevice.h>
    #include<asm/uAccess.h>
    #include<linux/interrupt.h>
    #include<linux/irq.h>
    #include<linux/gpio.h>
    #include<linux/slab.h>
    
    #define LEDS_MINOR	255
    #define DEVICE_NAME  "my-buttons" // 设备节点名称,将在/dev目录下创建 
    
    #ifndef ARRAYSIZE
    #define ARRAYSIZE(a)	(sizeof(a)/sizeof(a[0]))
    #endif
    
    //按键数量,在模块初始化函数中进行计算
    static int key_size;
    //按键缓冲区,一个元素存放一个按键值,'1'表示按下,'0'表示松开
    //在模块的初始化函数中分配缓冲区空间
    static char *keys_buf;	
    
    //使用面向对象思想设计按键,把一个按键信息进行封装
    struct key_info{
    	int id;					//按键编号
    	int gpio;				//统一的GPIO编号
    	unsigned long flags;	//触发方式
    	char *name;				//按键名
    	int irq;				//中断编号
    	struct timer_list timer;//增加一个定时器作消抖
    };
    
    //实例化对象
    static struct key_info keys[]={
    	[0]={
    		.id 	= 0,
    		.gpio 	= 5,
    		.flags 	= IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    		.name 	= "key-0",
    	},
    	[1]={
    		.id 	= 0,
    		.gpio 	= 54,
    		.flags 	= IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    		.name 	= "key-js1",
    	},	
    };
    
    static int key_open (struct inode *pinode, struct file *pfile)
    {
        printk("line:%d,%s is call\n",__LINE__,__FUNCTION__);
        return 0;    
    }
    
    loff_t key_llseek (struct file *pfile, loff_t offset, int whence)
    {
        return 0;    
    }
    
    static ssize_t key_read (struct file *pfile, char __user *buf, size_t count, loff_t *offset)    
    {
        int ret;
    	if(count > key_size)	count = key_size;
        if(count == 0)			return 0;
    	
    	//准备数据,但是按键数据在中断中实时更新,不需要在这里读取
    	//复制数据到用户空间
    	ret = copy_to_user(buf, keys_buf, count);
    	if(ret){
    		printk("error:copy_to_user\r\n");
    		ret = -EFAULT;
    		goto errot_copy_to_user;
    	}
    	return count;  // 返回成功读取的字节数  
    errot_copy_to_user:
    	return ret;
    }
    
    int key_release (struct inode *pinode, struct file *pfile)
    {
        return 0;
    }
    
    // 文件操作结构体(关联实现的操作函数) 
    static const struct file_operations dev_fops = {
        .open = key_open,      // 打开设备 
        .read = key_read,      // 读取设备状态 
    	.owner = THIS_MODULE,
    };
    
    // 杂项设备定义 
    static struct miscdevice key_device = {
        .name = DEVICE_NAME,	// 设备名称(出现在/dev目录下) 
        .minor = LEDS_MINOR, 	// 次设备号(建议使用动态分配) 
        .fops = &dev_fops,		// 关联文件操作函数集 
    };
    
    //超时处理函数
    void btn_timer(unsigned long data)
    {
    
    	int s;
    	struct key_info *pdata = (struct key_info *)data;
    
    	//检测当前的电平状态
    	s = !gpio_get_value(pdata->gpio);
    	keys_buf[pdata->id]='0'+s;	//保存状态
    }
    
    //按键中断函数
    //设置了双边触发,按下和松开都会进入这个函数
    irqreturn_t btns_irq_handler(int irq,void *devid)
    {
    	struct key_info *pdata = (struct key_info *)devid;
    
    	//启动新一次定时
    	mod_timer(&pdata->timer,jiffies + msecs_to_jiffies(20));
    	return IRQ_HANDLED;
    }
    
    static int __init xyd_btn_init(void)
    {
        int ret,i;
    
    	key_size = ARRAY_SIZE(keys);	//计算按键数量
    
    	//分配按键缓冲区
    	keys_buf = kzalloc(key_size,GFP_KERNEL);
    	if(keys_buf == NULL)
    		return -EFAULT;
    	
    	//循环注册中断
    	for(i = 0;i < key_size;i++){
    		keys[i].irq = gpio_t编程o_irq(keys[i].gpio);
    		if(keys[i].irq < 0)
    		{
    			printk("error:gpio_to_irq\r\n");
    			goto error_gpio_to_irq;  
    		}
    		printk("irq:%d\r\n",keys[i].irq);
    		
    		//传递每个按键结构变量地址,发生中断时可以通过参数取得
    		ret = request_irq(keys[i].irq, btns_irq_handler, keys[i].flags, keys[i].name, (void *)&keys[i]);
    		if(ret < 0)
    		{
    			printk("error:request_irq\r\n");
    			goto error_request_irq;  
    		}
    
    		//初始化按键的定时器,参数是按键自身的结构内存地址
    		setup_timer(&keys[i].timer,btn_timer, (unsigned long)&keys[i]);
    	}
        ret = misc_register(&key_device);
        if(ret < 0){
            printk("error:misc_register\r\n");
            goto error_misc_register;  
        }
        return 0;  								// 初始化成功 
    
    error_misc_register:
    error_request_irq:
    	while(--i >= 0){
    		free_irq(keys[i].irq, (void *)&keys[i]);	//注销中断
    	}
    	
    error_gpio_to_irq:
    	kfree(keys_buf);		//释放按键缓冲区空间
        return ret; 
    }
    
    static void __exit key_btn_exit(void)
    {
    	int i = key_size;
    	while(--i >= 0){
    		free_irq(keys[i].irq, (void *)&keys[i]);	//注销中断
    	}
    	misc_deregister(&key_device);	//注销杂项设备
    	kfree(keys_buf);				//释放按键缓冲区空间
    }
    
    module_init(xyd_btn_init);		  								// 指定模块初始化函数 
    module_exit(key_btn_exit);  									// 指定模块清理函数 
    MODULE_LICENSE("GPL");											// 声明模块许可证(GPL v2) 
    MODULE_AUTHOR("LIU");    										// 模块作者 
    MODULE_DESCRIPTION("RK3399 GPIO KEY Control Driver"); 			// 模块描述 
    

    app.c

    #include<stdio.h>              // 标准输入输出库(printf, perror等)
    #include<stdlib.h> 
    #include<string.h> 
    #include<sys/types.h>          // 系统数据类型定义(如dev_t)
    #include<sys/stat.h>           // 文件状态信息(文件模式等)
    #include<fcntl.h>              // 文件控制选项(open等)
    #include<unistd.h>             // 系统调用封装(lseek, read, write, sleep等)
    #include<sys/ioctl.h>          // I/O控制操作(ioctl)
    #include<errno.h> 
    
    #define BTN_SIZE	1					// 按键数量
    #define DEV_NAME 	"/dev/my-buttons"  	// 默认设备名
    
    int main(int argc, char **argv)
    {
        int fd,ret, i;                
        const char *devname; 				// 设备路径指针(初始化为默认路径)
        unsigned char pre_buf[BTN_SIZE+1],recv_buf[BTN_SIZE+1];
    
    	memset(pre_buf,'0',BTN_SIZE);
    	memset(recv_buf,'0',BTN_SIZE);
    	
        if(argc == 1) 
            devname = DEV_NAME;
        else if(argc == 2)
            devname= argv[1];
        else {
            printf("Usage:%s [/dev/devname]\r\n", argv[0]);
            return 0;
        }
    
        fd = open(devname, O_RdwR);  			// O_RDWR:以读写模式打开
        if(fd < 0) {
            perror("open");   					// 打印系统错误信息
            printf("fd=%d\r\n", fd);
            return -1;            				// 打开失败退出程序
        }
        printf("fd=%d\r\n", fd);  				// 成功打开后输出fd值
    
        while(1) {
    		ret = read(fd,recv_buf,BTN_SIZE);	// 读取按键数据
    		if(ret < 0){
    			if(errno != EAGAIN){
    				perror("read");
    				exit(-1);
    			}else	continue;
    		}
    
    		//只在状态发生变化时候才输出
    		for(i = 0;i < BTN_SIZE;i++){
    			//分别判断每一个按键状态是否发生变化
    			if(recv_buf[i] != pre_buf[i]){
    				//更新当前状态为上一次状态
    				pre_buf[i] = recv_buf[i];
    
    				//判断这次变化是按下还是松开
    				if(pre_buf[i] == '1')	printf("KEY%d is press!\r\n",i+1);
    				else					printf("KEY%d is up!\r\n",i+1);
    			}
    		}
        }    
        return 0;
    }
    

    现象

    Linux内核定时器使用及说明

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0
    价值2999元 Java视频教程限时免费下载
    专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
    立即下载

    精彩评论

    暂无评论...
    验证码 换一张
    取 消