嵌入式驱动开发详解4(内核定时器)
文章目录
- 前言
- 通用定时器
- 系统节拍
- 节拍数与时间转换
- 基本框架
- 定时器使用
- 代码展示
- 通用定时器特点
- 高精度定时器
前言
LInux内核定时器是一种基于未来时间点的计时方式,以当前时刻来启动的时间点,以未来的某一时刻为终止点。比如,现在是10点5分,我要定时5分钟,那么定时就是10点5分+5分钟=10点10分。这个和手机闹钟很相似。比如你要定一个第二天早晨8点的闹钟,就是当前时间定时到第二天早晨8点。
通用定时器
系统节拍
在了解定时器之前,先来了解一下内核系统节拍的设置。硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后 就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率, 也叫做节拍率(tick rate)(有的资料也叫系统频率),比如 1000Hz,100Hz 等等说的就是系统节拍 率。系统节拍率是可以设置的,单位是 Hz,我们在编译 Linux 内核的时候可以通过图形化界面 设置系统节拍率,按照如下路径打开配置界面:
-> Kernel Features -> Timer frequency ( [=y])

默认情况下选择 100Hz。设置好以后打开 Linux 内核源码根目录下的.config 文件中CONFIG_HZ会等于设置好的值,Linux 内核会使用 CONFIG_HZ 来设置自己的系统时 钟。打开文件 include/asm-generic/param.h,有如下内容:
# undef HZ
# define HZ CONFIG_HZ
# define USER_HZ 100
# define CLOCKS_PER_SEC (USER_HZ)
这里需要注意的是,并不是节拍率越高越好,高节拍率会提高系统时间精度,但是高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担。
Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会 将 jiffies 初始化为 0,jiffies 定义在文件 include/linux/jiffies.h 中,如下图所示,jiffies_64 和 jiffies 其实是同一个东西,jiffies_64 用于 64 位系统,而 jiffies 用于 32 位系统。

节拍数与时间转换
为了方便开发,Linux 内核提供了几个 jiffies 和 ms、us、ns 之间的转换函数,如表所示:

有时候我们需要在内核中实现短延时,尤其是在 Linux 驱动中。Linux 内核提供了毫秒、微 秒和纳秒延时函数:

