09_Linux内核定时器

news2024/9/22 21:33:24

目录

Linux时间管理和内核定时器简介

内核定时器简介

Linux内核短延时函数

定时器驱动程序编写

编写测试APP

运行测试 


Linux时间管理和内核定时器简介

        学习过UCOS或FreeRTOS的同学应该知道, UCOS或FreeRTOS是需要一个硬件定时器提供系统时钟,一般使用Systick作为系统时钟源。同理,Linux要运行,也是需要一个系统时钟的,至于这个系统时钟是由哪个定时器提供的,在CortexA7内核中有个通用定时器,在《Cortex-A7 Technical ReferenceManua pdf》的“9:Generic Timer”章节有简单的讲解,关于这个通用定时器的详细内容,可以参考《ARM ArchitectureReferenceManual ARMv7-A and ARMv7-R edition.pdf》的"chapter B8 The Generic Timer"章节。这个通用定时器是可选的,按照学习FreeRTOS和STM32的经验,猜测Linux会将这个通用定时器作为Linux系统时钟源(前提是SOC得选配这个通用定时器)。这里仅仅是猜测!不过对于Linux驱动编写者来说,不需要深入研究这些具体的实现,只需要掌握相应的API函数即可,除非你是内核编写者或者内核爱好者。

        Linux内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序、对于我们驱动编写者来说最常用的定时器。硬件定时器提供时钟源,时钟源的频率可以设置,设置好以后就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率,也叫做节拍率(tick rate)(有的资料也叫系统频率),比如1000Hz, 100Hz等等说的就是系统节拍率。系统节拍率是可以设置的,单位是Hz,在编译Linux内核的时候可以通过图形化界面设置系统节拍率,按照如下路径打开配置界面:

 选中“Timer frequency”,打开以后如图所示:

 从图可以看出,可选的系统节拍率为100Hz、200Hz、250Hz、300Hz、500Hz和1000Hz,默认情况下选择100Hz。设置好以后打开Linux内核源码根目录下的.config文件,在此文件中有如图所示定义:

 图中的CONFIG_HZ为100, Linux内核会使用CONFIG_HZ来设置自己的系统时钟。打开文件include/asm-generic/param.h,有如下内容:

 第7行定义了一个宏HZ,宏HZ就是CONFIG HZ,因此HZ=100,后面编写Linux驱动的时候会常常用到 HZ,因为HZ 表示一秒的节拍数,也就是频率。

大多数初学者看到系统节拍率默认为100Hz的时候都会有疑问,怎么这么小?100Hz是可选的节拍率里面最小的。为什么不选择大一点的呢?这里就引出了一个问题:高节拍率和低节拍率的优缺点:

1.高节拍率会提高系统时间精度,如果采用100Hz的节拍率,时间精度就是10ms,采用1000Hz的话时间精度就是1ms,精度提高了10倍。高精度时钟的好处有很多,对于那些对时间要求严格的函数来说,能够以更高的精度运行,时间测量也更加准确。

2.高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担,1000Hz和100Hz的系统节拍率相比,系统要花费10倍的“精力”去处理中断。中断服务函数占用处理器的时间增加,但是现在的处理器性能都很强大,所以采用1000Hz的系统节拍率并不会增加太大的负载压力。根据自己的实际情况,选择合适的系统节拍率,这里全部采用默认的100Hz系统节拍率。

Linux内核使用全局变量jiffies来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies初始化为0,jiffies定义在文件include/linux/jiffies.h中,定义如下:

 第76行,定义了一个64位的jiffies_64。

第77行,定义了一个unsigned long类型的32位的jiffies。

jiffies_64和jiffies其实是同一个东西,jiffies_64用于64位系统,而jiffies用于32位系统。为了兼容不同的硬件, jiffies其实就是jiffies_64的低32位, jiffies_64和jiffies的结构如图所示:

当我们访问jiffies的时候其实访问的是jiffies_64的低32位,使用get_jiffies_64这个函数可以获取jiffies_64的值。在32位的系统上读取jiffies的值,在64位的系统上jiffes和jiffies_64表示同一个变量,因此也可以直接读取jiffies的值。所以不管是32位的系统还是64位系统,都可以使用jiffies。

前面说HZ表示每秒的节拍数, jiffies表示系统运行的jiffies节拍数,所以jiffies/HZ就是系统运行时间,单位为秒。不管是32位还是64位的jiffies,都有溢出的风险,溢出以后会重新从0开始计数,相当于绕回来了,因此有些资料也将这个现象也叫做绕回。假如HZ为最大值1000的时候, 32位的jiffies只需要49.7天就发生了绕回,对于64位的jiffies来说大概需要5.8 亿年才能绕回,因此jiffies_64的绕回忽略不计。处理32位jiffies的绕回显得尤为重要,Linux内核提供了如表所示的几个API函数来处理绕回。

