Linux驱动中delayed_workqueue使用:

本文介绍Linux内核中delayed workqueue的使用方法,包括定义、初始化及如何通过timer实现延迟执行任务。适用于中断受限场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



在驱动中,有时不能使用中断(这种情况很少遇到),此时delayed_workqueue就可以发挥其巨大的功效了。也可以用其它同类的内核API实现:如timer.delayed_workqueue的使用过程如下:

(1)定义workqueue: struct workqueue_struct *test_workqueue; //  kernel/workqueue.c

(2)定义workqueue要做的delayed工作:struct delayed_work test_delayed_work;// include/linux/workqueue.h

(3)初始化workqueue:INIT_DELAYED_WORK(&test_delayed_work,test_workqueue_func); //定义在:include/linux/workqueue.h

(4) 创建线程queue并加以名字:test_workqueue = create_singlethread_workqueue("name_of_this_queue");

(5)运行queue:queue_delayed_work(test_workqueue, &test_delayed_work, delay_time); //定义在:kernel/workqueue.c, 其中delay_time是延迟多少时间来运行queue。

注: 在test_workqueu_func中一定要再次将delayed_workqueue加入queue中,即再次运行queue_delayed_work。否则,此queue只运行一次。

下面详细讲解每个函数。

#define INIT_DELAYED_WORK(_work, _func)             \  
  1.     do {                            \  
  2.         INIT_WORK(&(_work)->work, (_func));      \  
  3.         init_timer(&(_work)->timer);         \  
  4.     } while (0)  
#define INIT_DELAYED_WORK(_work, _func)				\
	do {							\
		INIT_WORK(&(_work)->work, (_func));		\
		init_timer(&(_work)->timer);			\
	} while (0)
#define INIT_WORK(_work, _func)                     \  

    do {                                \  

  1.         (_work)->data = (atomic_long_t) WORK_DATA_INIT();    \  
  2.         INIT_LIST_HEAD(&(_work)->entry);         \  
  3.         PREPARE_WORK((_work), (_func));             \  
  4.     } while (0)  
  5. #endif  
  6.   
  7. // WORK_DATA_INIT() == 0;  
  8. // PREPARE_WORK((_work), (_func)) == (_work)->func = (_func);  
  9.   
  10. struct delayed_work {  
  11.     struct work_struct work;  
  12.     struct timer_list timer;  
  13. };  
#define INIT_WORK(_work, _func)						\
	do {								\
		(_work)->data = (atomic_long_t) WORK_DATA_INIT();	\
		INIT_LIST_HEAD(&(_work)->entry);			\
		PREPARE_WORK((_work), (_func));				\
	} while (0)
#endif

// WORK_DATA_INIT() == 0;
// PREPARE_WORK((_work), (_func)) == (_work)->func = (_func);

struct delayed_work {
	struct work_struct work;
	struct timer_list timer;
};

INIT_WORK的主要工作是data赋值为0; 初始化entry链表;将_func传给work的_func。
#define init_timer(timer) init_timer_key((timer), NULL, NULL)  
#define init_timer(timer) init_timer_key((timer), NULL, NULL)

init_timer_key((timer), NULL, NULL) --> debug_time_init [ NOP]

使用delayed workqueue最主要的是调用queue_delayed_work。如果delay = 0,则直接调用:queue_work 其具体实现过程:queue_work --> queue_work_on --> __queue_work --> insert_work

如果delay !=0,则调用:queue_delayed_work_on

 其具体实现过程:queue_delayed_work_on 此函数是通过timer来完成最后的延时工作。

