09_Linux内核定时器

目录

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.hqwc.cn/news/15008.html

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

相关文章

人工智能(pytorch)搭建模型17-pytorch搭建ReitnNet模型,加载数据进行模型训练与预测

大家好,我是微学AI,今天给大家介绍一下人工智能(pytorch)搭建模型17-pytorch搭建ReitnNet模型,加载数据进行模型训练与预测,RetinaNet 是一种用于目标检测任务的深度学习模型,旨在解决目标检测中存在的困难样本和不平衡…

Maven学习

1.配置环境变量 1.M2_HOME Maven的安装目录 2.修改Path %M2_HOME%\bin2.配置IDEA 配置文件的地址 本地仓库的地址 修改配置文件的路径 修改本地仓库的目录 注意&#xff0c;这里的路径的分隔符必须是/ 配置镜像 <mirror><id>aliyunmaven</id><mi…

在idea中使用Git技术

1.配置git环境 打开idea,点击file->setting->搜索git&#xff0c; 将git的安装路径填写进去 2.去gitee创建一个远程仓库 3.拉入一个.gitignore文件&#xff0c;过滤掉不需要管理的文件 4.在idea进行如下操作 5.选择要提交的内容 目前只是保存在了本地仓库 6.推送到远端…

SEGA: Semantic Guided Attention on Visual Prototype for Few-Shot Learning

方法比较简单&#xff0c;利用语义改进prototype&#xff0c;能促进性提升

十二、Docker Compose 介绍与安装

学习参考&#xff1a;尚硅谷Docker实战教程、Docker官网、其他优秀博客(参考过的在文章最后列出) 目录 前言一、docker compose介绍二、docker compose能干嘛三、docker compose安装与卸载3.1 docker-compose安装3.2 docker-compose卸载 总结 前言 在使用k8s之前&#xff0c;随…

LNMP架构及部署、skyuc电影网站部署

目录 一、安装nginx 1、关闭防火墙 2.创建管理nginx用户 3.配置nginx 4.命令优化 5.创建nginx脚本 二、安装mysql数据库 三、安装PHP 1.上传php安装包 2.上传 zend-loader-hph5.6 3.创建用户 四、LNMP平台中部署skyuc电影网站 1.解压 SKYUC.v3.4.2.srouce 2.创建数据…

光场1.0——非聚焦型光场相机

本文概要 本文讲主要从光场硬件结构设计以及软件处理方式的层面来介绍一下光场的相关内容&#xff0c;关于光场的优势和具体应用点并不在本文的主要范围内。 光场1.0 1. 结构原理说明 首先来介绍一下光场相机&#xff0c;那么什么是光场相机呢&#xff0c;光场相机经历了两…

SPEC CPU 2006 在 CentOS 5.0 x86_64 古老系统测试

下载镜像 CentOS 2 3 4 5 6 等历史老版本下载地址 国内镜像地址_hkNaruto的博客-CSDN博客 下载CentOS 5.0 1-7 ISO文件 注意&#xff1a;尝试过下载DVD版本&#xff0c;速度太慢了。还是通过国内镜像下载这几个iso快。 安装虚拟机 VirtualBox 挂载第一个iso&#xff0c;启动…

突破数据边界,开启探索之旅!隐语开源Meetup一周年专场7月22日上海见

小伙伴们&#xff0c;&#x1f4e2;「隐语开源一周年 Meetup 」即将来袭&#xff01;&#x1f389;在一周年 Meetup 上&#xff0c;不仅会对隐语 1.0 版本进行详解&#xff0c;还有新鲜出炉的隐语 MVP 部署体验包&#xff0c;让你秒变高手&#xff01;更有机会与隐私计算行业的…

DAY37:贪心算法(四)跳跃游戏+跳跃游戏Ⅱ

文章目录 55.跳跃游戏思路完整版总结 45.跳跃游戏Ⅱ思路完整版为什么next覆盖到了终点可以直接break&#xff0c;不用加上最后一步逻辑梳理 总结 55.跳跃游戏 给定一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。 数组中的每个元素代表你在该位置可以跳跃…

【LeetCode周赛】2022上半年题目精选集——贪心

文章目录 2136. 全部开花的最早一天&#xff08;贪心&#xff09;⭐⭐⭐⭐⭐思路代码语法解析&#xff1a;Integer[] id IntStream.range(0, plantTime.length).boxed().toArray(Integer[]::new); 2141. 同时运行 N 台电脑的最长时间&#xff08;贪心&#xff09;⭐⭐⭐⭐⭐解…

【大数据实战电商推荐系统】概述版

文章目录 第1章 项目体系框架设计&#xff08;说明书&#xff09;第2章 工具环境搭建&#xff08;说明书&#xff09;第3章 项目创建并初始化业务数据3.1 IDEA创建Maven项目&#xff08;略&#xff09;3.2 数据加载准备&#xff08;说明书&#xff09;3.3 数据初始化到MongoDB …