驱动-Linux定时-timer_list

了解内核定时相关基础知识

文章目录


简要介绍

硬件为内核提供了一个系统定时器来计算流逝的时间(即基于未来时间点的计时方式, 以当前时刻为计时开始的起点, 以未来的某一时刻为计时的终点) , 内核只有在系统定时器的帮助下才能计算和管理时间, 但是内核定时器的精度并不高, 所以不能作为高精度定时器使用。并且内核定时器的运行没有周期性, 到达计时终点后会自动关闭。 如果要实现周期性定时, 就要在定时处理函数中重新开启定时器。

Linux 内核中使用 timer_list 结构体表示内核定时器, 该结构体定义在"内核源码/include/li

nux/timer.h"文件中, 具体内容如下所示

java 复制代码
struct timer_list {
    struct hlist_node   entry;
    unsigned long       expires;/* 定时器超时时间,单位是节拍数 */
    void            (*function)(struct timer_list *);/* 定时处理函数 */
    u32         flags;

#ifdef CONFIG_LOCKDEP
    struct lockdep_map  lockdep_map;
#endif
    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
};

timer_list 特点

inux 内核定时器是基于 timer_list 结构的动态定时器,具有以下特点:

  • 不是周期性的,超时后会自动失效

  • 基于内核的 jiffies 计时

  • 在中断上下文执行,因此不能睡眠

  • 精度取决于 HZ 值(通常为 1ms 或 10ms)

API 函数

函数 作用
void add_timer(struct timer_list *timer) 向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行
int del_timer(struct timer_list * timer) 删除一个定时器
int mod_timer(struct timer_list *timer,unsigned long expires) 修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器

在使用add_timer()函数向 Linux 内核注册定时器之前,还需要设置定时时间,定时时间由timer_list结构体中的expires参数所确定,单位为节拍数

这里简要理解,节拍数 jiffies 和 时间之间的转换函数:

jiffies_64用于64位系统,而jiffies用于 32 位系统。为了方便开发,Linux 内核还提供了几个jiffies和ms、us、ns之间的转换函数,如下 所示:

函数 作用
int jiffies_to_msecs(const unsigned long j) 将 jiffies 类型的参数 j 分别转换为对应的毫秒
int jiffies_to_usecs(const unsigned long j) 将 jiffies 类型的参数 j 分别转换为对应的微秒
u64 jiffies_to_nsecs(const unsigned long j) 将 jiffies 类型的参数 j 分别转换为对应的纳秒
long msecs_to_jiffies(const unsigned int m) 将毫秒转换为 jiffies 类型
long usecs_to_jiffies(const unsigned int u) 将微秒转换为 jiffies 类型
unsigned long nsecs_to_jiffies(u64 n) 将纳秒转换为 jiffies 类型

既然要定时,那么情形就是 把定时时间转换成节拍数,系统内核根据节拍数和节拍频率。内核里面只认节拍数,它对应的就是频率

例如:进行3秒钟的定时:

java 复制代码
timer_test.expires = jiffies_64 +msecs_to_jiffies(3000)

实验

测试程序 - timer_mod.c

java 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>

 

static void function_test(struct timer_list  *t);//定义function_test定时功能的函数
DEFINE_TIMER(timer_test,function_test);

static void function_test(struct timer_list *t){
  
  printk(" this is function test \n");
  mod_timer(&timer_test,jiffies_64+msecs_to_jiffies(5000));  使用mod_timer函数将定时时间设置为五秒后

}

static int __init time_module_init(void) //驱动入口函数
{
  	timer_test.expires = jiffies_64 + msecs_to_jiffies(5000);//将定时时间设置为五秒后
	add_timer(&timer_test);//添加一个定时器
	return 0;

}
static void __exit time_module_exit(void) //驱动出口函数
{
    	del_timer(&timer_test);//删除一个定时器
	  printk("module exit \n");

}
module_init(time_module_init);
module_exit(time_module_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("wang fang chen");

源码分析

  • 这里用到了定时器timer_list 的三个api 函数:

    定义定时器:DEFINE_TIMER(timer_test,function_test); 参数为定时器名称和回调方法

    添加定时器:add_timer(&timer_test);

    修改定时器:mod_timer(&timer_test,jiffies_64+msecs_to_jiffies(5000));

    删除定时器:del_timer(&timer_test);

    **注意点:**定时器的名称并不是作为变量定义的,作为方法参数定义了的 DEFINE_TIMER的方法参数。

    回调函数 function_test 是先定义,然后实现 。

编译文件-Makefile

java 复制代码
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += timer_mod.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

实验验证

加载驱动看打印信息

注意事项

  • 定时器精度:内核定时器的精度受 HZ 值影响,不适合需要高精度的场合

  • 执行上下文:回调函数在中断上下文执行,不能调用可能睡眠的函数

  • 多核处理:del_timer() 可能在 SMP 系统上返回后定时器仍在运行,使用 del_timer_sync() 更安全

  • 竞争条件:确保在模块退出时删除所有定时器

总结

这里了解的是内核的一个定时器timer_list 的使用。 了解基本知识即可。

相关推荐
MaoXian_n10 小时前
[IMX] 03.时钟树 - Clock Tree
arm开发·驱动开发·单片机·嵌入式硬件
sukalot12 小时前
window 显示驱动开发-报告图形内存(四)
驱动开发
天夏已微凉2 天前
OpenHarmony系统HDF驱动开发介绍(补充)
驱动开发·音视频·harmonyos
不摆烂选手2 天前
Linux 阻塞和非阻塞 I/O 简明指南
linux·驱动开发·ubuntu·正点原子imx6ull学习笔记
__Benco2 天前
OpenHarmony平台驱动开发(十五),SDIO
驱动开发
7yewh2 天前
FPGA前瞻篇-计数器设计与实现实例
arm开发·驱动开发·嵌入式硬件·fpga开发·硬件架构·硬件工程·精益工程
程序员JerrySUN2 天前
《驱动开发硬核特训 · 专题篇》:深入理解 I2C 子系统
驱动开发
sukalot2 天前
window 显示驱动开发-将虚拟地址映射到内存段(二)
驱动开发
忧虑的乌龟蛋3 天前
嵌入式Linux I2C驱动开发详解
linux·驱动开发·嵌入式·iic·i2c·读数据·写数据