09、中断下半部:延迟工作实验 *
一、什么是延迟工作
延迟工作就是把任务推迟到后面再处理的一种方法。简单说就是:如果某个任务需要花很长时间,或者暂时不需要立刻执行,就可以先放一放,等后面再处理。
具体来说就是:
- 把需要延迟的任务先放进一个"任务队列"里
- 让电脑后台的程序自动去处理这些排队的任务
这样做主要有两个好处:
- 解决耗时任务:比如发送邮件、处理照片这些需要时间的任务,可以先放到队列里,让用户继续操作其他功能不卡顿
- 定时执行任务:比如每天凌晨自动备份数据,或者设置在特定时间发送提醒
举个生活中的例子: 就像你在手机上写完短信点击发送后,还能继续刷视频一样。其实发送短信的任务被放在了队列里,系统会在不影响你使用的情况下默默完成它。再比如设置闹钟,你设定好时间后,手机不会立刻响铃,而是等到指定时间才执行提醒任务。
这种技术特别适合:
- 需要处理大量任务的场景(比如电商大促时的订单处理)
- 需要保持系统流畅的场景(比如网页提交表单后立即显示成功页面)
- 定时维护任务(比如每周日凌晨自动清理缓存)
理想型的按键电压变化过程如图:
理想情况下,按键没被按下时显示为1,按下后立即变为0。但实际情况中,由于按键是机械结构,加上手指按下的瞬间可能出现抖动,实际电压变化会像图中那样出现波动。
按键按下时,因为机械结构的抖动,电平信号会不稳定跳变(比如图中t1到t2这段时间)。这段时间内,即使只按了一次,系统可能误判为多次按下。为了解决这个问题,我们通过以下步骤处理:
- 触发检测:当按键第一次按下时,立刻启动一个20毫秒的计时器(这段时间足够覆盖抖动)。
- 等待稳定:在这20毫秒内,忽略所有按键状态的变化。
- 最终确认:20毫秒后,重新检查按键状态。如果此时按键仍然处于按下状态,就认定这是一次有效的按键操作。
这样就能避开抖动期的干扰,确保系统只记录一次按键动作。
二、 struct delayed_work
在 Linux 内核中, 使用 struct delayed_work 来描述延迟工作, 定义在include/linux/workqueue.h 当中, 原型定义如下所示:
struct delayed_work{
struct work_struct work;// 延迟工作的基本工作结构
struct timer_list timer;// 定时器,用于延迟执行工作
};
2
3
4
delayed_work
结构体用于实现延迟执行的任务,它包含两个关键部分:
- work:一个工作项结构(
work_struct
),用来定义具体要执行的任务内容; - timer:一个定时器结构(
timer_list
),用来设定任务的延迟时间和触发时机。
它的作用就像设置一个「定时闹钟」:把需要执行的任务放进这个结构里,通过定时器设定好延迟时间,等时间到了再自动执行任务。简单来说,就是先「预约」一个未来要执行的工作,等到了预定时间系统才会去处理它。
三、延迟工作相关接口函数
3.1、初始化延迟工作函数
① 静态定义延迟任务(DECLARE_DELAYED_WORK)
当需要预先定义并初始化一个延迟任务时,可以直接用这个宏:
DECLARE_DELAYED_WORK(变量名, 处理函数);
作用:一次性完成变量的定义和初始化。
参数:
变量名
:给这个延迟任务起的名字(如my_delay_work
)。处理函数
:任务执行时要调用的函数(如my_handler
)。
底层原理:会自动创建一个包含工作队列和定时器的结构体,并将函数绑定到任务中。
② 动态初始化延迟任务(INIT_DELAYED_WORK)
当需要在运行时动态初始化已声明的延迟任务时,用这个宏:
INIT_DELAYED_WORK(变量名, 处理函数);
前提:必须先用
struct delayed_work 变量名;
声明变量。作用:
- 初始化任务对应的工作队列。
- 初始化任务的定时器(用于延迟触发)。
参数:
变量名
:已声明的延迟任务变量(如my_delay_work
)。处理函数
:任务执行时要调用的函数(如my_handler
)。
简单对比
场景 | 静态定义(DECLARE) | 动态初始化(INIT) |
---|---|---|
何时使用 | 编译时预先定义变量和初始化 | 运行时动态初始化已声明的变量 |
语法 | DECLARE_DELAYED_WORK(var, func); | struct delayed_work var; INIT_DELAYED_WORK(&var, func); |
特点 | 一步到位,代码简洁 | 分两步操作,但更灵活 |
示例代码
静态定义:
// 定义并初始化一个名为 my_work 的延迟任务,触发时执行 handle_my_work 函数
DECLARE_DELAYED_WORK(my_work, handle_my_work);
2
动态初始化:
// 第一步:声明变量
struct delayed_work my_work;
// 第二步:在需要的时候初始化(如函数内)
INIT_DELAYED_WORK(&my_work, handle_my_work);
2
3
4
5
核心区别
- 静态宏:适合在全局或静态变量中直接定义,代码更简洁。
- 动态宏:适合在函数内部或需要延迟初始化的场景中使用。
3.2、调度/取消调度 延迟工作函数
① 使用共享工作队列调度
static inline bool schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
作用:在指定延迟时间后,安排一个延迟任务执行。
参数:
dwork
:要执行的任务对象(延迟工作)的指针。delay
:延迟时间(单位是内核时钟节拍数jiffies
)。
特点:直接使用系统默认的共享工作队列,无需自己指定队列。
② 使用自定义工作队列调度
static inline bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay)
作用:将延迟任务加入到指定的工作队列,在延迟时间后执行。
参数:
wq
:目标工作队列的指针(需要自己创建或指定)。dwork
:要执行的任务对象的指针。delay
:延迟时间(单位是jiffies
)。
特点:必须指定一个自定义的工作队列
wq
。
③ 取消延迟工作的函数
extern bool cancel_delayed_work_sync(struct delayed_work *dwork);
作用:取消已调度的延迟任务,并等待任务彻底完成。
参数:
dwork
是要取消的任务对象的指针。返回值:
true
:成功取消任务并等待完成。false
:任务已开始执行或无法取消。
总结:
调度任务:
- 如果用系统默认队列:用
schedule_delayed_work
。 - 如果用自定义队列:用
queue_delayed_work
,并指定队列wq
。
- 如果用系统默认队列:用
取消任务:用
cancel_delayed_work_sync
,确保任务被终止且完全停止。
三、实验代码
说明:
::: 硬件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 :::
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
#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 delayed_work 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_delayed_work(test_workqueue, &test_workqueue_work, 3 * HZ);
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 = create_workqueue("test_workqueue");
// 初始化延迟工作项
INIT_DELAYED_WORK(&test_workqueue_work, test_work);
return 0;
}
static void interrupt_irq_exit(void)
{
free_irq(irq, NULL); // 释放中断
cancel_delayed_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
四、实验截图
驱动加载:insmod interrupt.ko
拉高GPIO: