【Linux驱动】内核定时器控制 LED 闪烁

news/2024/7/24 10:17:50 标签: linux, 运维, 服务器

Linux 提供了定时器,当超过预定时间时,就会触发回调函数,此外,Linux内核还提供了短延时函数,比如微秒、纳秒、毫秒延时函数。

一、内核定时器API

1、节拍数

内核定时器是通过节拍数来计时的,节拍数与时间存在关联性,在 include/asm-generic/param.h  中定义了一秒会产生多少次节拍数。由图中可知,当前系统一秒产生的节拍数为 100 次。

知道了节拍数的概念,接下来就不得不提一下全局变量 jiffies 了,jiffies 定义在文件 linux/jiffies.h 中,用于记录系统从启动以来的系统节拍数,类似于时间戳。由此可知定时器延时的基本原理,假设当前 jiffies = 1000,我们要延时2s(200次节拍),当 jiffies = 1000 + 200 时,说明定时的时间到了,此时就会执行相应的回调函数。

注意:jiffies既然是变量,那必然存在溢出的风险,溢出以后会 jiffies 重新从 0 开始计数

2、定时器数据结构

Linux内核提供了 struct timer_list 类型来表示定时器,timer_list 定义在文件 linux/timer.h 中。

struct timer_list {
	struct list_head entry;
	unsigned long expires;            // 超时时间(单位: 节拍数)
	struct tvec_base *base;

	void (*function)(unsigned long);  // 回调函数
	unsigned long data;               // 回调函数的参数

	int slack;
};

超时时间: 

超时时间其实是一个时间点,表示时间点的不是秒或者毫秒,而是触发回调的 jiffies 变量的值。

超时时间点 = jiffies + <delay>

  • jiffies:可以表示起始时间点
  • <delay>:延时节拍数,为了方便,Linux提供了将时间转换成节拍数的API
APIAPI声明解析
msecs_to_jiffieslong msecs_to_jiffies(const unsigned int m)ms  转 节拍数
usecs_to_jiffieslong usecs_to_jiffies(const unsigned int u)us  转 节拍数
nsecs_to_jiffiesunsigned long nsecs_to_jiffies(u64 n)ns  转 节拍数

回调函数: 

当 jiffies  到达目标节拍数时,就会触发回调函数

3、初始化定时器

定义了一个 timer_list 类型变量后必须使用 init_timer 进行初始化。init_timer 是一个宏,这里为了方便介绍以函数形式展现: 

void init_timer(struct timer_list *timer);

 

4、注册定时器

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

void add_timer(struct timer_list *timer);

5、删除定时器

删除定时器可以使用 del_timer 或者 del_timer_sync

  • del_timer:需要等待定时器退出处理函数才能调用 del_timer
  • del_timer_sync:del_timer的同步版,会自动等待定时器退出处理函数,然后执行删除操作
/**
 * @return 返回 0,表示定时器尚未启动;返回 1,表示定时器已启动
 */
int del_timer(struct timer_list * timer);
int del_timer_sync(struct timer_list *timer);

6、修改定时器的超时时长

mod_timer 函数用于修改定时值,即延迟多长时间后执行回调。如果定时器处于尚未启动的状态,mod_timer 函数会启动定时器。

/**
 * @param timer:   要操作的定时器
 * @param expires: 超时时间(下一次执行回调函数的时间)
 * @return         返回 0,表示定时器尚未启动;返回 1,表示定时器已启动
 */
int mod_timer(struct timer_list *timer, unsigned long expires);

二、内核定时器控制LED

这里重点介绍定时器相关内容,设备注册、驱动节点创建、设备树等内容暂不一一列举

1、创建定时器并初始化

在自定义的驱动结构体中声明一个定时器

struct chrdev_t 
{
	dev_t 	 			devid;				/* 设备号 */
	uint32_t 			major;				/* 主设备号 */
	uint32_t 			minor;				/* 次设备号 */
	struct cdev 		dev;				/* 字符设备 */
	struct class*   	class;				/* 设备节点所属类 */
	struct device*  	driver_node;		/* 驱动文件节点 */ 
	
	struct timer_list   timer;				/* 内核定时器 */
     uint32_t 			next_jiffies;	    /* 下一次定时器启动的jiffies节拍数 */     

	struct device_node* gpioNode;			/* 设备树节点 */
	uint32_t 			gpioNum;			/* gpio 引脚编号 */
	uint8_t 			status;				/* LED 的状态 */
};
static struct chrdev_t chrdev;

在驱动入口函数中初始化定时器

// 回调函数
void timer_callback(unsigned long arg)
{
	struct chrdev_t* timerDev = (struct chrdev_t*)arg;
    printk("LED状态: %d\n", timerDev->status);
	// 反转 LED 状态
	timerDev->status = !timerDev->status;
    // 重新启动定时器
	mod_timer(&timerDev->timer, timerDev->next_jiffies);
}

