一、基础概念
1. 内核定时器是什么
内核定时器是基于系统时钟节拍(jiffies)的软件定时器,属于一次性定时器(超时执行一次,不会自动循环),用于驱动中延时、周期性检测、超时处理(看门狗、按键消抖、设备超时、LED闪烁等)。
- 分类:
- 传统低精度定时器:
timer_list(基于jiffies,毫秒级,最常用) - 高精度定时器:
hrtimer(基于硬件定时器,纳秒/微秒级,需要内核开启CONFIG_HIGH_RES_TIMERS)
- 传统低精度定时器:
2. 核心基础:jiffies
- 内核开机初始化全局变量
unsigned long jiffies,记录系统启动以来的时钟节拍总数。 HZ:每秒产生多少次时钟中断,配置文件定义(嵌入式常见 HZ=100 / 250 / 1000)- HZ=100:1节拍=10ms
- HZ=1000:1节拍=1ms
- 时间与节拍互转宏
// 毫秒转节拍msecs_to_jiffies(ms)// 节拍转毫秒jiffies_to_msecs(j)// 秒转节拍secs_to_jiffies(s)- 超时判断标准写法(避免溢出)
// 判断是否超时:当前jiffies >= 超时点if(time_after(jiffies,timer_expire))// 未超时if(time_before(jiffies,timer_expire))二、传统定时器 timer_list(驱动90%场景使用)
1. 结构体定义
#include<linux/timer.h>structtimer_list{structhlist_nodeentry;unsignedlongexpires;// 超时jiffies值void(*function)(unsignedlong);// 超时回调函数unsignedlongdata;// 传给回调的参数// 内核隐藏字段};2. 标准使用四步流程
① 定义定时器对象
全局/局部(建议全局,栈空间有限)
structtimer_listled_timer;② 编写超时回调函数
重点限制:
- 回调运行在软中断上下文,不能睡眠!
- 禁止:
msleep()、kmalloc(GFP_KERNEL)、信号量down() - 允许:
mdelay()、kmalloc(GFP_ATOMIC)、自旋锁
- 禁止:
- 执行速度要快,不要做耗时操作
voidled_timer_func(unsignedlongarg){// 示例:翻转LED状态gpio_set_value(arg,!gpio_get_value(arg));// 如果要循环定时,必须手动重新启动定时器mod_timer(&led_timer,jiffies+msecs_to_jiffies(500));}③ 初始化定时器(2种方式)
方式1:编译期宏(推荐)
#defineLED_GPIO10timer_setup(&led_timer,led_timer_func,LED_GPIO);方式2:运行时初始化(旧内核init_timer已废弃)
init_timer(&led_timer);led_timer.function=led_timer_func;led_timer.data=LED_GPIO;④ 启动/修改/删除定时器
- 启动/重置定时器
mod_timer(最常用,无需判断是否已注册)
// 500ms后执行回调mod_timer(&led_timer,jiffies+msecs_to_jiffies(500));- 删除定时器
// 安全删除:等待正在执行的回调函数跑完再返回del_timer_sync(&led_timer);// 非等待删除:不阻塞,可能回调还在运行del_timer(&led_timer);驱动卸载函数必须调用
del_timer_sync,防止卸载后定时器野指针访问。
3. 完整极简示例(LED闪烁驱动片段)
#include<linux/timer.h>#include<linux/gpio/consumer.h>structtimer_listled_timer;voidtimer_cb(unsignedlonggpio){gpio_set_value(gpio,!gpio_get_value(gpio));mod_timer(&led_timer,jiffies+msecs_to_jiffies(500));}staticintdrv_probe(...){intgpio=12;timer_setup(&led_timer,timer_cb,gpio);mod_timer(&led_timer,jiffies+msecs_to_jiffies(100));return0;}staticintdrv_remove(...){del_timer_sync(&led_timer);return0;}4. timer_list 核心API汇总
| API | 作用 |
|---|---|
| timer_setup | 初始化定时器 |
| mod_timer(timer, expires) | 设置/修改超时时间,自动加入内核链表 |
| del_timer | 删除定时器,不阻塞 |
| del_timer_sync | 阻塞删除,等待回调执行完毕(卸载必用) |
| timer_pending(timer) | 判断定时器是否已经挂入队列(未超时) |
5. 优缺点
优点
- API简单,所有内核版本兼容
- 资源开销极小,适合大量简单定时场景(按键消抖、状态轮询、LED闪烁)
缺点
- 精度受HZ限制,HZ=100时最小定时10ms,无法微秒级延时
- 回调软中断上下文,不能睡眠
三、高精度定时器 hrtimer(微秒/纳秒定时)
1. 适用场景
需要高精度定时:PWM控制、编码器采样、高频信号采集、精准延时。
头文件:#include <linux/hrtimer.h>
2. 核心结构体
structhrtimer{structtimerqueue_nodenode;ktime_texpires;// 高精度时间,ktime_t类型enumhrtimer_restart(*function)(structhrtimer*);// ...};ktime_t:内核高精度时间类型,单位纳秒- 回调返回值控制是否循环:
HRTIMER_NORESTART:一次性定时HRTIMER_RESTART:自动重新定时
3. 基础使用步骤
- 定义
structhrtimerhrt;- 回调函数(返回重启标记)
enumhrtimer_restarthrt_cb(structhrtimer*timer){// 定时任务// 重置超时时间,500mshrtimer_forward_now(timer,ms_to_ktime(500));returnHRTIMER_RESTART;}- 初始化+启动
// 初始化:CLOCK_MONOTONIC 不受系统时间修改影响hrtimer_init(&hrt,CLOCK_MONOTONIC,HRTIMER_MODE_REL);hrt.function=hrt_cb;// 启动,相对当前时间500mshrtimer_start(&hrt,ms_to_ktime(500),HRTIMER_MODE_REL);- 卸载销毁
hrtimer_cancel(&hrt);// 同步等待回调结束4. 时间转换宏
ms_to_ktime(ms)// 毫秒转ktimens_to_ktime(ns)// 纳秒转ktimektime_to_ms(t)// ktime转毫秒5. 优缺点
优点:微秒/纳秒级高精度,独立硬件定时器,不受HZ限制
缺点:内核需要开启高精度定时器配置,开销比timer_list大
四、定时器上下文与关键禁忌(高频踩坑点)
1. 上下文区分
timer_list回调:软中断上下文- 无进程上下文,无current,不能睡眠
hrtimer回调:高优先级软中断,同样禁止睡眠
2. 绝对禁止操作
- 睡眠类函数:
msleep、ssleep、wait_event、互斥锁mutex_lock - 分配内存阻塞:
kmalloc(GFP_KERNEL),要用GFP_ATOMIC - 回调内长时间循环阻塞系统中断
3. 循环定时标准规范
不要在回调里频繁创建/销毁定时器,两种标准方案:
- timer_list:回调末尾调用
mod_timer手动续期 - hrtimer:返回
HRTIMER_RESTART自动续期
4. 竞态保护要点
- 卸载函数必须用
del_timer_sync / hrtimer_cancel,防止回调访问已释放设备 - 如果定时器访问共享变量,需要加自旋锁(不能用互斥锁)
五、内核延时函数(和定时器区分开,很多人混淆)
定时器是异步延时(到点执行函数,不阻塞当前代码)
下面是同步阻塞延时,直接卡主当前线程:
- 忙等待(原子上下文/中断里用,耗CPU)
mdelay(ms);// 毫秒忙等udelay(us);// 微秒忙等ndelay(ns);// 纳秒忙等- 睡眠等待(进程上下文,可休眠,释放CPU)
msleep(ms);ssleep(s);六、选型对比表(驱动开发选型指南)
| 类型 | 精度 | 上下文 | 是否自动循环 | 适用场景 |
|---|---|---|---|---|
| timer_list | HZ节拍级(1~10ms) | 软中断,不能睡眠 | 需手动mod_timer | LED闪烁、按键消抖、设备轮询、低精度超时 |
| hrtimer | 微秒/纳秒级 | 高优先级软中断 | 支持自动重启 | PWM、电机采样、高频定时、精准控制 |
| msleep | 毫秒级阻塞 | 仅进程上下文 | 无,同步阻塞 | 初始化短延时 |
| mdelay | 忙等阻塞 | 任意上下文 | 无 | 中断内极短延时 |
七、常见面试/调试考点
- mod_timer和add_timer区别
- add_timer只能启动未注册的定时器;mod_timer兼容启动+修改,推荐只用mod_timer
- del_timer和del_timer_sync区别
- sync会等待正在运行的回调结束,卸载函数必须用,防止use after free
- 定时器回调为什么不能睡眠
- 软中断没有进程调度实体,睡眠会造成内核死锁/卡死
- jiffies溢出问题怎么处理
- 必须使用time_before/time_after,不能直接用 > < 判断
- 循环定时两种实现方式
- timer_list:mod_timer;hrtimer:返回HRTIMER_RESTART
- sysfs LED控制和定时器结合实战
用timer_list定时翻转gpio,配合brightness节点启停定时器,就是你前面那条led命令的底层驱动逻辑。
八、Linux 内核 timer_list 定时器完整驱动示例
功能:加载模块后,每500ms翻转一次LED(GPIO模拟),卸载时安全销毁定时器。
1、timer_demo.c
#include<linux/module.h>#include<linux/kernel.h>#include<linux/init.h>#include<linux/timer.h>#include<linux/gpio/consumer.h>#include<linux/delay.h>// 自定义LED GPIO,根据你的开发板修改#defineLED_GPIO_NUM10// 定时器结构体staticstructtimer_listled_timer;// LED状态全局标记staticbool led_status=false;// 定时器超时回调函数// 注意:运行在软中断上下文,禁止睡眠、mutex_lock、msleepstaticvoidled_timer_callback(unsignedlongarg){intgpio=arg;// 翻转LED状态led_status=!led_status;gpio_set_value(gpio,led_status);// 重新启动定时器,实现循环定时 500msmod_timer(&led_timer,jiffies+msecs_to_jiffies(500));}staticint__inittimer_demo_init(void){intret;// 1. 申请GPIOret=gpio_request(LED_GPIO_NUM,"timer_led");if(ret<0){pr_err("gpio request failed, ret = %d\n",ret);returnret;}// 设置GPIO为输出,初始低电平(灭灯)gpio_direction_output(LED_GPIO_NUM,0);led_status=false;// 2. 初始化定时器:绑定回调、传参GPIO号timer_setup(&led_timer,led_timer_callback,LED_GPIO_NUM);// 3. 启动定时器,100ms后第一次触发mod_timer(&led_timer,jiffies+msecs_to_jiffies(100));pr_info("timer demo module loaded, led gpio = %d\n",LED_GPIO_NUM);pr_info("current jiffies = %lu, HZ = %d\n",jiffies,HZ);return0;}staticvoid__exittimer_demo_exit(void){// 同步删除定时器:等待正在执行的回调完全结束再返回del_timer_sync(&led_timer);// 熄灭LED,释放GPIOgpio_set_value(LED_GPIO_NUM,0);gpio_free(LED_GPIO_NUM);pr_info("timer demo module unloaded\n");}module_init(timer_demo_init);module_exit(timer_demo_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Driver Demo");MODULE_DESCRIPTION("Linux kernel timer_list simple demo");MODULE_VERSION("1.0");2、Makefile
obj-m += timer_demo.o # 替换成你的内核源码路径 KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean3、编译、加载、测试命令
# 编译make# 加载驱动sudoinsmod timer_demo.ko# 查看内核打印dmesg-w# 查看当前jiffies验证节拍cat/proc/jiffies# 卸载驱动sudormmod timer_demo4、核心知识点注释梳理
1. 定时器四步标准流程
timer_setup:初始化定时器(绑定回调+传参)mod_timer:启动/刷新超时时间- 回调内部再次调用
mod_timer实现循环 - 卸载函数
del_timer_sync安全销毁
2. 回调函数硬性限制(极易踩坑)
- 软中断上下文,不能睡眠
- 禁止:
msleep()、mutex_lock()、wait_event、kmalloc(GFP_KERNEL) - 允许:
mdelay()、自旋锁spin_lock、kmalloc(GFP_ATOMIC)
- 禁止:
- 回调执行逻辑必须简短,不能长时间循环阻塞系统
3. API区别
mod_timer:通用接口,定时器未启动则新建,已启动则修改超时,推荐只用它del_timer:异步删除,不阻塞,可能出现回调正在访问模块内存del_timer_sync:阻塞等待回调执行完毕,驱动卸载必须使用
4. 时间转换宏
msecs_to_jiffies(500)// 500ms 转系统节拍secs_to_jiffies(2)// 2秒转节拍jiffies_to_msecs(jiffies)// 节拍转毫秒5、高精度定时器 hrtimer 简易扩展片段(对比参考)
如果需要微秒级定时,替换为hrtimer核心代码:
#include<linux/hrtimer.h>staticstructhrtimerhrt;enumhrtimer_restarthrt_callback(structhrtimer*timer){// 翻转LEDled_status=!led_status;gpio_set_value(LED_GPIO_NUM,led_status);// 延后500ms自动重启hrtimer_forward_now(timer,ms_to_ktime(500));returnHRTIMER_RESTART;}// 初始化hrtimer_init(&hrt,CLOCK_MONOTONIC,HRTIMER_MODE_REL);hrt.function=hrt_callback;hrtimer_start(&hrt,ms_to_ktime(500),HRTIMER_MODE_REL);// 卸载销毁hrtimer_cancel(&hrt);6、常见报错排查
gpio_request failed:GPIO被其他驱动占用,更换GPIO号- 卸载模块内核Oops:忘记使用
del_timer_sync,定时器回调访问已释放内存 - 定时器不循环:回调内部缺少
mod_timer - 睡眠锁死内核:回调中调用msleep/mutex等睡眠函数