如果unkown超过known的话, time_after函数返回真,否则返回假。如果unkown没有超过 known的话time_before函数返回真,否则返回假。time_after_eq函数和time_after函数类似,只是多了判断等于这个条件。同理,time_before_eq函数和time_before函数也类似。比如我们要判断某段代码执行时间有没有超时,此时就可以使用如下所示代码:

 timeout就是超时时间点,比如我们要判断代码执行时间是不是超过了2秒,那么超时时间点就是jiffies+(2*HZ),如果jiffies大于timeout那就表示超时了,否则就是没有超时。

第4-6行就是具体的代码段。

第9行通过函数time_before来判断jiffies是否小于timeout,如果小于的话就表示没有超时。

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

 

内核定时器简介

定时器是一个很常用的功能,需要周期性处理的工作都要用到定时器。Linux内核定时器采用系统时钟来实现,并不是在裸机的PIT等硬件定时器。Linux内核定时器使用很简单,只需要提供超时时间(相当于定时值)和定时处理函数即可,当超时时间到了以后设置的定时处理函数就会执行,和使用硬件定时器的套路一样,只是使用内核定时器不需要做一大堆的寄存器初始化工作。在使用内核定时器的时候要注意一点,内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。Linux内核使用timer_list结构体表示内核定时器, timer_list定义在文件include/linux/timer.h中,定义如下(省略掉条件编译):

 要使用内核定时器首先要先定义一个timer_list变量表示定时器,tiemr_list结构体的expires 成员变量表示超时时间,单位为节拍数。比如现在需要定义一个周期为2秒的定时器,那么这个定时器的超时时间就是jiffies+(2*HZ),因此expires-jiffies+(2*HZ)。function就是定时器超时以后的定时处理函数,要做的工作就放到这个函数里面,需要编写这个定时处理函数。

定义好定时器以后还需要通过一系列的API函数来初始化此定时器,这些函数如下:

init_timer函数

Init_timer函数负责初始化timer_list类型变量,当我们定义了一个timer_list变量以后一定要先用init_timer初始化一下。init_timer函数原型如下:

 timer:要初始化定时器。

返回值:没有返回值。

add_timer函数

add_timer函数用于向Linux内核注册定时器,使用add_timer函数向内核注册定时器以后,定时器就会开始运行,函数原型如下:

 timer:要注册的定时器。

返回值:没有返回值。

del_timer函数

del_timer函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用del_timer函数删除定时器之前要先等待其他处理器的定时处理器函数退出。del_timer函数原型如下:

 timer:要删除的定时器。

返回值: 0,定时器还没被激活; 1,定时器已经激活。

del_timer_sync函数

del_timer_sync函数是del_timer函数的同步版,会等待其他处理器使用完定时器再删除,del_timer sync不能使用在中断上下文中。del_timer_sync函数原型如下所示:

 timer:要删除的定时器。

返回值:0,定时器还没被激活:1,定时器已经激活。

mod_timer函数

mod_timer函数用于修改定时值,如果定时器还没有激活的话, mod_timer函数会激活定时器!函数原型如下:

 timer:要修改超时时间(定时值)的定时器。

expires:修改后的超时时间。

返回值: 0,调用mod_timer函数前定时器未被激活; 1,调用mod_timer函数前定时器已被激活。

 关于内核定时器常用的API函数就讲这些,内核定时器一般的使用流程如下所示:

 

Linux内核短延时函数

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

 

定时器驱动程序编写

第38~50行,定时器设备结构体,在48行定义了一个定时器成员变量timer。

第60~82行, LED灯初始化函数,从设备树中获取LED灯信息,然后初始化相应的IO

第91-102行,函数timer_open,对应应用程序的open函数,应用程序调用open函数打开/dev/timer驱动文件的时候此函数就会执行。此函数设置文件私有数据为timerdev,并且初始化定时周期默认为1秒,最后调用led_init函数初始化LED所使用的IO

第111~137行,函数 timer_unlocked_ioct,对应应用程序的ioctl函数,应用程序调用ioctl函数向驱动发送控制信息,此函数响应并执行。此函数有三个参数: filp, cmd和arg,其中filp是对应的设备文件, cmd是应用程序发送过来的命令信息, arg是应用程序发送过来的参数,在本章例程中arg参数表示定时周期。

