定时器驱动如何初始化

Linux内核中,定时器驱动的初始化通常放在驱动模块的probe函数或init函数里完成。你需要定义一个struct timer_list结构体变量,这个变量可以静态定义也可以动态分配。静态定义使用DEFINE_TIMER宏,动态分配则用timer_setup函数。初始化时要指定定时器到期后执行的回调函数以及可能的参数。

初始化除了设置回调函数,还需要指定定时器的expires字段为0,表示尚未启动。如果你的驱动支持多个定时器,最好为每个定时器单独命名,便于调试。另外在设备树或平台驱动中,初始化前要确保时钟源和中断资源已经申请成功,避免定时器工作异常。完成初始化后,定时器并不会立即运行,需要后续调用add_timer或mod_timer来激活。

linux 定时器驱动_linux 定时器驱动_linux 定时器驱动

怎样设置定时器超时时间

设置超时时间是通过修改timer_list结构体的expires字段实现的。expires的单位是jiffies,也就是内核的时钟滴答数。你需要根据目标时间差和当前jiffies来计算,例如想延时100毫秒,可以用jiffies + msecs_to_jiffies(100)。msecs_to_jiffies能自动处理不同HZ配置下的换算,是推荐的做法。

除了绝对超时值,你还可以使用相对超时的辅助函数,比如timer_setup之后用mod_timer直接传入相对延时。mod_timer比先del再add更安全linux 软件,因为它能处理定时器已经在队列中的情况。注意超时时间不能为0或负数,否则定时器会立即触发,可能导致意外的递归调用。如果驱动需要长时间定时,建议拆分成多个短定时器,避免影响系统响应。

linux 定时器驱动_linux 定时器驱动_linux 定时器驱动

定时器回调函数注意什么

定时器回调函数的原型是void (function)(struct timer_list t)。它在软中断上下文中执行,因此不能睡眠,不能调用可能引起调度的函数,比如mutex_lock、kmalloc(GFP_KERNEL)等。只能使用自旋锁、原子操作和GFP_ATOMIC标志的内存分配。如果你需要执行耗时操作,应当把任务交给工作队列或内核线程。

回调函数内部可以重新修改自己的expires并再次调用add_timer或mod_timer来实现周期定时。但要注意防止定时器风暴,即回调函数执行时间超过定时周期导致系统过载。另外,回调函数中访问共享数据时必须加锁保护,因为定时器可能和其他进程或中断上下文并发运行。从内核4.15开始,推荐使用timer_setup来设置回调,它会自动处理timer_list中的参数传递。

如何删除和修改定时器

linux 定时器驱动_linux 定时器驱动_linux 定时器驱动

删除定时器使用del_timer或del_timer_sync函数。del_timer会立即从激活队列中移除定时器,但如果定时器回调正在其他CPU上执行,它可能仍在运行。del_timer_sync会等待回调函数完成后再返回,更安全,但注意不能在中断上下文或持有自旋锁时调用sync版本,否则可能导致死锁。

修改定时器通常用mod_timer,它一次性完成删除和重新添加。mod_timer会更新expires字段,如果定时器不在激活状态则直接添加,如果已激活则修改超时时间并重新排队。返回值表示定时器在被修改前是否处于激活状态。注意不要重复调用mod_timer去设置相同的时间,那会产生不必要的开销。在驱动卸载时,一定要先调用del_timer_sync确保定时器不再运行,再释放相关资源。

驱动中使用定时器的实例

以一个虚拟的LED闪烁驱动为例,我们可以实现一个每500毫秒翻转一次的定时器。在probe函数中调用timer_setup(&led_timer, led_blink_callback, 0)。回调函数led_blink_callback内先翻转GPIO电平linux makefile,然后调用mod_timer(&led_timer, jiffies + msecs_to_jiffies(500))来重新触发自己。这样定时器就会持续周期运行。

在驱动卸载的remove函数中,需要先调用del_timer_sync(&led_timer)确保回调不会再被调用。同时要把GPIO恢复为默认状态。如果你想让闪烁过程可以用户控制,可以在ioctl或sysfs中添加一个开关,用bool变量配合spinlock来控制回调中是否继续mod_timer。这个实例展示了定时器最典型的周期触发模式,实际项目中可以扩展出按键去抖、轮询传感器等应用。

高精度定时器怎么用

linux 定时器驱动_linux 定时器驱动_linux 定时器驱动

当需要微秒级甚至纳秒级的定时精度时,标准定时器的jiffies粒度不够用,这时就要使用高精度定时器hrtimer。hrtimer基于ktime_t,以纳秒为单位,不依赖系统HZ。初始化使用hrtimer_init,设置时钟模式为HRTIMER_MODE_REL或HRTIMER_MODE_ABSlinux 定时器驱动,回调函数原型为enum hrtimer_restart (function)(struct hrtimer )。回调返回HRTIMER_RESTART表示重新启动,HRTIMER_NORESTART表示停止。

使用hrtimer_start启动定时器,参数包含ktime_set或ktime_from_ms等转换函数。高精度定时器的回调同样在软中断上下文(高精度定时器软中断)执行linux 定时器驱动,依然不能睡眠。如果需要周期触发,可以在回调中调用hrtimer_forward然后返回HRTIMER_RESTART。注意hrtimer的overrun检测可以告诉你回调延迟了多少次,这在实时系统中非常有用。对于大多数普通驱动,标准定时器已经足够,只有音频、工业控制等对时间要求严格的场景才需要hrtimer。

你在驱动开发中遇到过定时器回调里访问共享数据导致死锁的问题吗?欢迎在评论区分享你的排查经验,点赞和转发让更多开发者避开这些坑。

Tagged:
Author

这篇优质的内容由TA贡献而来

刘遄

《Linux就该这么学》书籍作者,RHCA认证架构师,教育学(计算机专业硕士)。

发表回复