基本框架
Linux内核使用timer_list结构体表示内核定时器,timer_list定义在文件include/linux/timer.h中,定义如下:
struct timer_list {/** All fields that change during normal runtime grouped to the* same cacheline*/struct hlist_node entry; unsigned long expires; /*定时器超时时间,单位节拍数*/void (*function)(unsigned long);/*定时处理函数*/unsigned long data;/*要传递function函数的参数*/u32 flags;
#ifdef CONFIG_TIMER_STATSint start_pid;void *start_site;char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};
在上面这个结构体中,有几个参数需要重点关注一下。一个是expires到期时间,单位是节拍数。等于定时的当前的始终节拍计数(存储在系统的全局变量和jiffies)+定时时长对应的时钟节拍数量。
那么如何把时间 转换成节拍数量呢?示例:假如从现在开始定时1秒,转换成节拍数量是多少呢? 内核中有一个宏HZ,表示一秒对应的时钟节拍数,那么我们就可以通过这个宏来把时间转换成节拍数。所以,定时1秒就是expires = jiffies + 1*HZ。前面已经说明了HZ是怎么设置的。
定时器使用
- 当我们定义了一个 timer_list 变量以后一定 要先用 init_timer 初始化一下
void init_timer(struct timer_list *timer)
- add_timer 函数用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后, 定时器就会开始运行
void add_timer(struct timer_list *timer)
- del_timer 函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。
int del_timer(struct timer_list * timer)
- del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,
int del_timer_sync(struct timer_list *timer)
- mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器!
int mod_timer(struct timer_list *timer, unsigned long expires)
其中timer:要修改超时时间(定时值)的定时器。 expires:修改后的超时时间。
代码展示
下面这个代码实现了内核定时器周期性的点亮和熄灭开发板上的 LED 灯,当CMD = 3 是LED 灯的闪烁周期可以由内核定时器来设置,这里用到了ioctrl的相关知识,linux内核给用户提供了两类系统调用函数:一类是数据操作函数,比如read、write…。 另外一类函数是非数据操作函数,比如ioctl…,用户程序可以用ioctl给底层设备发送指令。 如果不是很熟悉这一块内容的话可以参考我的下一篇帖子:嵌入式驱动开发详解5(ioctl的使用)
应用层代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include "string.h"
#include <stdlib.h>
#include <sys/ioctl.h>#define CLOSE_CMD _IO(0XEF, 0X1) /* 关闭定时器 */
#define OPEN_CMD _IO(0XEF, 0X2) /* 打开定时器 */
#define SETPERIOD_CMD _IO(0XEF, 0X3) /* 设置定时器周期命令 */int main(int argc,char *argv[])
{int fd,ret;char *filename;unsigned int cmd;unsigned int arg;unsigned char str[100];if(argc != 2){printf("Error Usage!!!\r\n");return -1;}filename = argv[1];fd = open(filename ,O_RDWR);if(fd < 0){printf("file %s open failed!\r\n",filename);return -1;}while(1){printf("Input CMD:");ret = scanf("%d",&cmd);if(ret != 1){ /* 参数输入错误 */fgets(str,100,stdin); /* 防止卡死 */}if(cmd == 1)cmd = CLOSE_CMD;else if(cmd == 2)cmd = OPEN_CMD;else if(cmd == 3){cmd = SETPERIOD_CMD;printf("Input Timer Period:");ret = scanf("%d",&arg);if(ret != 1){fgets(str,100,stdin); }}ioctl(fd,cmd,arg);}ret = close(fd);if(ret < 0){printf("file %s close failed! \r\n",filename);return -1;}return 0;
}
内核层代码如下:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h> //copy
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h> //淇″彿閲� 浜掞拷锟戒綋
#include <linux/timer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define TIMER_CNT 1
#define TIMER_NAME "timer"
#define CLOSE_CMD _IO(0XEF, 0X1) /* 鍏抽棴瀹氭椂鍣� */
#define OPEN_CMD _IO(0XEF, 0X2) /* 鎵撳紑瀹氭椂鍣� */
#define SETPERIOD_CMD _IO(0XEF, 0X3) /* 璁剧疆瀹氭椂鍣ㄥ懆鏈熷懡浠� */ //铏界劧姝ゅ娌℃湁鍐檃rg锛屼絾鏄渶鍚庡鏋滄湁arg鍙傛暟杩樻槸鍙互浼犺繘鏉�#define LED_ON 1
#define LED_OFF 0struct timer_dev{dev_t devid; int major;int minor;struct cdev cdev;struct class *class;struct device *device;struct device_node *nd;int led_gpio;int timeperiod; struct timer_list timer; spinlock_t lock;
};struct timer_dev timerdev;static int led_init(void)
{int ret;timerdev.nd = of_find_node_by_path("/gpioled");if(timerdev.nd == NULL){printk("timerdev node cant not find!!\r\n");ret = -1;goto fail_node;}else{printk("timerdev node found!!\r\n");}timerdev.led_gpio = of_get_named_gpio(timerdev.nd,"led-gpio",0);if(timerdev.led_gpio < 0){printk("cant not get led-gpio\r\n");ret = -1;goto fail_node;}printk("led-gpio-num=%d\r\n",timerdev.led_gpio);gpio_request(timerdev.led_gpio,"led");ret = gpio_direction_output(timerdev.led_gpio,1);if(ret < 0){printk("can`t set gpio!!!\r\n");}return 0;
fail_node:return ret;}static int timer_open(struct inode *inode, struct file *file)
{int ret = 0;file->private_data = &timerdev;timerdev.timeperiod = 1000;ret = led_init();if(ret < 0){return ret;}return 0;
}static long timer_unlocked_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{unsigned long flags;int timerperiod;struct timer_dev *dev = filep->private_data;switch (cmd) {case CLOSE_CMD:del_timer_sync(&dev->timer);break;case OPEN_CMD:spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;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->timeperiod = arg;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer,jiffies+msecs_to_jiffies(dev->timeperiod));break;default:break;}return 0;
}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->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer,jiffies+msecs_to_jiffies(timerperiod));
}static const struct file_operations timer_fops = {.owner = THIS_MODULE,.open = timer_open,.unlocked_ioctl = timer_unlocked_ioctl,
};static int __init timer_init(void)
{int ret;/* 鍒濆鍖栧師瀛愬彉閲� */spin_lock_init(&timerdev.lock);if(timerdev.major){timerdev.devid = MKDEV(timerdev.major,timerdev.minor);ret = register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);}else{ret = alloc_chrdev_region(&timerdev.devid,0,TIMER_CNT,TIMER_NAME);timerdev.major = MAJOR(timerdev.devid);timerdev.minor = MINOR(timerdev.devid);printk("alloc_chrdev_region major=%d minor=%d\r\n",timerdev.major, timerdev.minor);}if (ret < 0) {printk("Could not register\r\n");goto fail_devid;}timerdev.cdev.owner = THIS_MODULE;cdev_init(&timerdev.cdev, &timer_fops);ret = cdev_add(&timerdev.cdev,timerdev.devid,TIMER_CNT);if(ret < 0){printk("Could not cdev\r\n");goto fail_cdev;}timerdev.class = class_create(THIS_MODULE,TIMER_NAME);if(IS_ERR(timerdev.class)){ret = PTR_ERR(timerdev.class);goto fail_class;}timerdev.device = device_create(timerdev.class,NULL,timerdev.devid,NULL,TIMER_NAME);if(IS_ERR(timerdev.device)){ret = PTR_ERR(timerdev.device);goto fail_device;}//鍒濆鍖杢imer锛岃缃畾鏃跺櫒澶勭悊鍑芥暟锛岃繕鏈缃懆鏈燂紝鎵€浠ヤ笉浼氭縺娲诲畾鏃跺櫒init_timer(&timerdev.timer);timerdev.timer.function = timer_function;timerdev.timer.data = (unsigned long)&timerdev; //璁剧疆瑕佷紶閫掔粰 timer_function 鍑芥暟鐨勫弬鏁颁负 timerdev 鐨勫湴鍧€return 0;
fail_device:class_destroy(timerdev.class);
fail_class:cdev_del(&timerdev.cdev);
fail_cdev:unregister_chrdev_region(timerdev.devid,TIMER_CNT);
fail_devid:return ret;
}static void __exit timer_exit(void)
{gpio_set_value(timerdev.led_gpio,1);del_timer_sync(&timerdev.timer);printk("timer_exit\r\n");cdev_del(&timerdev.cdev);unregister_chrdev_region(timerdev.devid,TIMER_CNT);device_destroy(timerdev.class,timerdev.devid);class_destroy(timerdev.class);
}module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hbb");
通用定时器特点
内核定时器定时精度不高,不能作为高精度定时器使用。并且内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。
高精度定时器
Linux内核提供了高精度定时器,能够实现纳秒(ns)级别的定时精度。这主要得益于内核中的hrtimer框架,它允许开发者创建和运行高精度的定时器。
关于详细的hrtimer结构体的实现可以参考下面这篇文章linux内核定时器
到这里对通用定时器大致说明就结束了,后面有新的相关的重要的内容会继续进行更新。
对代码有兴趣的同学可以查看链接https://github.com/NUAATRY/imx6ull_dev
相关文章:
嵌入式驱动开发详解4(内核定时器)
文章目录 前言通用定时器系统节拍节拍数与时间转换基本框架定时器使用代码展示通用定时器特点 高精度定时器 前言 LInux内核定时器是一种基于未来时间点的计时方式,以当前时刻来启动的时间点,以未来的某一时刻为终止点。比如,现在是10点5分&…...
Linux:信号的预备和产生
引入: 比如当前快递小哥需要通知你下来取快递(产生信号),然后通过电话或短信告知了你(发送信号),但是当前你正在打游戏,所以你并不会马上去处理,但是你会记得这件事&…...
国城杯2024——Curve
相关知识链接:https://tangcuxiaojikuai.xyz/post/187210a7.html #sagemath from Crypto.Util.number import *def add(P, Q):(x1, y1) P(x2, y2) Qx3 (x1*y2 y1*x2) * inverse(1 d*x1*x2*y1*y2, p) % py3 (y1*y2 - a*x1*x2) * inverse(1 - d*x1*x2*y1*y2, p…...
AI生成不了复杂前端页面?也许有解决方案了
在2024年,编程成为了人工智能领域最热门的赛道。AI编程技术正以惊人的速度进步,但在生成前端页面方面,AI的能力还是饱受质疑。自从ScriptEcho平台上线以来,我们收到了不少用户的反馈,他们表示:“生成的页面…...
常见矩阵分析法(BCG、GE、IE、SPACE、TOWS、优先、战略优先级、安索夫、风险矩阵):如何通过系统化方法助力战略决策与数据驱动决策
在快速变化的商业环境中,企业决策者面临着诸多复杂的选择与挑战。矩阵分析法作为战略分析的重要工具,能够系统化地分析企业的内外部环境,帮助管理层做出更加科学、合理的决策。本文将全面解析常见的矩阵分析法,并探讨它们在数据驱…...
JWT 在 SaaS 系统中的作用与分布式 SaaS 系统设计的最佳实践
在现代 SaaS(软件即服务) 系统中,随着服务规模的扩大和用户需求的多样化,如何高效、安全地进行用户身份验证、权限控制以及租户隔离,成为了系统架构中的核心问题之一。**JWT(JSON Web Token)**作…...
基于C#和Sql Server的网上书店管理系统
基于C#和Sql Server的网上书店管理系统 摘要 本系统是建立在 Windows 平台上,基于 B/S 结构的一个网上书店。通过这个网上书店,可以实 现简单的电子商务功能。 整个网站风格一致,较为美观,有完善的导航机制。普通用户从前台首页…...
特高频局放装置在现代配电设施中的应用
引言 随着电力系统的快速发展,尤其是现代配电系统的不断升和智能化,配电网的安全、稳定和运行变得愈发重要。为了确保电力系统能够及时应对各种运行问题,并提高故障诊断和监控的能力,现代配电系统中的监测技术也不断得到创新与提…...
FSC认证是什么?FSC认证费用
FSC认证是指森林管理委员会(Forest Stewardship Council)颁发的一种认证,以下是对FSC认证的详细介绍: 一、FSC认证的定义与目的 FSC认证标志着一件产品来自经过环境友好、社会有益和经济可行的可持续管理的森林。FSC是一个独立的…...
JAVA数据结构
1.数组 (Array): 固定大小的容器,用于存储相同类型的元素,数组在内存中是连续存储的,支持通过索引快 速访问元素。 int[] numbers = new int[10]; numbers[0] = 1;2.Java Collections Framework (JCF) JCF提供了一组接口和类用于管理和操作集合(如列表,集合,…...
mysql8 主从复制一直失败
问题描述: 开启同步后从服务器一直失败,报错如下: Last_SQL_Error: Coordinator stopped because there were error(s) in the worker(s). The most recent failure being: Worker 1 failed executing transaction ANONYMOUS at source log …...
EDA - Spring Boot构建基于事件驱动的消息系统
文章目录 概述事件驱动架构的基本概念工程结构Code创建事件和事件处理器创建事件总线创建消息通道和发送逻辑创建事件处理器消息持久化创建消息发送事件配置 Spring Boot 启动类测试运行项目 概述 在微服务架构和大规模分布式系统中,事件驱动架构(EDA&a…...
使用vue-seamless-scroll实现echarts图表大屏滚动,出现空白间隔的解决方案
一、背景介绍 最近的业务开发需求,想要实现echarts图表大屏滚动,小编首先采用vue-seamless-scroll进行实现,结果发现第二屏出现空白间隔,尝试了多种解决方案均不生效,最终选择换一个方案。 二、封装的ScrollList组件…...
ios使用UIScrollView和PageControl创建图片轮播
1.创建cocoa touch class 2.同时创建xib页面 3.SceneDelegate设置根视图控制器 // // SceneDelegate.m // iosstudy2024 // // Created by figo on 2024/8/5. //#import "SceneDelegate.h" #import "WidgetViewController.h"interface SceneDelegate …...
3D 生成重建024-LGM第一个开源的3D生成大模型!
3D 生成重建024-LGM第一个开源的3D生成大模型 文章目录 0 论文工作1 论文方法2 实验效果 0 论文工作 这篇论文介绍了一种名为LGM(大型多视角高斯模型)的新方法,用于从单视角图像或文本提示生成高分辨率的三维内容。该方法的核心思想是双重的…...
linux目录权限
一、目录权限的基本概念 Linux中的每个文件和目录都有与之关联的权限,这些权限决定了谁可以读取、写入或执行它们。权限分为三组: 所有者(Owner)权限:目录所有者的权限群组(Group)权限&#x…...
语言模型使用心得
使用像文心一言这样的语言模型,在撰写文章时确实能提供极大的帮助。然而,重要的是我们要明确主次关系:自己的创意和内容应当是文章的核心,而语言模型则扮演着一个辅助角色,帮助我们梳理思路,使文章条理更加…...
ChatGPT客户端安装教程(附下载链接)
用惯了各类AI的我们发现每天打开网页还挺不习惯和麻烦,突然发现客户端上架了,懂摸鱼的人都知道这里面的道行有多深,话不多说,开整! 以下是ChatGPT客户端的详细安装教程,适用于Windows和Mac系统:…...
Electron 基础+传值+引用+安全
文章目录 概要elctron 生命周期及窗口应用主进程与渲染进程交互技术细节electron 中需要注意的安全问题 概要 一、Electron简介 Electron是一个开源框架,它允许开发者使用JavaScript、HTML和CSS构建跨平台的桌面应用程序。它基于Chromium(谷歌浏览器的…...
手机租赁系统全面解析与开发指南
内容概要 手机租赁系统已经成为现代商业中不可或缺的一部分,尤其是在智能手机普及的时代。随着消费者对新机型兴趣的不断增加,大家纷纷走上了“试一试再买”的道路,手机租赁这条路因此越走越宽。这部分的市场需求让创业者们看到了机会。不仅…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
【iOS】 Block再学习
iOS Block再学习 文章目录 iOS Block再学习前言Block的三种类型__ NSGlobalBlock____ NSMallocBlock____ NSStackBlock__小结 Block底层分析Block的结构捕获自由变量捕获全局(静态)变量捕获静态变量__block修饰符forwarding指针 Block的copy时机block作为函数返回值将block赋给…...
Canal环境搭建并实现和ES数据同步
作者:田超凡 日期:2025年6月7日 Canal安装,启动端口11111、8082: 安装canal-deployer服务端: https://github.com/alibaba/canal/releases/1.1.7/canal.deployer-1.1.7.tar.gz cd /opt/homebrew/etc mkdir canal…...
大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程
基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...
CppCon 2015 学习:REFLECTION TECHNIQUES IN C++
关于 Reflection(反射) 这个概念,总结一下: Reflection(反射)是什么? 反射是对类型的自我检查能力(Introspection) 可以查看类的成员变量、成员函数等信息。反射允许枚…...
