嵌入式驱动学习第一周——定时器与延时函数
前言
这篇博客一起学习定时器,定时器是最常用到的功能之一,其最大的作用之一就是提供了延时函数。
嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!
行文目录
- 前言
- 1. Linux内核定时器介绍
- 1.1 定时器介绍
- 1.2 超时时间计算
- 2. 内核定时器使用
- 2.1 内核定时器的API函数
- 2.2 内核定时器的使用过程
- 2.2 内核定时器的使用案例
- 3. 内核的延迟机制
- 3.1 对比jiffies的函数
- 3.2 忙等延时
- 3.2.1 短延时
- 3.2.2 长延时
- 3.3 睡眠延时
- 3.3.1 sleep类延时函数
- 3.3.2 schedule类延时函数
- 3.3.3 sleep_on类延时函数
- 参考资料
1. Linux内核定时器介绍
1.1 定时器介绍
Linux内核定时器采用系统时钟,而非像单片机中使用PIT等硬件定时器。其使用只需要提供超时时间与定时处理函数即可,当超时时间到了以后设置的定时函数就会执行。
不同于之前的单片机中的定时器,内核定时器并非周期性运行的,而是超时后会关闭,因此想周期性实现定时的话,就需要在定时处理函数中重新开启定时器。
1.2 超时时间计算
Linux内核使用了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;
};
使用内核定时器需要先定义一个timer_list变量。
其中的expires成员变量表示超时时间,单位为节拍数。假设现在需要一个周期为2s的定时器,那么定时器的超时时间为jiffies+(2*Hz),因此expires就为该值。其中jiffies是系统运行的节拍数,jiffies/Hz即系统运行时间,单位为s。
结构体中的function为定时器超时后的定时处理函数。
2. 内核定时器使用
2.1 内核定时器的API函数
内核定时器的使用最关键的就是设置超时时间和定时处理函数,剩下步骤和其他一样,都需要初始化与删除等操作,具体的API如下所示:
/** @description: 初始化timer_list类型变量* @param-timer: 要初始化的定时器* @return : 无*/
void init_timer(struct timer_list *timer);
/** @description: 向Linux内核注册定时器,注册完后定时器就会开始运行* @param-timer: 要注册的定时器* @return : 无*/
void add_timer(struct timer_list *timer);
不管定时器有没有被激活,都可以用del_timer()函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用此函数之前要先等待其他处理器的定时处理器函数退出。因此可以使用其同步版——del_timer_sync()
/** @description: 删除一个定时器* @param-timer: 要删除的定时器* @return : 无*/
int del_timer(struct timer_list *timer);
/** @description: del_timer函数的同步版,会等其他处理器使用完定时器再删除* @param-timer: 要删除的定时器* @return : 0, 定时器还没被激活;1, 定时器已经激活*/
int del_timer_sync(struct timer_list *timer);
/** @description : 用于修改定时值,如果定时器还没有激活的话,mod_timer会激活定时器* @param-timer : 要修改超时时间的定时器* @param-expires: 修改后的超时时间* @return : 0,调用mod_timer 函数前定时器未激活;1,调用前定时器已被激活*/
int mod_timer(struct timer_list *timer, unsigned long expires);
2.2 内核定时器的使用过程
内核定时器的一般使用流程如下所示:
struct timer_list timer;void function(unsigned long arg)
{// 定时器处理代码// 如果要周期性运行就用mod_timermod_timer(&dev->timertest, jiffies+msecs_to_jiffies(2000));
}void init(void)
{init_timer(&timer);timer.function = function;timer.expires = jffies + msecs_to_jiffies(2000);timer.data = (unsigned long)&dev;add_timer(&timer);
}void exit(void)
{del_timer(&timer); // 删除定时器del_timer_sync(&timer); // 同步版本
}
2.2 内核定时器的使用案例
先在设备结构体中加入定时器变量和自旋锁,自旋锁用来保护超时时间。
struct timer_dev {dev_t devid;struct cdev cdev;struct class *class;struct device *device;int major;int minor;struct device_node *nd;int led_gpio;int timerperiod; // 定时周期,单位为msstruct timer_list timer; // 定时器spinlock_t lock; // 自旋锁,保护超时时间
};
编写函数timer_unlocked_ioctl(),对应应用程序的ioctl函数,应用程序调用ioctl函数向驱动发送控制信号,次函数相应并执行
static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct timer_dev *dev = (struct timer_dev *)filp->private_data;int timerperiod;unsigned long flags;switch (cmd){// 关闭定时器case CLOSE_CMD: del_timer_sync(&dev->timer);break;// 打开定时器case OPEN_CMD:spin_lock_irqsave(&dev->lock, flags); // 加锁保护超时时间timerperiod = dev->timerperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod)); // 设置定时器break;// 设置定时器周期case SETPERIOD_CMD:spin_lock_irqsave(&dev->lock, flags); // 加锁保护超时时间dev->timerperiod = arg;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));break;default:break;}return 0;
}static struct file_operations timer_fops = {.owner = THIS_MODULE,.open = timer_open,.unlocked_ioctl = timer_unlocked_ioctl,
};
定时器的超时函数,在最后重新设置超时时间,实现定时器的周期性。
void timer_function(unsigned long arg)
{struct timer_dev *dev = (struct timer_dev *)arg;static int sta = 1;int timerperiod;unsigned long flags;sta = !sta;gpio_set_value(dev->led_gpio, sta);spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timerperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timerperiod));
}
最后在__init()函数中初始化定时器并设置超时函数
static int __init timer_init(void)
{// 初始化自旋锁spin_lock_init(&timerdev.lock);// 驱动代码// 初始化定时器并设置超时函数init_timer(&timerdev.timer);timerdev.timer.function = timer_function;timerdev.timer.data = (unsigned long)&timerdev;return 0;
}
3. 内核的延迟机制
内核中涉及的延时主要有两种实现方式:忙等待或者睡眠等待。前者阻塞程序,在延时时间到达前一直占用CPU;后者则是将进程挂起(置进程于睡眠态并释放CPU资源)。所以前者一般用在毫秒以内的精确延时,后者用于延时时间在毫秒以上的长延时。
3.1 对比jiffies的函数
linux内核中提供了以下几个函数用来对比jiffies和设置的值之间是否相等。
time_after(unkown, known);
time_before(unkown, known);
time_after_eq(unkown, known);
time_before_eq(unkown, known);
unknown为jiffies,known为需要对比的值,如果unknown超过known,time_after返回真,否则返回假;如果unknown没有超过known,time_before返回真,否则返回假。后面的time_after_eq与time_before_eq类似,只是增加了相等的判断。
3.2 忙等延时
3.2.1 短延时
Linux内核提供了毫秒,微秒和纳秒延时函数。这些实现方式均是忙等待短延时。
void ndelay(unsigned long nsecs); // 纳秒
void udelay(unsigned long usecs); // 微秒
void mdelay(unsigned long msecs); // 毫秒
其本质类似于以下代码:
void delay(unsigned int time)
{ while (time--);
}
3.2.2 长延时
利用jiffies和time_before()实现延时100个jiffies和2s
/*延迟 100 个 jiffies*/ unsigned long delay = jiffies + 100; while (time_before(jiffies, delay)); /*再延迟 2s*/ unsigned long delay = jiffies + 2*HZ; while (time_before(jiffies, delay));
3.3 睡眠延时
3.3.1 sleep类延时函数
下述函数将使得调用它的进程睡眠参数指定的时间,受系统 HZ 和进程调度的影响,msleep()类似函数的精度是有限的。msleep()、ssleep()不能被打断,而msleep_interruptible()则可以被打断。
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);
3.3.2 schedule类延时函数
schedule_timeout()可以使当前任务睡眠指定的jiffies 之后重新被调度执行,它的实现原理是向系统添加一个定时器,在定时器处理函数中唤醒参数对应的进程。上一小节的sleep类函数的底层实现也是调用它实现的:
signed long schedule_timeout_interruptible(signed long timeout);
signed long schedule_timeout_uninterruptible(signed long timeout)
3.3.3 sleep_on类延时函数
函数可以将当前进程添加到等待队列中,从而在等待队列上睡眠。当超时发生时,进程将被唤醒(后者可以在超时前被打断):
sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);
interruptible_sleep_on_timeout(wait_queue_head_t*q, unsigned long timeout);
参考资料
[1] 【正点原子】I.MX6U嵌入式Linux驱区动开发指南 第五十章
[2] Linux内核延时机制
相关文章:
嵌入式驱动学习第一周——定时器与延时函数
前言 这篇博客一起学习定时器,定时器是最常用到的功能之一,其最大的作用之一就是提供了延时函数。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏&…...
Tips杂记
🥲 🥸 🤌 🫀 🫁 🥷 🐻❄️🦤 🪶 🦭 🪲 🪳 🪰 🪱 🪴 🫐 🫒 🫑…...
可以用numpy为for加速
Numpy除了用于科学计算,还有一个功能是可以代替某些for循环,进行同样的功能实现,有于是向量矩阵运算,碰到复杂的for时,计算速度可以提高,从而提高程序性能。以下是一些常用的NumPy函数和操作,可…...
cartographer ceres后端优化
这里引用一篇文章 https://zhuanlan.zhihu.com/p/567635409 因为cartographer中的代码有的地方添加了AddParameterBlock,有的地方没有添加,会引起歧义,原来AddParameterBlock可以隐式添加优化变量,这篇文章介绍了具体原因,核心内容如下: AddParameterBlock的作用作用一:…...
day57 集合 List Set Map
List实现类 List接口特点:元素有序 可重复 Arraylist 可变数组 jdk 8 以前Arraylist容量初始值10 jdk8 之后初始值为0,添加数据时,容量为10; ArrayList与Vector的区别? LinkList:双向链表 优点࿱…...
蓝桥杯:真题讲解3(C++版)附带解析
报纸页数 来自:2016年七届省赛大学C组真题(共8道题) 分析: --画出报纸长的样子,如果我们在上面多画一张报纸,那么就符合题意的5,6,11,12。 观察这张图:观察3…...
继续预训练对大语言模型的影响
翻译自文章:Investigating Continual Pretraining in Large Language Models: Insights and Implications 摘要 本文研究了大型语言模型(LLMs)中不断学习(CL)的不断发展领域,重点是制定有效和可持续的训练…...
关于空频变换的知识点
1.DCT变换: 离散余弦变换是一种将图像从空域转换到频域的技术,它可以将图像分解为频域分量。对于RGB图像,它由红色(R)、绿色(G)和蓝色(B)三个通道组成。当应用DCT变换时…...
纯css实现-让字符串在文字少时显示为居中对齐,而在文字多时显示为左对齐
纯css实现-让字符串在文字少时显示为居中对齐,而在文字多时显示为左对齐 使用flex实现 思路 容器样式(.container): Flex容器的BFC性质使得其内部的子元素(.text-box)在水平方向上能够居中,通过justify-c…...
初学HTMLCSS——盒子模型
盒子模型 盒子:页面中所有的元素(标签),都可以看做是一个 盒子,由盒子将页面中的元素包含在一个矩形区域内,通过盒子的视角更方便的进行页面布局盒子模型组成:内容区域(content&…...
吸猫毛空气净化器哪个好?推荐除猫毛好的宠物空气净化器品牌
如今,越来越多的家庭选择养宠物!虽然家里变得更加温馨,但养宠可能会带来异味和空气中的毛发增多可能会引发健康问题,这也是一个大问题。 但我不想家里到处都是异味,尤其是便便的味道,所以很需要一款能够处…...
【玩转408数据结构】线性表——双链表、循环链表和静态链表(线性表的链式表示 下)
知识回顾 在前面的学习中,我们已经了解到了链表(线性表的链式存储)的一些基本特点,并且深入的研究探讨了单链表的一些特性,我们知道,单链表在实现插入删除上,是要比顺序表方便的,但是…...
分布式概念
分布式概念 一、分布式介绍1.1 分布式计算1.1.1 分布式计算的方法1.1.1 分布式计算与互联网的普及1.1.2 分布式计算项目1.1.3 参与计算 1.2 分布式存储系统1.2.1 P2P 数据存储系统1.2.2 云存储系统 1.3 应用 二、分布式基础概念2.1 微服务2.2 集群2.3 分布式2.4 节点2.5 远程调…...
vue中的ref/reactive区别及原理
Vue中的ref和reactive是两种不同的数据响应式管理方式。 ref是Vue 3中新加入的特性,它可以将一个普通的JavaScript对象转换为响应式对象。通过ref创建的响应式对象在访问和修改时会自动触发重新渲染。ref返回的是一个包含value属性的对象,访问或修改数据…...
深度学习介绍与环境搭建
深度学习介绍与环境搭建 慕课大学人工智能学习笔记,自己学习记录用的。(赋上连接) https://www.icourse163.org/learn/ZUCC-1206146808?tid1471365447#/learn/content?typedetail&id1256424053&cid1289366515人工智能、机器学习与…...
QT C++实践|超详细数据库的连接和增删改查操作|附源码
0:前言 🪧 什么情况需要数据库? 1 大规模的数据需要处理(比如上千上万的数据量)2 需要把数据信息存储起来,无论是本地还是服务上,而不是断电后数据信息就消失了。 如果不是上面的原因化,一般…...
matlab:涉及复杂函数图像的交点求解
matlab:涉及复杂函数图像的交点求解 在MATLAB中求解两个图像的交点是一个常见的需求。本文将通过一个示例,展示如何求解两个图像的交点,并提供相应的MATLAB代码。 画出图像 首先,我们需要绘制两个图像,以便直观地看…...
Unity(第二十二部)官方的反向动力学一般使用商城的IK插件,这个用的不多
反向动力学(Inverse Kinematic,简称IK)是一种通过子节点带动父节点运动的方法。 正向动力学 在骨骼动画中,大多数动画是通过将骨架中的关节角度旋转到预定值来生成的,子关节的位置根据父关节的旋转而改变,这…...
nginx反向代理,获取客户端ip
一、获取客户端ip代码 /*** description: 获取客户端IP* return string*/ public static function getClientIp(){$ip ;if(getenv(HTTP_CLIENT_IP) && strcasecmp(getenv(HTTP_CLIENT_IP),unknown)){$ip getenv(HTTP_CLIENT_IP);}else if(getenv(HTTP_X_FORWARDED_F…...
13 Codeforces Round 886 (Div. 4)G. The Morning Star(简单容斥)
G. The Morning Star 思路:用map记录x,y,以及y-x、yx从前往后统计一遍答案即可公式 a n s c n t [ x ] c n t [ y ] − 2 ∗ c n t [ x , y ] c n t [ y x ] c n t [ y − x ] anscnt[x]cnt[y]-2 * cnt[x,y]cnt[yx]cnt[y-x] anscnt[x]…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
