07、中断下半部:共享工作队列的使用
工作队列是管理任务的机制,用来把需要处理的任务按顺序排好队,再由多个线程或进程来执行这些任务。它的核心逻辑很简单:新任务来时排到队列末尾,工作线程或进程从队列开头取任务执行。
和tasklet的区别是:
- 能否暂停:tasklet不能暂停(比如等待磁盘读写),而工作队列可以暂停,适合处理耗时任务。
- 任务类型:tasklet适合处理短小但需要立即执行的任务,工作队列适合处理需要较长时间或可能卡住的任务。
工作队列的执行方式是:
- 系统启动时会创建一个"后台线程",默认处于等待状态。
- 当有任务要处理时,这个线程会被叫醒执行任务,完成后又进入等待状态。
工作队列分两种:
- 共享队列:多个程序共用同一个任务队列,适合简单任务。
- 自定义队列:自己创建独立队列,能更灵活控制任务执行顺序和优先级。
简单来说,工作队列就像一个任务调度员,把活儿先收着,等有空闲的"小助手"(内核线程)时再分派处理。而tasklet更像是一个不能喘气的员工,必须一口气做完手头活,不能中途停下来等东西。
一、工作队列相关接口函数
- 初始化:先定义
work_struct
,再用宏配置任务函数。 - 调度:调用
schedule_work()
把任务丢进队列。 - 取消:用
cancel_work_sync()
强行终止(会等当前执行结束)。
1.1、初始化工作项
- 定义工作项 直接声明一个
work_struct
类型的变量,例如:
C
struct work_struct my_work;
1
初始化方式
- 方法1:单独初始化 使用
INIT_WORK
宏,传入工作项变量和处理函数:
- 方法1:单独初始化 使用
C
INIT_WORK(&my_work, my_work_handler);
1
其中 my_work_handler
是你自定义的处理函数(后面会执行的任务)。
- 方法2:一步到位 使用
DECLARE_WORK
宏直接定义并初始化:
C
DECLARE_WORK(my_work, my_work_handler);
1
1.2、调度与取消工作项
- 安排执行 调用
schedule_work()
将工作项加入队列:
C
bool success = schedule_work(&my_work);
1
- 作用:把你的任务放进系统的工作队列,等待空闲时自动执行。
- 返回值:
true
表示成功加入队列,false
表示任务已经在执行中。 - 取消执行 调用
cancel_work_sync()
中止任务:
C
bool canceled = cancel_work_sync(&my_work);
1
- 作用:如果任务还在队列里,立刻移除它;如果任务已经开始执行,会等它执行完再取消。
- 返回值:
true
表示成功取消,false
表示任务已经执行完毕或无法取消。
二、共享工作队列实验源码
说明:
::: 硬件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
C
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操作
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
2.2、驱动部分代码
C
#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 work_struct test_workqueue;
// 工作项处理函数
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);
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;
}
// 初始化工作项
INIT_WORK(&test_workqueue, test_work);
return 0;
}
static void interrupt_irq_exit(void)
{
free_irq(irq, NULL); // 释放中断
printk("bye bye\n");
}
module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linux");
1
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
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
三、实验截图
驱动加载:insmod interrupt.ko
拉高GPIO1_A4