第10章:中断处理-11: Workqueues

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 来安排下半部处理。

补充说明:

  1. 工作队列的核心特性

    • 进程上下文:工作队列函数运行在内核线程(如 kworker 进程)的上下文,因此可以安全调用睡眠函数(如 kmalloc(GFP_KERNEL)wait_event),这是与 tasklet(软中断上下文,不可睡眠)的核心区别。

    • 延迟执行:通过 schedule_work 调度后,工作函数会在系统负载允许时执行,延迟可能比 tasklet 更高,但适合处理耗时任务。

    • 重复调度安全:多次调用 schedule_work 对同一 work_struct 不会导致重复执行(内核会自动忽略已在队列中的工作),因此无需额外判断。

  2. 系统默认工作队列与专用工作队列

    • 系统默认工作队列(init_workqueue)由所有驱动共享,适合简单场景;

    • 若驱动的工作函数可能长时间阻塞(如等待外部设备响应),应创建专用工作队列(create_workqueue),避免影响其他驱动的工作调度:
      struct workqueue_struct *my_wq = create_workqueue("my_driver_wq");
      queue_work(my_wq, &my_work);  // 向专用队列提交工作
      
  3. 工作队列与用户空间数据交互的限制

    工作队列运行在独立的内核线程上下文,没有绑定到特定用户进程,因此直接调用 copy_to_user 会失败(无法访问用户进程地址空间)。如需与用户空间交互,需通过异步通知(如信号)或等待队列让用户进程主动读取数据(如 short 驱动中通过 wake_up_interruptible 唤醒读进程)。

  4. 与 tasklet 的选择依据

    • 若下半部处理无需睡眠(如简单数据拷贝、状态更新),优先使用 tasklet(速度更快,适合高频中断);

    • 若需要睡眠或执行耗时操作(如复杂计算、I/O 操作),必须使用工作队列。

  5. 工作队列的执行时机

    ​​​​​​​工作函数由内核线程调度执行,具体时机取决于系统调度策略,可能在中断处理完成后、其他进程让出 CPU 时,或定期调度点触发。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值