0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux驱动开发-内核定时器

DS小龙哥-嵌入式技术 2022-09-17 15:06 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

【摘要】 内核定时器是内核用来控制在未来某个时间点(基于jiffies(节拍总数))调度执行某个函数的一种机制,相关函数位于 和 kernel/timer.c 文件中。 当内核定时器定时时间到达时,会进入用户指定的函数,相当于软中断。内核定时器注册开启后,运行一次就不会再运行(相当于自动注销),我们可以重新设置定时器的超时时间,让定时器重复运行。

1. 内核定时器介绍

内核定时器是内核用来控制在未来某个时间点(基于jiffies(节拍总数))调度执行某个函数的一种机制,相关函数位于 和 kernel/timer.c 文件中。

当内核定时器定时时间到达时,会进入用户指定的函数,相当于软中断。内核定时器注册开启后,运行一次就不会再运行(相当于自动注销),我们可以重新设置定时器的超时时间,让定时器重复运行。

每当时钟中断发生时,全局变量jiffies(一个32位的unsigned long 变量)就加1,因此jiffies记录了linux系统启动后时钟中断发生的次数,驱动程序常利用jiffies来计算不同事件间的时间间隔。内核每秒钟将jiffies变量增加HZ次。因此,对于HZ值为100的系统,jiffy+1等于隔了10ms,而对于HZ为1000的系统,jiffy+1仅为1ms。

内核定时器结构体:
下面列出了需要关心的成员

struct timer_list {
	unsigned long expires;         //设置超时时间,用jiffies作为基准值
	void (*function)(unsigned long); //类似中断服务函数,设置定时器到时后处理的函数 
	unsigned long data;           //中断服务函数的参数
}

expires设置:以当前时间为基准加上延时时间,时间基准用jiffies变量表示,延时时间可以使用以下两个宏转换成jiffies单位。

2. 内核定时器相关API函数

2.1 修改定时器超时时间

函数原型 *int mod_timer(struct timer_list timer, unsigned long expires)
函数功能 修改定时器超时时间
函数参数 timer:对应的定时器结构体 expires:超时时间
函数返回值 成功返回 :修改成功的时间值
函数定义文件 \linux-3.5\kernel\timer.c

2.2 初始化定时器

函数原型 #define init_timer(timer)\
函数功能 初始化定时器结构
函数参数 timer:对应的定时器结构体
函数定义文件 \linux-3.5\include\linux\timer.h

2.3 关闭定时器

函数原型 int del_timer(struct timer_list *timer)
函数功能 关闭定时器,停用一个定时器。
函数参数 timer:对应的定 时器结构体
函数返回值 返回0:成功
函数定义文件 \linux-3.5\include\linux\timer.h

2.4 关闭定时器

函数原型 int del_timer_sync(struct timer_list *timer)
函数功能 关闭定时器,停用一个定时器,多处理器使用。如果编内核时不支持 SMP(多处理器), del_timer_sync()和 del_timer()等价
函数参数 timer:对应的定时器结构体
函数返回值 返回0:成功
函数定义文件 \linux-3.5\include\linux\timer.h

2.5 转换时间(微妙单位)

函数原型 unsigned long usecs_to_jiffies(const unsigned int m)
函数功能 转换时间(微妙单位),用于填充定时器结构体,设置超时时间
函数参数 m:要转换的时间值(微妙为单位)
函数返回值 成功返回转换成功的时间。用于填充定时器结构体,设置超时时间
函数定义文件 \linux-3.5\kernel\timer.c

2.6 转换时间(毫秒为单位)

函数原型 unsigned long msecs_to_jiffies(const unsigned int m)
函数功能 转换时间(毫秒为单位),用于填充定时器结构体,设置超时时间
函数参数 m:要转换的时间值(毫秒为单位)
函数返回值 成功返回转换成功的时间。用于填充定时器结构体,设置超时时间
函数定义文件 \linux-3.5\kernel\timer.c

将jiffies单位转为struct timespec结构体表示:

Void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value);

