Linux内核定时器调用可睡眠函数方法
Linux内核中如何在定时器中调用可睡眠的函数
在Linux内核中,定时器回调函数(如通过timer_list
或hrtimer
注册的函数)通常运行在中断上下文中,而中断上下文的关键限制是不能调用任何可能引发睡眠的函数(例如使用互斥锁、内存分配GFP_KERNEL
或阻塞操作)。为了解决这一问题,必须将可睡眠的操作迁移到进程上下文中执行。以下是实现这一目标的步骤和示例代码:
方法:使用工作队列(Workqueue)
工作队列(Workqueue)是内核中用于在进程上下文中执行任务的机制。通过将任务提交到工作队列,可以安全地调用可睡眠函数。
实现步骤:
定义工作队列和定时器:
声明一个工作队列结构体和一个定时器结构体。初始化工作队列和定时器:
在模块初始化时,绑定工作队列的处理函数,并配置定时器的回调。在定时器回调中调度工作队列:
定时器触发时,在中断上下文中调度工作队列任务,而非直接执行可睡眠操作。在工作队列处理函数中执行睡眠操作:
工作队列的处理函数在进程上下文中运行,允许调用可睡眠函数。清理资源:
在模块卸载时,确保取消定时器和工作队列的任务。
示例代码
1 |
|
关键点解释
工作队列的作用:
schedule_work(&my_work)
将任务提交到内核的全局工作队列(system_wq
),该队列由内核线程管理,任务在进程上下文中执行。- 使用
INIT_WORK
初始化工作项,并绑定处理函数my_work_handler
。
定时器的限制:
- 定时器回调函数
my_timer_callback
仅负责快速调度工作队列,不执行任何可能阻塞的操作。
- 定时器回调函数
进程上下文的优势:
- 在工作队列处理函数中,可以安全调用如
msleep()
、mutex_lock()
或kmalloc(GFP_KERNEL)
等函数。
- 在工作队列处理函数中,可以安全调用如
替代方案
内核线程(Kernel Thread):
手动创建内核线程并通过信号量或等待队列同步任务。灵活性更高,但实现更复杂。Tasklet 或 SoftIRQ:
不适用,因为它们同样运行在中断上下文。延迟工作队列(Delayed Workqueue):
使用schedule_delayed_work()
实现延迟执行,适用于需要延时的场景。
注意事项
- 资源竞争:确保工作队列处理函数是可重入的,或在并发访问时使用锁机制。
- 内存安全:在模块卸载时,通过
flush_work()
等待工作队列任务完成,防止访问已释放的内存。 - 定时器重置:若需周期性任务,可在定时器回调中调用
mod_timer()
重新激活。
通过上述方法,可以安全地在定时器中间接调用可睡眠函数,避免内核崩溃或不可预测的行为。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Helloeuler!