// 驱动入口函数
static int __init kerneltimer_init(void)
{
    // ... 

	/* 初始化定时器 */
	init_timer(&chrdev.timer);
	chrdev.timer.function = timer_callback;
	chrdev.timer.data = (unsigned long)&chrdev;

    // ... 
}

// 驱动出口函数
static void __exit kerneltimer_exit(void)
{
    // ...

	/* 删除定时器 */
	del_timer_sync(&chrdev.timer);
}

2、ioctl 驱动操作函数实现

应用层推荐使用 ioctl 接口来传递延时时间,对应的内核驱动接口便是 unlocked_ioctl

static struct file_operations chrdev_fops = {
	.owner = THIS_MODULE, 
	.open = chrdev_open,
	.unlocked_ioctl = chrdev_ioctl,        // 对应应用层的 ioctl 接口
	.release = chrdev_release,
};

我们可以在 chrdev_ioctl 中启动定时器,这里我们使用 mod_timer,虽然 add_timer 可以启动定时器,但无法对同一个定时器注册多次。

// 超时时间 = 定时器启动时间点jiffies  + 延时时间 delay
#define timer_delay(delay)		jiffies + msecs_to_jiffies(delay)

static int chrdev_open(struct inode *pinode, struct file *pfile)
{
	printk("open timer\n");
	pfile->private_data = &chrdev;
	return 0;
}

static long chrdev_ioctl(struct file * pfile, unsigned int cmd, unsigned long arg)
{    
    struct chrdev_t* timerDev = pfile->private_data;
    // 使用 arg 来传递定时器的延时时间
    unsigned long delayms = arg;
    
    mod_timer(&timerDev->timer, timer_delay(delayms));
    return 0;
}


http://www.niftyadmin.cn/n/5289648.html

相关文章

设计模式-注册模式

设计模式专栏 模式介绍模式特点应用场景注册模式和单例模式的区别代码示例Java实现注册模式Python实现注册模式 注册模式在spring中的应用 模式介绍 注册模式是一种设计模式&#xff0c;也称为注册树或注册器模式。这种模式将类的实例化和创建分离开来&#xff0c;避免在应用程…

AGV智能搬运机器人-替代人工工位让物流行业降本增效

在当今快速发展的世界中&#xff0c;物流业面临着巨大的挑战&#xff0c;包括提高效率、降低成本和优化工作流程。为了应对这些挑战&#xff0c;一种新型的自动化设备——智能搬运机器人正在崭露头角。本文将通过一个具体的案例来展示富唯智能转运机器人在实际应用中的价值。 案…

Checkpoint 执行机制原理解析

在介绍Checkpoint的执行机制前&#xff0c;我们需要了解一下state的存储&#xff0c;因为state是Checkpoint进行持久化备份的主要角色。Checkpoint作为Flink最基础也是最关键的容错机制&#xff0c;Checkpoint快照机制很好地保证了Flink应用从异常状态恢复后的数据准确性。同时…

解决xcode15下载模拟器慢以及没有断点续传

问题描述&#xff1a;Xcode15 为了最小化安装包大小&#xff0c;iOS17模拟器需要单独安装。然而下载模拟器的时候&#xff0c;经常出现Could not download iOS... 的下载失败提示。 以下为解决方案&#xff1a; 一、直接下载IOS17模拟器的包 以下两种方式都可以 方法一&…

centos7安装nginx并安装部署前端

目录&#xff1a; 一、安装nginx第一种方式&#xff08;外网&#xff09;第二种方式&#xff08;内网&#xff09; 二、配置前端项目三、Nginx相关命令 好久不用再次使用生疏&#xff0c;这次记录一下 一、安装nginx 第一种方式&#xff08;外网&#xff09; 1、下载nginx ng…

【教学类-42-03】20231225 X-Y 之间加法题判断题3.0(确保错误题有绝对错误的答案)

背景需求&#xff1a; 根据需求&#xff0c;0-5以内的判断是21题正确&#xff0c;21题错误&#xff0c;但由于错误答案是随机数抽取&#xff0c;有可能恰好是正确的&#xff0c;所以会出现每套题目的正确数和错误数不一样的情况 优化思路一&#xff1a; 设置如果错误答案与正…

ubuntu22.04 将python源切换为清华源

在 Ubuntu 22.04 中将 Python 包管理器 pip 的源切换到清华大学镜像源可以加快包的下载速度&#xff0c;特别是在中国大陆地区。以下是详细的步骤&#xff1a; 方法 1: 临时使用清华源 在使用 pip 安装包时&#xff0c;您可以通过 --index-url 参数临时指定清华源。例如&…

Qt 中使用 MySQL 数据库保姆级教程(上)

作者&#xff1a;billy 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 前言 在 Qt 中默认只搭载了 QSqlLite 数据库驱动&#xff0c;若要使用其他数据库需要自己下载数据库&#xff0c;并将数据库驱动加载到…