一共有三种命令 CLOSE_CMD, OPEN_CMD和SETPERIOD_CMD,这三个命令分别为关闭定时器、打开定时器、设置定时周期。这三个命令的左右如下:

CLOSE_CMD:关闭定时器命令,调用del_timer_sync函数关闭定时器。

OPEN-CMD:打开定时器命令,调用mod_timer函数打开定时器,定时周期为timerdev的timeperiod成员变量,定时周期默认是1秒。

SETPERIOD-CMD:设置定时器周期命令,参数arg就是新的定时周期,设置timerdev的timeperiod成员变量为arg所表示定时周期指。并且使用mod_timer重新打开定时器,使定时器以新的周期运行。

第140-144行,定时器驱动操作函数集timer_fops

第147~162行,函数timer_function,定时器服务函数,此函有一个参数 arg,在本例程中arg 参数就是timerdev的地址,这样通过arg参数就可以访问到设备结构体。当定时周期到了以后此函数就会被调用。在此函数中将LED灯的状态取反,实现LED灯闪烁的效果。因为内核定时器不是循环的定时器,执行一次以后就结束了,因此在161行又调用了mod_timer函数重新开启定时器。

第169-209行,函数timer_init,驱动入口函数。

第205-207行初始化定时器,设置定时器的定时处理函数为timer_function,另外设置要传递给timer_function函数的参数为timerdev的地址。在此函数中并没有调用timer_add函数来开启定时器,因此定时器默认是关闭的,除非应用程序发送打开命令。

第216-231行,驱动出口函数

219行关闭LED,也就是卸载驱动以后LED处于熄灭状态。

第220行调用del_timer_sync函数删除定时器,也可以使用del_timer函数。

 

编写测试APP

1.运行APP以后提示我们输入要测试的命令,输入1表示关闭定时器、输入2 表示打开定时器,输入3设置定时器周期。

2.如果要设置定时器周期的话,需要让用户输入要设置的周期值,单位为毫秒。

 第22-24行,命令值。

第53~73行,while(1)循环,让用户输入要测试的命令,然后通过第72行的ioctl函数发送给驱动程序。如果是设置定时器周期命令SETPERIOD_CMD,那么ioctl函数的arg参数就是用户输入的周期值。

运行测试 

 驱动加载成功以后如下命令来测试:

 输入上述命令以后终端提示输入命令,如图所示:

输入“2”,打开定时器,此时LED灯就会以默认的1秒周期开始闪烁。在输入"3”来设置定时周期,根据提示输入要设置的周期值,如图所示: 

 输入“500”,表示设置定时器周期值为500ms,设置好以后LED灯就会以500ms为间隔,开始闪烁。最后可以通过输入“1”来关闭定时器,如果要卸载驱动的话输入如下命令即可:

 

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

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

相关文章

Vue生态及实践 - 优化实践

目录 目标 keep alive util/vue.js【vue里面常用的函数】 src/components/UKeepAlive.vue 无限加载列表优化的实现方案 src/util/throttle.js src/components/UInfiniteList.vue src/module/topic/views/UTopic.vue 献上一张ai生成图~ 目标 Keep Alive实践长列表优化…

基于pytorch的神经网络与对比学习CL的训练示例实战和代码解析

目录 对比学习原理解析构建一个对比学习模型(代码详解)导入库构建简单的神经网络构建对比学习的损失函数开始训练 完整代码 对比学习原理解析 对比学习(Contrastive Learning)是一种无监督学习方法,用于从未标记的数据…

3 STM32标准库函数 之 窗口看门狗(WWDG)所有函数的介绍及使用

3 STM32标准库函数 之 窗口看门狗(WWDG)所有函数的介绍及使用 1. 图片有格式2 文字无格式三 库函数之窗口看门狗(WWDG)所有函数的介绍及使用前言一、IWDG库函数固件库函数预览1.1 函 数 IWDG_WriteAccessCmd1.1.1 IWDG_WriteAcces…

string模拟实现

文章目录 1.回顾库函数strcpymemcpystrcmpstrstr 2.回顾类和对象哪些函数里会有this指针?this指针调用方法结论:只要是不修改this指针指向的对象内容的成员函数,都可以加上const自己写了构造函数,编译器不会自动生成默认构造2.1构…

代码随想录第21天 | 回溯理论基础 77. 组合

回溯理论基础 回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构! 因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的…

MySQL面试题总结(部分)

一.介绍MySQL为什么在面试中会提及 1.为什么要在面试时MySQL会被提及? 在面试中问MySQL问题有几个主要原因: 1. 数据库管理系统的重要性:MySQL作为一种常用的关系型数据库管理系统(RDBMS),在互联网和企业应用中得到广泛使用。对数…