示例:
jiffies_to_timespec(jiffies,&value);
printk("value.ts_sec=%d\n",value.tv_sec);
printk("value.tv_nsec=%d\n",value.tv_nsec);

2.7 初始化定时器的结构体成员

TIMER_INITIALIZER( _function, _expires, _data) 宏用于赋值定时器结构体的function、 expires、 data 和 base 成员, 这个宏的定义如下所示:(被DEFINE_TIMER宏调用)

#define TIMER_INITIALIZER(_function, _expires, _data) {		\
		.entry = { .prev = TIMER_ENTRY_STATIC },	\
		.function = (_function),			\
		.expires = (_expires),				\
		.data = (_data),				\
		.base = &boot_tvec_bases,			\
		.slack = -1,					\
		__TIMER_LOCKDEP_MAP_INITIALIZER(		\
			__FILE__ ":" __stringify(__LINE__))	\
	}

2.8 初始化定时器并且赋值

DEFINE_TIMER( _na me , _functi o n, _e x pires, _data) 宏是定义并初始化定时器成员的“快捷方式”, 这个宏定义如下所示:

/*初始化定时器,并进行赋值*/
#define DEFINE_TIMER(_name, _function, _expires, _data)		\
	struct timer_list _name =				\
		TIMER_INITIALIZER(_function, _expires, _data)

2.9 定时器初始化赋值

setup_timer()也可用于初始化定时器并赋值其成员, 其源代码如下:

