11、中断下半部:并发管理工作队列实验
在现代软件开发中,我们经常需要同时处理多个任务。这些任务可能需要同时进行,或者按照一定顺序处理。为了更高效地管理这些任务,我们需要一种工具来帮助它们有序运行。这时候,"并发管理工作队列"就派上用场了。
一、工作队列的实现
在使用工作队列时,流程通常分为三步:
- 先定义一个任务项(work)
- 把任务添加到任务队列(workqueue)里
- 工作线程(后台线程)会自动执行队列里的任务
当有新任务加入队列时:
- 工作线程会取出并执行所有待处理的任务
- 执行完毕后线程会暂停等待
- 等到有新任务加入队列时,线程再次被唤醒执行
- 这个过程会不断循环重复
在单核系统中: 每个CPU核心都会:
- 自动创建一个专属的工作线程
- 每个线程对应自己的任务队列 这样每个CPU都能独立处理自己的任务队列,避免互相干扰。这种设计让每个CPU都能高效处理自己的工作项。
在多核系统中,工作队列的设计和单核系统不一样。单核系统通常只有一个任务队列和一个线程,而多核系统会为每个核心分配一个独立的任务队列和线程。这样每个线程可以同时处理自己队列里的任务,充分利用多个核心一起干活。
当有新任务产生时,系统需要决定把它放进哪个队列。常见的做法是根据各个队列的任务量来分配,比如优先给任务少的队列加任务,避免某个队列任务太多而变慢。每个队列自己管理自己的任务,对应的线程会从自己的队列里取任务执行。这样一来,多个线程可以同时处理各自的任务,提升整体效率和响应速度。
二、什么是并发管理工作队列
传统的工作队列在处理任务时存在明显不足。无论是单核还是多核系统,都存在两个主要问题:一是无法充分发挥多核处理器的性能,二是无法公平处理不同优先级的任务。为了解决这些问题,Con Kolivas提出了名为"并发管理工作队列"(CMWQ)的解决方案。
简单来说,CMWQ就像一家繁忙餐厅的点餐系统。假设你和同事一起在餐厅工作,当很多顾客同时来吃饭时:
- 所有点餐请求都会放进一个共享的"任务队列"里
- 你和同事可以同时从队列里领取任务
- 每个人独立完成自己的服务工作
- 不用等前一位顾客点完才能处理下一位
这种工作方式的好处很明显:
- 充分利用所有服务员的能力(就像多核处理器的每个核心都能被用上)
- 任务分配更公平(不同优先级的订单可以被合理安排)
- 整个系统运行效率更高(顾客等待时间更短)
CMWQ的核心设计就是通过这种"多人同时取任务"的机制,让系统资源得到最佳利用。就像餐厅里多个服务员能同时处理不同订单一样,计算机的多个处理器核心也能同时处理不同任务,从而提升整体性能。这种设计让任务处理更高效、更灵活,尤其适合需要同时处理大量工作的复杂系统。
三、并发管理工作队列接口函数
Linux内核中的alloc_workqueue函数用来创建工作队列。工作队列是一个管理任务执行的工具,它能安排任务在后台或同时运行,让程序不用等待任务完成就能继续执行其他操作。
struct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags, int max_active);
参数解释:
- fmt:设置工作队列的名字格式。
- flags:通过标志位控制队列行为,例如WQ_UNBOUND表示不绑定CPU,WQ_HIGHPRI表示高优先级等。
- max_active:设定队列中同时处理的工作项最大数量。
函数会返回指向工作队列结构体的指针,若创建失败则返回NULL。
四、实验程序
说明:
::: 硬件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 :::
Makefiel:
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
驱动代码:
#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(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 = alloc_workqueue("test_workqueue", WQ_UNBOUND, 0);
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
驱动加载:insmod interrupt.ko
拉高GPIO: