09_Linux内核定时器

news/2024/10/23 9:26:56/

目录

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.ppmy.cn/news/783853.html

相关文章

如何离线播放Windows 10 Store游戏

Until recently, Windows 10 store games could only be played when connected to the internet. Microsoft has thankfully changed this, but as usual, they had to make things complicated: You can only have one “designated offline device” at a time. 直到最近&am…

荒野大镖客2显示服务器离线,荒野大镖客2怎么离线玩

荒野大镖客2怎么离线玩?很多大学玩家们宿舍到晚上都会断网,断网了就无法继续游玩荒野大镖客2,那么这款游戏有办法离线游戏吗?接下来安卓市场小编为大家带来荒野大镖客2离线模式进入方法。 荒野大镖客2离线模式进入方法 打开R星平台安装好荒野大镖客2后…

主机与虚拟机ubuntu网络无法ping通问题

一. 主机与虚拟机无法ping通问题 在嵌入式开发中,经过会涉及网络方面的问题。最常用到的是开发板在 虚拟机(ubuntu)通过NFS服务挂载到ubuntu中,这里就涉及网络是否可以ping通问题。 开发板 ping 通 ubuntu虚拟机系统的前提&…

合宙Air724UG LuatOS-Air core API--rtk

Table of Contents rtk rtk_sol_mode常量 rtk_time_type常量 rtk_sec常量 rtk_status常量 RTK消息 MSG_RTK_INFO rtk.open(param) rtk.set_mode(mode) rtk.set_time(timetype,time) rtk.write(data) rtk.close() rtk lua.rtk rtk访问库,支持千寻RTK和星舆RTK&#x…

SSR端口冲突的解决法法

问题: 运行SSR报以下错误: 分析: 应该是通讯端口被占用了: 在cmd中输入: netstat -an|findstr "1080" 1080是SSR的端口 返回: 解决: 运行SSR,修改当前端口&#xf…

下载使用SwitchyOmega连接服务器教程。很简单,一看就上手。

首先介绍一下使用场景模式,通过ssh远程连接服务器(centos),我用的Xshell。 第一步:按图步骤进行设置。 第二步:下载SwitchyOmega git下载地址:https://github.com/FelisCatus/SwitchyOmega/releases 打开页面,直接往…

stun p2p穿越nat技术 简介

目录 什么是STUN? 为什么需要STUN? 什么是STUN服务器? STUN如何工作? NAT探测阶段 打洞阶段 STUN如何在SD-WAN网络中应用? 什么是STUN? P2P网络要求通信双方都能主动发起访问,但是NAT设备…

kali_NAT模式、桥接上网/仅主机_详细

奶奶说,这个NAT折磨了我3天,让我把过程记下来,分享出去,给需要的人。 一、NAT模式介绍 各位在配置NAT的时候会不会有个问题, NAT模式下的kali,子网(ip)需不需要与主机处于同一网段&…