//初始化定时器并进行赋值
#define setup_timer(timer, fn, data)					\
	do {								\
		static struct lock_class_key __key;			\
		setup_timer_key((timer), #timer, &__key, (fn), (data));\
	} while (0)

static inline void setup_timer_key(struct timer_list * timer,
				const char *name,
				struct lock_class_key *key,
				void (*function)(unsigned long),
				unsigned long data)
{
	timer->function = function;
	timer->data = data;
	init_timer_key(timer, name, key);
}

3. 使用定时器的步骤

(1) 定义定时器结构体timer_list。

/*定义一个内核定时器配置结构体*/
static struct timer_list mytimer ; 

(2) 设置超时时间,定义定时器处理函数和传参。

mytimer.expires=jiffies+ msecs_to_jiffies(1000); /*设置定时器的超时时间,1000毫秒*/
//或者
//mytimer.expires=jiffies+HZ; /*设置定时器的超时时间,1000毫秒*/

mytimer.function = time_fun;	              /*定时器超时的回调函数,类似中断服务函数*/
mytimer.data = 12;                       /*传给定时器服务函数的参数*/

(3) 开启定时器。

init_timer(&mytimer);          /*初始化定时器*/
add_timer(&mytimer);	        /*启动定时器*/

完整示例代码:

#include 
#include 
#include 

static struct timer_list timer;

static void timer_function(unsigned long data)
{
	printk("data=%ld\n",data);
	mod_timer(&timer,msecs_to_jiffies(3000)+jiffies);
}

static int __init tiny4412_linux_timer_init(void)
{
	timer.expires=HZ*3+jiffies; /*单位是节拍*/
	timer.function=timer_function;
	timer.data=666;
	
	/*1. 初始化定时器*/
	init_timer(&timer);
	/*2. 添加定时器到内核*/
	add_timer(&timer);
    printk("驱动测试: 驱动安装成功\n");
    return 0;
}

static void __exit tiny4412_linux_timer_cleanup(void)
{
	/*3. 删除定时器*/
	del_timer_sync(&timer);
    printk("驱动测试: 驱动卸载成功\n");
}

module_init(tiny4412_linux_timer_init);    /*驱动入口--安装驱动的时候执行*/
module_exit(tiny4412_linux_timer_cleanup); /*驱动出口--卸载驱动的时候执行*/

MODULE_LICENSE("GPL");  /*设置模块的许可证--GPL*/

4. 内核提供的延时函数

Linux 内核中提供了进行纳秒、微秒和毫秒延迟。
void ndelay(unsigned long nsecs) ;
void udelay(unsigned long usecs) ;
void mdelay(unsigned long msecs) ;
上述延迟的实现原理本质上是忙等待,根据 CPU 频率进行一定次数的循环。在内核中,最好不要直接使用mdelay()函数, 这将无谓地耗费CPU资源。
void msleep(unsigned int millisecs) ;
unsigned long msleep_interruptible(unsigned int millisecs) ;
void ssleep(unsigned int seconds) ;
上述函数将使得调用它的进程睡眠参数指定的时间, msleep()、 ssleep()不能被打断,而 msleep_interruptible()则可以被打断。

5. 精度较高的时间获取方式

高精度定时器通常用ktime作为计时单位。
获取内核高精度时间单位: ktime_t ktime_get(void)

下面是一些时间辅助函数用于计算和转换:

ktime_t ktime_set(const long secs, const unsigned long nsecs);   
ktime_t ktime_sub(const ktime_t lhs, const ktime_t rhs);   
ktime_t ktime_add(const ktime_t add1, const ktime_t add2);   
ktime_t ktime_add_ns(const ktime_t kt, u64 nsec);   
ktime_t ktime_sub_ns(const ktime_t kt, u64 nsec);   
ktime_t timespec_to_ktime(const struct timespec ts);   
ktime_t timeval_to_ktime(const struct timeval tv);   
struct timespec ktime_to_timespec(const ktime_t kt);   //转换的时间通过timespec结构体保存
struct timeval ktime_to_timeval(const ktime_t kt);     //转换的时间通过timeval结构体保存
s64 ktime_to_ns(const ktime_t kt);     //转换为ns单位
int ktime_equal(const ktime_t cmp1, const ktime_t cmp2);   
s64 ktime_to_us(const ktime_t kt);    //转换为us单位
s64 ktime_to_ms(const ktime_t kt);    //转换为ms单位
ktime_t ns_to_ktime(u64 ns);

示例: 计算经过的一段时间

static int hello_init(void)
{
	ktime_t my_time,my_time2;
	unsigned int i,j;
	unsigned int time_cnt=0;
	my_time=ktime_get();    		//获取当前时间
	i=ktime_to_us(my_time); 		//转us
	
	udelay(600);  				//延时一段时间
	
	my_time2=ktime_get();  	  	//获取当前时间
	j=ktime_to_us(my_time2);  		//转us
	
	printk("time_cnt=%ld\n",j-i); 	//得出之间差值,正确值为: 600
	return 0;
}
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    4

    文章

    1436

    浏览量

    42492
  • 定时器
    +关注

    关注

    23

    文章

    3361

    浏览量

    121750
  • 函数
    +关注

    关注

    3

    文章

    4406

    浏览量

    66843
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    Linux驱动开发的必备知识

    驱动框架进行开发。 6、调试技能: 掌握内核调试工具,如 KDB、KGDB、printk 等。 能够分析内核日志,定位驱动程序中的问
    发表于 12-04 07:58

    【迅为工业RK3568稳定可靠】itop-3568开发Linux驱动开发实战:RK3568内核模块符号导出详解

    【迅为工业RK3568稳定可靠】itop-3568开发Linux驱动开发实战:RK3568内核模块符号导出详解
    的头像 发表于 11-21 13:25 664次阅读
    【迅为工业RK3568稳定可靠】itop-3568<b class='flag-5'>开发</b>板<b class='flag-5'>Linux</b><b class='flag-5'>驱动</b><b class='flag-5'>开发</b>实战:RK3568<b class='flag-5'>内核</b>模块符号导出详解

    使用系统定时器SysTick来实现精确延时微秒和毫秒函数

    Cortex-M内核MCU都有的一个定时器,所以以上延时微秒和延时毫秒的函数适用于任何 Cortex-M内核的MCU。有了精确延时函数,那么使用通用GPIO软件模拟一些通信协议,如IIC、SPI等串行协议,就可以
    发表于 11-20 07:12

    【免费送书】成为硬核Linux开发者:《Linux 设备驱动开发(第 2 版)》

    Linux系统的设备驱动开发,一直给人门槛较高的印象,主要因内核机制抽象、需深度理解硬件原理、开发调试难度大所致。2021年,一本讲解
    的头像 发表于 11-18 08:06 442次阅读
    【免费送书】成为硬核<b class='flag-5'>Linux</b><b class='flag-5'>开发</b>者:《<b class='flag-5'>Linux</b> 设备<b class='flag-5'>驱动</b><b class='flag-5'>开发</b>(第 2 版)》

    【书籍评测活动NO.67】成为硬核Linux开发者:《Linux 设备驱动开发(第 2 版)》

    )。成为硬核Linux开发Linux系统的设备驱动开发,一直给人门槛较高的印象,主要因内核机制
    发表于 11-17 17:52

    PWM、定时器、SysTick 区别及应用场景

    在单片机和嵌入式开发中,经常会遇到PWM(脉宽调制)、定时器、SysTick(系统滴答定时器)这几个词。很多初学者容易把它们混为一谈,以为都是“定时相关的东西”。其实,它们在本质和应用
    的头像 发表于 11-17 10:53 230次阅读
    PWM、<b class='flag-5'>定时器</b>、SysTick 区别及应用场景

    SysTick系统滴答定时器简介

    SysTick—系统定时器是属于CM33内核中的一个外设,内嵌在NVIC中。系统定时器是一个24bit的向下递减的计数,计数每计数一次的
    的头像 发表于 09-23 09:50 1335次阅读
    SysTick系统滴答<b class='flag-5'>定时器</b>简介

    大彩讲堂:VisualHMI-LUA教程-定时器的使用指南

    定时器的使用
    的头像 发表于 08-31 16:59 856次阅读
    大彩讲堂:VisualHMI-LUA教程-<b class='flag-5'>定时器</b>的使用指南

    TPS3435 Nano IQ精密超时看门狗定时器技术解析与应用指南

    WSON和8引脚SOT23封装。TPS3435/TPS3435-Q1定时器的工作温度范围为-40°C至125°C。这款超时看门狗定时器非常适合机器人伺服驱动器、混合模块信号、HVAC控制
    的头像 发表于 08-26 16:20 713次阅读
    TPS3435 Nano IQ精密超时看门狗<b class='flag-5'>定时器</b>技术解析与应用指南

    【HZ-RK3568开发板免费体验】3、开启Linux Kernel RT功能

    事件。 内核定时器: Preempt-RT提供了更精确和可配置的内核定时器,使得可以实现微秒级的定时精度,适用于对时间要求极高的应用场景。 实时扩展: Preempt-RT提供了一些实时扩展机制,使得
    发表于 07-22 14:03

    MCU定时器/计数

    架构与功能特性‌ 定时器类型与配置‌ 高级控制定时器‌:支持互补PWM输出与刹车功能,适用于电机驱动等高精度控制场景。通用定时器‌:集成输入捕获、输出比较、单脉冲模式等基础功能‌。 系
    的头像 发表于 04-27 13:54 592次阅读

    基于OpenSBI的linux nommu实现

    :SupervisorSoftwareBinaryInterface软件二进制接口Linux内核工作在S模式下时,不能直接访问机器定时器。而系统的运行依赖于定时器,为了解决这个问
    的头像 发表于 02-08 13:43 1056次阅读
    基于OpenSBI的<b class='flag-5'>linux</b> nommu实现

    EE-345:SHARC处理的启动内核定制和固件可升级性

    电子发烧友网站提供《EE-345:SHARC处理的启动内核定制和固件可升级性.pdf》资料免费下载
    发表于 01-07 14:14 0次下载
    EE-345:SHARC处理<b class='flag-5'>器</b>的启动<b class='flag-5'>内核定</b>制和固件可升级性

    定时器已安排!开发小白看过来~

    本文将为您详细介绍Air201定时器的基本操作与设置,确保您轻松上手,包教包会! 在Air201模组搭载的LuatOS系统中,定时器(timer)是一项基础且关键的服务。 它允许开发者在特定的时间点
    的头像 发表于 12-31 14:30 767次阅读
    <b class='flag-5'>定时器</b>已安排!<b class='flag-5'>开发</b>小白看过来~