<think>我们正在讨论Linux内核中的工作队列(workqueue)机制,特别是针对用户提供的两行代码: 1. `accdet->accdet_workqueue = create_singlethread_workqueue("accdet");` 2. `INIT_WORK(&accdet->accdet_work, accdet_work_callback);` 现在用户进一步询问关于`queue_work`函数的作用及其在内核工作队列机制中的意义,并希望了解在`accdet`工作队列调度机制中的具体应用。 ### 1. `queue_work`函数的作用 `queue_work`是Linux内核中用于将工作项(work item)提交到工作队列(workqueue)的函数。其函数原型通常为: ```c bool queue_work(struct workqueue_struct *wq, struct work_struct *work); ``` - **参数说明**: - `wq`:指向目标工作队列的指针,即之前通过`create_singlethread_workqueue`或`create_workqueue`等函数创建的工作队列。 - `work`:指向要提交的工作项的指针,该工作项必须已经通过`INIT_WORK`宏初始化过。 - **返回值**:如果成功将工作项加入队列,则返回`true`;如果工作项已经在队列中(即尚未执行),则返回`false`(避免重复入队)[^1][^2]。 ### 2. 工作队列机制中的意义 工作队列是Linux内核中用于异步执行任务的一种机制。它允许内核代码将任务(通常是在中断处理程序或其它不能睡眠的上下文中触发的任务)延迟到进程上下文中执行。这样,任务就可以使用所有进程上下文的特性,如睡眠(调用可能阻塞的函数)、访问用户空间内存等。 `queue_work`函数在这个机制中扮演着“任务提交者”的角色: - **异步执行**:当调用`queue_work`时,它只是将工作项(包含回调函数指针)加入到指定的工作队列中,然后立即返回。实际的任务(即回调函数)会在未来的某个时间点由与该工作队列关联的内核线程(称为worker thread)执行。 - **解耦关键路径**:在中断处理等时间敏感的上下文中,我们可以快速将耗时的任务(如硬件状态读取、数据处理等)通过`queue_work`提交到工作队列,从而避免阻塞中断处理流程[^1][^3]。 - **任务调度控制**:工作队列可以是单线程的(如使用`create_singlethread_workqueue`创建)或多线程的(如使用`create_workqueue`创建)。单线程工作队列保证所有提交到该队列的任务按入队顺序串行执行,避免了并发问题;而多线程工作队列则可以在多个CPU核心上并行处理任务,提高吞吐量[^1][^3]。 ### 3. 在`accdet`工作队列调度机制中的应用 结合用户提供的代码片段,我们可以推测`accdet`是一个设备驱动(可能是耳机检测驱动)的结构体。它包含: - `accdet_workqueue`:指向一个单线程工作队列的指针。 - `accdet_work`:一个工作项(`work_struct`结构体),用于封装待执行的任务。 典型的工作流程如下: 1. **初始化阶段**(通常在驱动的`probe`函数中): - 创建单线程工作队列:`accdet->accdet_workqueue = create_singlethread_workqueue("accdet");` - 初始化工作项并绑定回调函数:`INIT_WORK(&accdet->accdet_work, accdet_work_callback);` 2. **任务触发阶段**(例如在中断处理函数中): ```c // 伪代码:在中断上下文中检测到耳机插拔事件 irq_handler(...) { // 快速读取必要的中断状态 ... // 提交工作项到工作队列,由工作队列线程在进程上下文中执行详细处理 queue_work(accdet->accdet_workqueue, &accdet->accdet_work); return IRQ_HANDLED; } ``` 3. **任务执行阶段**: 工作队列线程(名为`accdet`)会在其运行循环中取出`accdet_work`工作项,并调用其绑定的回调函数`accdet_work_callback`。该函数可能是这样的: ```c static void accdet_work_callback(struct work_struct *work) { struct accdet_device *accdet = container_of(work, struct accdet_device, accdet_work); // 执行耗时的耳机状态检测和报告逻辑 ... } ``` - **注意**:由于工作队列在进程上下文运行,`accdet_work_callback`可以执行任意复杂的操作,包括睡眠、等待锁、调用可能阻塞的函数等。 ### 4. 使用`queue_work`的注意事项 - **避免重复入队**:如果同一个工作项尚未执行,再次调用`queue_work`会返回`false`,因此通常无需额外检查工作项是否已在队列中。 - **内存安全**:确保工作项在提交后和执行前不会被释放。通常工作项嵌入在设备结构体(如`accdet`)中,其生命周期与设备一致。 - **并发控制**:虽然单线程工作队列保证了串行执行,但如果多个中断源提交工作项到同一个队列,仍需注意共享数据的访问(例如使用锁保护`accdet`结构体中的共享数据)。 ### 总结 `queue_work`是Linux工作队列机制的核心提交接口,它实现了任务提交与执行的解耦,特别适合驱动中将中断等上下文中触发的耗时任务转移到进程上下文中安全执行。在`accdet`驱动中,它确保了耳机检测事件能够被可靠且高效地处理。 ### 相关问题 1. 如何取消一个已提交但尚未执行的工作项? 2. 工作队列与Tasklets、Softirqs有何区别?各自适用场景是什么? 3. 在多线程工作队列中如何保证数据访问的并发安全? 4. 工作队列的回调函数中是否可以再次提交自身(即递归提交)?会有什么风险? [^1]: Linux中的workqueue (2011-04-20 17:09:14) 转载▼ 标签: 杂谈 分类:Linux 工作队列 (work queue) 是 Linux kernel 中将工作推后执行的一种机制。这种机制和 BH 或 Tasklets 不同之处在于工作队列是把推后的工作交由一个内核线程去执行,因此工作队列的优势就在于它允许重新调度甚至睡眠。 [^2]: linux kernel 工作队列。基本上这一节咱们就是介绍了Linux内核中工作队列机制提供的接口,两对函数INIT_DELAYED_WORK()对schedule_delayed_work(),INIT_WORK()对schedule_work(). [^3]: Linux kernel 中的work queue原理。对work queue的深入理解需要了解Linux的调度机制,最基本的是Linux内核的调度时机,因为这关系到驱动程序开发者能对自己注册到任务队列函数的执行时机有大体的了解。</think>### Linux内核 `queue_work` 函数解析 #### 1. `queue_work` 的核心作用 `queue_work` 是 Linux 内核工作队列机制的核心调度函数,用于将初始化好的工作项(work item)提交到指定的工作队列中。其函数原型为: ```c bool queue_work(struct workqueue_struct *wq, struct work_struct *work); ``` - **参数解析**: - `wq`:目标工作队列(如 `accdet->accdet_workqueue`) - `work`:已初始化的 `work_struct` 结构(如 `&accdet->accdet_work`) - **返回值**: - `true`:工作项成功加入队列 - `false`:工作项已在队列中(避免重复入队) #### 2. 在 `accdet` 驱动中的工作流程 结合您提供的代码,典型调用场景如下: ```c // 1. 创建单线程工作队列(驱动初始化阶段) accdet->accdet_workqueue = create_singlethread_workqueue("accdet"); // 2. 初始化工作项并绑定回调函数 INIT_WORK(&accdet->accdet_work, accdet_work_callback); // 3. 在中断/事件触发时提交任务(如耳机插拔检测) void irq_handler(...) { queue_work(accdet->accdet_workqueue, &accdet->accdet_work); // 关键调度点 } ``` #### 3. 内核机制深度解析 1. **任务调度本质** `queue_work` 将工作项加入工作队列的待执行链表,触发内核调度器唤醒关联的 **worker thread**(对单线程队列即 `kworker/uX:accdet`)。该线程在进程上下文执行回调函数 `accdet_work_callback`,避免了中断上下文的限制[^1][^3]。 2. **单线程队列特性** 使用 `create_singlethread_workqueue` 创建的队列具有关键特性: - 所有工作项 **串行执行**(FIFO 顺序) - 避免并发冲突(无需额外加锁) - 适用于顺序敏感型硬件操作(如耳机状态检测)[^1][^4] 3. **执行时机与调度** 工作项的执行受内核调度策略影响: ```mermaid graph LR A[queue_work提交] --> B[工作项加入队列] B --> C{调度器检查} C -->|线程可运行| D[在进程上下文执行回调] C -->|线程阻塞| E[延迟至调度唤醒] ``` 在支持内核抢占(CONFIG_PREEMPT)的系统中,高优先级任务可抢占正在执行的工作项[^3]。 #### 4. 关键设计意义 1. **中断上下文解耦** 允许在中断处理函数中快速提交任务,将耗时操作(如硬件状态轮询、数据上报)转移到进程上下文[^1]。 2. **异步执行保障** 确保回调函数可安全使用: - 睡眠操作(`msleep()`) - 内存分配(`kmalloc(GFP_KERNEL)`) - 用户空间数据访问(`copy_to_user()`) 3. **资源隔离性** 单线程队列为 `accdet` 驱动提供专属执行环境,避免其他子系统工作项干扰[^4]。 #### 5. 典型使用约束 ```c if (!queue_work(accdet->accdet_workqueue, &accdet->accdet_work)) { pr_debug("Work item already queued\n"); // 避免重复提交 } ``` - **内存安全**:工作项所属结构体(如 `accdet`)必须保持有效直至回调完成 - **并发控制**:单线程队列虽串行执行,但若多中断源提交任务需注意共享数据保护 ### 相关问题 1. 如何取消已提交但未执行的工作项?`cancel_work_sync` 和 `cancel_delayed_work` 有何区别? 2. 工作队列与 Tasklets/SoftIRQs 在延迟任务处理上有何本质差异?[^1][^2] 3. 在多核系统中,`create_workqueue` 和 `create_singlethread_workqueue` 的性能影响有何不同? 4. 工作队列回调函数中再次调用 `queue_work` 提交自身是否安全?会导致什么后果? [^1]: 工作队列允许将任务推后到内核线程执行,支持睡眠操作,这是与 Tasklets 的关键区别。 [^2]: Linux 工作队列提供两对核心接口:`INIT_WORK`/`queue_work` 用于即时任务,`INIT_DELAYED_WORK`/`schedule_delayed_work` 用于延迟任务。 [^3]: 工作队列执行时机受内核调度策略影响,在允许抢占的内核中,高优先级任务可抢占正在执行的工作项。 [^4]: 单线程工作队列通过串行化执行避免并发冲突,适合硬件状态检测等顺序敏感型操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值