Linux驱动-中断-自定义工作队列

熟悉了解自定义工作队列。对比着前面tasklet 和 共享工作队列,熟悉相关基础内容。


概念

共享队列是由内核管理的全局工作队列, 自定义工作队列是由内核或驱动程序创建的特定工作队列, 用于处理特定的任务。
在 Linux 驱动开发中,workqueue_struct 代表工作队列,是内核提供的延迟执行机制,常用于中断处理中的"底半部"(bottom half)处理。

工作队列-工作项结构体

结构体 struct work_struct 描述的是要延迟执行的工作项,如下:

struct work_struct {
atomic_long_t data; // 工作项的数据字段
struct list_head entry; // 工作项在工作队列中的链表节点
work_func_t func; // 工作项的处理函数
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map; // 锁依赖性映射
#endif
};

这些工作组织成工作队列, 内核使用 struct workqueue_struct 结构体描述一个工作队列

struct workqueue_struct {
struct list_head pwqs; // 工作队列上的挂起工作项列表
struct list_head delayed_works; // 延迟执行的工作项列表
struct delayed_work_timer dwork_timer; // 延迟工作项的定时器
struct workqueue_attrs *unbound_attrs; // 无绑定工作队列的属性
struct pool_workqueue *dfl_pwq; // 默认的池化工作队列
...
};

一、参考资料

Linux驱动-中断-共享队列
创建自定义工作队列
linux中断下文工作队列之自定义工作队列(中断五)

二、核心API

创建自定义工作队列

struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);
struct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags, int max_active);
  • create_workqueue():创建多线程工作队列(每个CPU一个工作者线程)
  • create_singlethread_workqueue():创建单线程工作队列
  • alloc_workqueue():更灵活的创建工作队列方式(推荐新代码使用)

参数 name 是创建的工作队列的名字。使用这个函数可以给每个 CPU 都创建一个 CPU 相关的工作队列。创建成功返回一个 struct workqueue_struct 类型指针,创建失败返回 NULL。

销毁工作队列

void destroy_workqueue(struct workqueue_struct *wq);

初始化工作项

INIT_WORK(struct work_struct *work, work_func_t func);
INIT_DELAYED_WORK(struct delayed_work *work, work_func_t func);
  • INIT_WORK:初始化立即执行的工作项
  • INIT_DELAYED_WORK:初始化可延迟执行的工作项

调度工作到自定义队列

bool queue_work(struct workqueue_struct *wq, struct work_struct *work);
bool queue_delayed_work(struct workqueue_struct *wq, 
                       struct delayed_work *work, 
                       unsigned long delay);

当工作队列创建好之后,需要将要延迟执行的工作项放在工作队列上,调度工作队列,使用 queue_work_on 函数(最新api 推荐)。

这里温习下之前共享队列里面,如何调度的?如下: 对比看一看

bool schedule_work(struct work_struct *work);
bool schedule_delayed_work(struct delayed_work *work, unsigned long delay);

刷新工作队列

void flush_workqueue(struct workqueue_struct *wq);
bool flush_work(struct work_struct *work);
bool cancel_work_sync(struct work_struct *work);
bool cancel_delayed_work_sync(struct delayed_work *dwork);
  • flush_workqueue:等待队列中所有工作完成
  • cancel_work_sync:取消并等待工作项完成

新式工作队列 API (推荐)

#define alloc_workqueue(fmt, flags, max_active, args...)
bool queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work);
bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq,
                        struct delayed_work *dwork, unsigned long delay);

三、驱动代码程序

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>

int irq;
struct workqueue_struct *test_workqueue;
struct work_struct test_workqueue_work;

// 工作项处理函数
void test_work(struct work_struct *work)
{
  msleep(1000);
  printk("This is test_work\n");
}

// 中断处理函数
irqreturn_t test_interrupt(int irq, void *args)
{
  printk("This is test_interrupt\n");
  queue_work(test_workqueue, &test_workqueue_work); // 提交工作项到工作队列
  return IRQ_RETVAL(IRQ_HANDLED);
}

static int interrupt_irq_init(void)
{
  int ret;
  irq = gpio_to_irq(101); // 将GPIO映射为中断号
  printk("irq is %d\n", irq);

  // 请求中断
  ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);
  if (ret < 0)
  {
    printk("request_irq is error\n");
    return -1;
  }

  test_workqueue = create_workqueue("test_workqueue"); // 创建工作队列
  INIT_WORK(&test_workqueue_work, test_work);          // 初始化工作项

  return 0;
}

static void interrupt_irq_exit(void)
{
  free_irq(irq, NULL);                    // 释放中断
  cancel_work_sync(&test_workqueue_work); // 取消工作项
  flush_workqueue(test_workqueue);        // 刷新工作队列
  destroy_workqueue(test_workqueue);      // 销毁工作队列
  printk("bye bye\n");
}

module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fangchen");

实验结果如下:
在这里插入图片描述

思考

  • 你看日志打印,为什么 This is test_interrupt 一直打印,This is test_work 只是打印了两次。 这里要搞清楚 msleep(1000); 用法。 如:msleep 阻塞导致调度延迟如果频繁调用 schedule_work(),而工作项中的 msleep(1000) 尚未完成,后续调度可能被合并或丢弃。

程序解读

单从程序上看,和共享队列程序对比:

  • 多了一个自定义的队列 workqueue_struct ,中断队列的定义->驱动启动后中断队列的创建
  • 对于工作项work_struct 的操作一模一样。
  • 在提交工作项 work_struct 到队列中,调用方法不一样而已

自定义队列和共享队列对比

特效自定义队列共享队列
资源开销高(独占)低(共享)
延迟较高
队列所有权驱动私有内核全局共享
实现复杂度简单(无需检查来源)复杂(需检查中断源)
适用硬件高性能专用设备通用设备(PCI、GPIO)
优先级控制可自定义(如 WQ_HIGHPRI)有限(依赖内核预定义队列)

总结

  • 作为中断知识点中的一个,首先了解并理解
  • 对比共享中断,用法基本一致,对比 tasklet,其实也差不多。但是 底层核心机制完全不一样。
  • 对于 自定义队列的用法,api 方法而已,后续慢慢会用到的,这里先通过测试 验证结果,熟悉流程。

回过头来,我们再从概念的角度理解下,自定义工作队列:

  • 工作队列(Work Queue):一种将工作推迟执行的机制,通常用于中断上下文之外执行任务

  • 中断上下文:中断处理程序执行时的特殊上下文,有诸多限制(不能睡眠、不能调用可能阻塞的函数等)

说白了 中断机制上下文就是指中断回调函数,它是不可阻塞的 不能阻塞的。 那又要在中断上下文里面处理阻塞的动作/业务怎么办? 那不就是通过内核机制 共享队列/自定义工作队列 甚至用一下 tasklet 这些内核机制或者内核相关机制去处理 IO 或者 其他阻塞操作嘛。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

野火少年

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

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

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

打赏作者

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

抵扣说明:

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

余额充值