Conda安装及使用方法(常用命令)

系列文章目录 文章目录 系列文章目录前言一、Conda下载安装1.下载2.安装3.配置国内源 二、Conda安装Python环境1.创建虚拟环境2.激活虚拟环境3.虚拟环境安装Python库 三、Conda环境环境执行脚本四、PyCharm配置Conda环境五、Conda迁移环境1.方式一:拷贝环境2.方式二…

Modbus通信从入门到精通_1_Modbus通信基础

关于Modbus通信的相关知识比较零碎,此处对查找到的知识点从理论,通信协议、使用方法方面进行整理。 值得学习的博文:Modbus及调试用软件介绍;Modbus协议和上位机应用开发介绍 文章目录 1. Modbus通信理论1.1 Modbus通信特点1.2 存…

多线程(1): 线程的创建、回收、分离

1. 多线程概述 多线程在项目开发过程中使用频率非常高,因为使用多线程可以提高程序的并发性。提高程序并发性有两种方式:(1)多线程 (2)多进程。但是多线程对系统资源的消耗会更加少一些,并且线程和进程执行效率差不多。 在执行系统应用程序时&#xff…

2023/7/8总结

Tomcat 启动:双击bin目录下的startup.bat文件停止:双击bin目录下的shutdown.bat 文件访问 :http://localhost:8080(默认是8080,可以修改) git的使用 打开git bash git config --global user.name "名…

Vue3---什么是路由缓存问题

使用带有参数的路由时需要注意的是,当用户从 /users/johnny 导航到 /users/jolyne 时,相同的组件实例将被重复使用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩…

500万PV的网站需要多少台服务器?

1. 衡量业务量的指标 衡量业务量的指标项有很多,比如,常见Web类应用中的PV、UV、IP。而比较贴近业务的指标项就是大家通常所说的业务用户数。但这个用户数比较笼统,其实和真实访问量有比较大的差距,所以为了更贴近实际业务量及压力…

什么是提示工程?

原文链接:芝士AI吃鱼 理解大规模人工智能模型为何如此行事是一门艺术。即使是最有成就的技术专家也会对大型语言模型 (LLM) 的意想不到的能力感到困惑,大型语言模型是ChatGPT等人工智能聊天机器人的基本构建模块。 因此,提示工程成为生成式 …

特征选择算法 | Matlab 基于最大互信息系数特征选择算法(MIC)的分类数据特征选择

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 特征选择算法 | Matlab 基于最大互信息系数特征选择算法(MIC)的分类数据特征选择 部分源码 %--------------------

python 常用数据结构-列表

list 列表 列表定义与使用列表常用方法列表嵌套列表推导式 列表定义 列表是有序的可变元素的集合,使用中括号[]包围,元素之间用逗号分隔 列表是动态的,可以随时扩展和收缩 列表是异构的,可以同时存放不同类型的对象 列表中允…

阶乘后的零(力扣)数学 JAVA

给定一个整数 n ,返回 n! 结果中尾随零的数量。 提示 n! n * (n - 1) * (n - 2) * … * 3 * 2 * 1 示例 1: 输入:n 3 输出:0 解释:3! 6 ,不含尾随 0 示例 2: 输入:n 5 输出&…

WSL2 及 docker开发环境搭建

WSL2 及 docker开发环境搭建 1.使能WSL 控制面板->程序->程序和功能->启动或关闭Windows功能->勾选红框中选项->确认后重启电脑  2.下载Linux Kernel Update安装包 下载地址如下, 附件已将下载的安装包作为附件形式上传,…

ITIL 4服务连续性管理实践

一、目的和描述 关键信息 服务连续性管理实践的目的是确保灾难发生时,服务的可用性和性能能够保持在足够的水平。本实践提供了一个框架机制,利用产生有效响应的能力来构建组织的弹性,以保障关键利益相关者的利益,还有组织的声誉…

element 封装dialog弹窗组件鼠标移动到弹窗出现title

问题&#xff1a; element 封装dialog弹窗组件鼠标移动到弹窗出现title 封装的组件 <template><el-dialog title"111"v-bind"$attrs" v-on"$listeners" :visible.sync"show" ></el-dialog> </template><s…

02-webpack的热更新是如何做的,以及原理

一、是什么 HMR 可以理解为模块热替换&#xff0c;指在应用程序运行过程中&#xff0c;替换、添加、删除模块&#xff0c;而无需重新刷新整个应用. 如&#xff0c;我们在应用运行过程中修改了某个模块&#xff0c;通过自动刷新会导致整个应用的整体刷新&#xff0c;那页面中的…