嵌入式驱动学习第一周——定时器与延时函数

前言

   这篇博客一起学习定时器,定时器是最常用到的功能之一,其最大的作用之一就是提供了延时函数。

   嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!

行文目录

  • 前言
  • 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);

   unknownjiffiesknown需要对比的值,如果unknown超过knowntime_after返回真,否则返回假;如果unknown没有超过knowntime_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 长延时

   利用jiffiestime_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内核延时机制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/708747.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

刷题第3天(基础理论):链表基础理论

1.链表定义:链表是一种通过指针串联在一起的线性结构。每个节点由两部分组成,一个是数据域,一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思) …

cRIO9040中NI9871模块的测试

硬件准备 CompactRIO9040NI9871直流电源(可调)网线RJ50转DB9线鸣志STF03-R驱动器和步进电机 软件安装 参考:cRIO9040中NI9381模块的测试 此外,需安装NI-Serial 9870和9871扫描引擎支持 打开NI Measurement&Automa…

Docke相关命令总结

docker systemctl 相关 commanddetailsudo systemctl start docker启动dockersudo systemctl stop docker停止dockersudo systemctl restart docker重启dockersudo systemctl status docker查看docker状态 镜像相关 commanddetaildocker search 镜像名称搜索镜像docker pull …

多线程爬虫基础代码

#导入线程模块 import threading def coding(): #定义 coding 函数,用于打印字符串 "aaa" 十次for i in range(10):print("aaa")def ac(): #定义 ac 函数,用于打印字符串 "bbbb" 十次&a…

jetson nano——编译安装opencv-python==4.3.0.38

目录 1.下载源码,我提供的链接如下:2.解压文件3.安装依赖scikit4.安装opencv-python5.查看opencv-python版本 系统:jetson-nano-jp451-sd-card-image ubuntu 18.04 1.下载源码,我提供的链接如下: 链接:http…

网络:IPv6

1、由于IPv4地址资源枯竭,所以产生了IPV6。 版本长度地址数量IPv432 bit4 294 967 296IPv6128 bit340 282 366 920 938 463 374 607 431 768 211 456 2、IPv6的基本报头在IPv4报头基础上,增加了流标签域,去除了一些冗余字段,使报…

RabbitMQ常用命令笔记

Ubuntu 安装 sudo apt install rabbitmq-server查看状态 sudo rabbitmqctl status启动可视化插件 sudo rabbitmq-plugins enable rabbitmq_management查看可视化端口 sudo rabbitmqctl status添加用户名密码 sudo rabbitmqctl add_user 用户名 密码设置管理员权限 sudo r…

docker (十二)-私有仓库

docker registry 我们可以使用docker push将自己的image推送到docker hub中进行共享,但是在实际工作中,很多公司的代码不能上传到公开的仓库中,因此我们可以创建自己的镜像仓库。 docker 官网提供了一个docker registry的私有仓库项目&#…

Zookeeper基础入门-2【ZooKeeper 分布式锁案例】

Zookeeper基础入门-2【ZooKeeper 分布式锁案例】 四、ZooKeeper-IDEA环境搭建4.1.环境搭建4.1.1.创建maven工程:zookeeper4.1.2.在pom文件添加依赖4.1.3.在项目的src/main/resources 目录下,新建文件为“log4j.properties”4.1.4.创建包名com.orange.zk …

分布式概念:写一个分布式锁

分布式锁是一种用于解决分布式系统中资源并发访问的问题的机制。它可以保证在分布式环境中,同一时刻只有一个线程或进程可以访问某个共享资源,从而避免了竞态条件的发生。 以下是一个简单的分布式锁的实现示例: 使用一个共享的分布式存储系统…

Neoverse S3 系统 IP:机密计算和多芯片基础设施 SoC 的基础

第三代Neoverse系统IP Neoverse S3 产品推出了我们的第三代基础设施特定系统 IP,这是下一代基础设施 SOC 的理想基础,适用于从 HPC 和机器学习到 Edge 和 DPU 的各种应用。S3 机箱专注于为我们的合作伙伴提供 Chiplet、机密计算等关键创新以及 UCIe、DD…

(Linux学习一):Mac安装vmWare11.5,centOS 7安装步骤教程

一。下载vmware 官网地址:下载地址 由于我的电脑系统是Mac 10.15.6版本系统,我下载的是VMware Fusion 11.5版本,13是最新版本不支持安装需要系统在11以上。 百度网盘下载地址: VMware Fusion 11 VMware Fusion 12 VMware Fusion 13 下载需要…

matlab实现不同窗滤波器示例

1 汉明窗低通滤波器 : 在Matlab中使用汉明窗设计低通滤波器可以通过fir1函数实现。汉明窗通常用于设计滤波器,可以提供更突出的频率特性。 下面是一个示例代码,演示如何在Matlab中使用汉明窗设计低通滤波器: % 定义滤波器参数 fs …

揭秘数字证书:保护你的数据不止于表面

数字证书,这个看似枯燥无味的电子文件,其实背后隐藏着一套精密的运行机制。今天陕西CA就来给大家揭开它的神秘面纱。 首先,数字证书是由权威的第三方机构颁发的,这些机构通常被称为证书颁发机构(CA)&#…

python web框架fastapi模板渲染--Jinja2使用技巧总结

文章目录 1.jinja2模板1.1、jinja2 的变量1.1.1 列表类型数据渲染1.1.2 字典类型数据渲染 2. jinja2 的过滤器3. jinja2 的控制结构3.1、分支控制3.2、循环控制 1.jinja2模板 要了解jinja2,那么需要先理解模板的概念。模板在Python的web开发中⼴泛使⽤,…

双硬盘备份的一种可行方案

双硬盘备份有什么优势弊端? 事物总有两面性,那么对于双硬盘数据备份任务来说,有什么优势与弊端呢? ◉ 双硬盘备份的优势: 安全性更好:由于数据备份到两个不同的硬盘,所以可以保证备份数据的冗…

基于springboot实现图书馆管理系统项目【项目源码+论文说明】

基于springboot实现图书馆管理系统演示 摘要 电脑的出现是一个时代的进步,不仅仅帮助人们解决了一些数学上的难题,如今电脑的出现,更加方便了人们在工作和生活中对于一些事物的处理。应用的越来越广泛,通过互联网我们可以更方便地…

练习 1 Web EasySQL极客大挑战

CTF Week 1 EasySQL极客大挑战 BUUCTF 典中典复习 Web SQL 先尝试输入,找一找交互页面 check.php 尝试万能语句 a’ or true SQL注入:#和–的作用 get传参只能是url编码,注意修改编码,输入的字符串要改成url格式。 POST请求和…

Linux:Kubernetes(k8s)——基础理论笔记(1)

我笔记来源的图片以及共享至GitHub,本章纯理论。这是k8s中部分的基础理论 👇 KALItarro/k8spdf: 这个里面只有一个pdf文件 (github.com)https://github.com/KALItarro/k8spdf👆 什么是kubernetes kubernetes 是一个开源的,用于管…

Unity(第十一部)场景

游戏有多个场景组成(新手村,某某副本,主城) 场景是有多个物体组成(怪物,地形,玩家等) 物体是有多个组件组成(刚体组件,自定义脚本) 创建场景 编辑…