In continuation of the previous text 第10章:中断处理-10: Tasklets, let's GO ahead.
Workqueues
Recall that workqueues invoke a function at some future time in the context of a spe cial worker process. Since the workqueue function runs in process context, it can sleep if need be. You cannot, however, copy data into user space from a workqueue, unless you use the advanced techniques we demonstrate in Chapter 15; the worker process does not have access to any other process’s address space.
回想一下,工作队列会在未来某个时间点,在一个专门的工作进程(worker process)上下文中调用函数。由于工作队列函数运行在进程上下文中,因此必要时可以睡眠。不过,不能从工作队列直接向用户空间复制数据(除非使用第 15 章介绍的高级技术),因为工作进程无法访问其他进程的地址空间。
The short driver, if loaded with the wq option set to a nonzero value, uses a work queue for its bottom-half processing. It uses the system default workqueue, so there is no special setup code required; if your driver has special latency requirements (or might sleep for a long time in the workqueue function), you may want to create your own, dedicated workqueue. We do need a work_struct structure, which is declared and initialized with the following:
如果 short 驱动加载时设置了非零的 wq 参数,它会使用工作队列进行下半部处理。这里使用的是系统默认工作队列,因此无需特殊的初始化代码;如果你的驱动对延迟有特殊要求(或者工作队列函数可能长时间睡眠),则可能需要创建自己的专用工作队列。
我们需要一个 work_struct 结构体,其声明和初始化如下:
static struct work_struct short_wq;
/* this line is in short_init() */
INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);
Our worker function is short_do_tasklet, which we have already seen in the previous section. When working with a workqueue, short establishes yet another interrupt handler that looks like this:
工作函数是 short_do_tasklet,我们在上一节已经见过这个函数。当使用工作队列时,short 会注册另一个中断处理程序,代码如下:
irqreturn_t short_wq_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
/* Grab the current time information. */
do_gettimeofday((struct timeval *) tv_head);
short_incr_tv(&tv_head);
/* Queue the bh. Don't worry about multiple enqueueing */
schedule_work(&short_wq);
short_wq_count++; /* record that an interrupt arrived */
return IRQ_HANDLED;
}
As you can see, the interrupt handler looks very much like the tasklet version, with the exception that it calls schedule_work to arrange the bottom-half processing.
可以看到,这个中断处理程序与 tasklet 版本非常相似,唯一的区别是它调用 schedule_work 来安排下半部处理。
补充说明:
-
工作队列的核心特性
-
进程上下文:工作队列函数运行在内核线程(如
kworker进程)的上下文,因此可以安全调用睡眠函数(如kmalloc(GFP_KERNEL)、wait_event),这是与 tasklet(软中断上下文,不可睡眠)的核心区别。 -
延迟执行:通过
schedule_work调度后,工作函数会在系统负载允许时执行,延迟可能比 tasklet 更高,但适合处理耗时任务。 -
重复调度安全:多次调用
schedule_work对同一work_struct不会导致重复执行(内核会自动忽略已在队列中的工作),因此无需额外判断。
-
-
系统默认工作队列与专用工作队列
-
系统默认工作队列(
init_workqueue)由所有驱动共享,适合简单场景; - 若驱动的工作函数可能长时间阻塞(如等待外部设备响应),应创建专用工作队列(
create_workqueue),避免影响其他驱动的工作调度:struct workqueue_struct *my_wq = create_workqueue("my_driver_wq"); queue_work(my_wq, &my_work); // 向专用队列提交工作
-
-
工作队列与用户空间数据交互的限制
工作队列运行在独立的内核线程上下文,没有绑定到特定用户进程,因此直接调用
copy_to_user会失败(无法访问用户进程地址空间)。如需与用户空间交互,需通过异步通知(如信号)或等待队列让用户进程主动读取数据(如short驱动中通过wake_up_interruptible唤醒读进程)。 -
与 tasklet 的选择依据
-
若下半部处理无需睡眠(如简单数据拷贝、状态更新),优先使用 tasklet(速度更快,适合高频中断);
-
若需要睡眠或执行耗时操作(如复杂计算、I/O 操作),必须使用工作队列。
-
-
工作队列的执行时机
工作函数由内核线程调度执行,具体时机取决于系统调度策略,可能在中断处理完成后、其他进程让出 CPU 时,或定期调度点触发。

被折叠的 条评论
为什么被折叠?



