08、中断下半部:自定义工作队列的使用
共享队列是操作系统内核统一管理的公共任务池,所有程序都可以使用这个共用的队列来处理任务。而自定义队列则是由操作系统或设备驱动专门创建的独立任务池,用来执行特定的定制化操作。
简单来说:
- 共享队列:系统自带的公共任务通道,大家都可以用
- 自定义队列:专门定制的任务通道,为特定功能服务
两者的主要区别在于:共享队列是系统级通用工具,自定义队列是针对具体需求设计的专用工具。
一、自定义工作队列介绍
1.1、工作队列相关结构体
① work_struct 工作项:
在 Linux
内核中,结构体 struct work_struct
描述的是要**延迟执行的****工作项**,定义在include/linux/workqueue.h
当中,如下所示:
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
2
3
4
5
6
7
8
::: 主要成员:
atomic_long_t data:存储工作项数据的字段,支持安全的多线程访问
struct list_head entry:工作项在队列中的链表节点,用于将工作项连接到队列中
work_func_t func:工作项执行时调用的处理函数 :::
② workqueue_struct 工作队列
这些工作组织成工作队列,内核使用 struct workqueue_struct
结构体描述一个工作队列,定义在 kernel/workqueue.c 当中,如下所示:
/*
* The externally visible workqueue. It relays the issued work items to
* the appropriate worker_pool through its pool_workqueues.
*/
struct workqueue_struct {
struct list_head pwqs; /* WR: all pwqs of this wq */
struct list_head list; /* PR: list of all workqueues */
struct mutex mutex; /* protects this wq */
int work_color; /* WQ: current work color */
int flush_color; /* WQ: current flush color */
atomic_t nr_pwqs_to_flush; /* flush in progress */
struct wq_flusher *first_flusher; /* WQ: first flusher */
struct list_head flusher_queue; /* WQ: flush waiters */
struct list_head flusher_overflow; /* WQ: flush overflow list */
struct list_head maydays; /* MD: pwqs requesting rescue */
struct worker *rescuer; /* I: rescue worker */
int nr_drainers; /* WQ: drain in progress */
int saved_max_active; /* WQ: saved pwq max_active */
struct workqueue_attrs *unbound_attrs; /* PW: only for unbound wqs */
struct pool_workqueue *dfl_pwq; /* PW: only for unbound wqs */
#ifdef CONFIG_SYSFS
struct wq_device *wq_dev; /* I: for sysfs interface */
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
char name[WQ_NAME_LEN]; /* I: workqueue name */
/*
* Destruction of workqueue_struct is sched-RCU protected to allow
* walking the workqueues list without grabbing wq_pool_mutex.
* This is used to dump all workqueues from sysrq.
*/
struct rcu_head rcu;
/* hot fields used during command issue, aligned to cacheline */
unsigned int flags ____cacheline_aligned; /* WQ: WQ_* flags */
struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
1.2、工作队列相关接口函数
1. 创建工作队列
create_workqueue(name)
- 作用:创建一个工作队列,每个CPU都会对应一个独立的线程。
- 参数:
name
是队列的名字。 - 返回值:成功返回队列的指针,失败返回
NULL
。
create_singlethread_workqueue(name)
- 作用:创建一个仅单线程的工作队列,不管有多少CPU,只创建一个线程。
- 参数:
name
是队列的名字。 - 返回值:成功返回队列的指针,失败返回
NULL
。
2. 将工作项添加到队列
queue_work_on(cpu, wq, work)
作用:把一个工作项立即放入指定CPU的队列,等待执行。
参数:
cpu
:目标CPU编号(如0
表示第一个CPU)。wq
:目标队列的指针。work
:要执行的工作项(需提前初始化)。
返回值:成功返回
true
,失败返回false
。
3. 取消已调度的工作
cancel_work_sync(work)
- 作用:取消已放入队列但未执行的工作。
- 如果工作正在执行:会等待它执行完毕再返回。
- 参数:
work
是要取消的工作项指针。 - 返回值:成功返回
true
,失败返回false
。
4. 强制处理队列中的所有工作
flush_workqueue(wq)
- 作用:立即执行队列中所有未完成的工作,直到全部处理完毕。
- 参数:
wq
是要处理的队列指针。
5. 删除工作队列
destroy_workqueue(wq)
- 作用:释放队列资源(如内存和线程)。
- 前提:必须确保队列中的所有工作已执行完毕(可通过
flush_workqueue
完成)。 - 参数:
wq
是要删除的队列指针。
使用流程总结
- 创建队列:用
create_workqueue
或create_singlethread_workqueue
。 - 添加工作:用
queue_work_on
将工作项放入队列。 - 取消工作:用
cancel_work_sync
终止未执行的工作。 - 清理队列:用
flush_workqueue
确保所有工作完成,再用destroy_workqueue
删除队列。
关键注意事项
- 单线程队列:
create_singlethread_workqueue
的线程是串行执行的,同一时间只能处理一个工作项。 - 多线程队列:
create_workqueue
的每个CPU线程独立运行,可能同时处理多个工作项。 - 删除前必须清理:删除队列前必须用
flush_workqueue
保证所有工作已完成,否则可能导致数据丢失或崩溃。
二、自定义共享队列案例
说明:
::: 硬件IO表:https://lceda001.feishu.cn/wiki/UN0pw7Y1KixKirkMh0rcP3Tandc
操作第七个管脚触发中断,对应GPIO1_A4 ::: ::: 引脚编号计算:
公式1:GPIO 小组编号计算公式:number = group * 8 + X
其中:group为小组号(A为0,B为1,C为2,D为3)
公式2:GPIO pin 脚计算公式:pin = bank * 32 + number
其中:bank 为组号,number由公式1计算得来
故:GPIO1_A4
1*32+0*8+4 = 36 :::
2.1、Makefile
export ARCH=arm64
export CROSS_COMPILE=/home/book/rk/tspi/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
obj-m +=interrupt.o #此处要和你的驱动源文件同名
KDIR := /home/book/rk/tspi/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make#操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操作
2
3
4
5
6
7
8
9
2.2、驱动代码
test_workqueue_work#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");
//schedule_work(&test_workqueue);
//提交一个任务 给 工作队列
//worker ---- 调度者
queue_work(test_workqueue, &test_workqueue_work); // 提交工作项到工作队列
return IRQ_RETVAL(IRQ_HANDLED);
}
static int interrupt_irq_init(void)
{
int ret;
irq = gpio_to_irq(36); // 将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 ---- workqueue_struct
//test_workqueue_work --- work_struct
//test_work --- 函数
test_workqueue_work= 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");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
2.3、实验截图
驱动加载:insmod interrupt.ko
拉高GPIO:
三、实现
workqueue.c