Skip to content

LV085-工作队列-延迟工作

前面我们学习了共享工作队列和自定义工作队列, 为了更形象地理解学习,将流水线比作工作队列, 流水线上一个个等待处理的物料比作一个个工作。 机器比作内核线程或进程。 在这里要学习的延迟工作, 可以类比为将物料延迟一定时间, 再放到生产线上加工。 延迟工作不仅可以在自定义工作队列中实现 也可以在共享工作队列上实现。 现在, 我们对延迟工作有了一个感性的认识, 接下来详细的学习下延迟工作吧。

一、延迟工作简介

1. 简介

延迟工作是一种将工作的执行延迟到稍后时间点进行处理的技术。 通常情况下, 当某个任务需要花费较长时间, 不需要立即执行或需要按时执行时, 延迟工作就会派上用场。

延迟工作的基本思想是将任务放入一个队列中, 然后由后台的工作进程会任务调度程序来处理队列中的任务。 任务可以在指定的延迟时间后执行, 也可以根据优先级, 任务类型或者其他条件进行排序和处理。

2. 应用场景

延迟工作在许多应用场景中都非常有用, 尤其是在需要处理大量任务, 提供系统性能和可靠性的情况下。 以下是一些常用的应用场景:

(1)延迟工作常用于处理那些需要花费较长时间的任务, 比如发送电子邮件, 处理图像等。通过将这些任务放入队列中并延迟执行, 可以避免阻塞应用程序的主线程, 提高系统的响应速度。

(2)延迟工作可以用来执行定时任务, 比如定时备份数据库, 通过将任务设置为在未来的某个时间点执行, 提高系统的可靠性和效率。

比如说开发板上的按键, 现在我们想通过驱动程序读取按键的状态, 那么只需要读取这个按键所连接的 GPIO 的状态就可以了。理想型的按键电压变化过程如图 :

image-20250322132412435

在上图中, 按键没有按下的时候按键值为 1, 当按键在 t1 时刻按键被按下以后按键值就变为 0, 这是最理想的状态。 但是实际的按键是机械结构, 加上刚按下去的一瞬间人手可能也有抖动, 实际的按键电压变化过程如下图 :

image-20250322132434049

在上图中, t1 时刻按键被按下, 但是由于抖动的原因, 直到 t2 时刻才稳定下来, t1 到 t2 这段时间就是抖动。 一般这段时间就是十几 ms 左右, 从上图中可以看出在抖动期间会有多次触发, 如果不消除这段抖动的话软件就会误判, 本来按键就按下了一次, 结果软件读取 IO 值发现电平多次跳变以为按下了多次。

所以我们需要跳过这段抖动时间再去读取按键的 IO 值,也就是至少要在 t2 时刻以后再去读 IO 值。我们可以使用内核定时器来实现消抖。 按键采用中断驱动方式, 当按键按下以后触发按键中断, 在按键中断中开启一个定时器,定时周期为 10ms, 当定时时间到了以后就会触发定时器中断, 最后在定时器中断处理函数中读取按键的值, 如果按键值还是按下状态那就表示这是一次有效的按键。定时器按键消抖如下图 :

image-20250322132616413

在上图中 t1~t3 这一段时间就是按键抖动, 是需要消除的。 设置按键为下降沿触发, 因此会在 t1、 t2 和 t3 这三个时刻会触发按键中断, 每次进入中断处理函数都会重新开器定时器中断, 所以会在 t1、 t2 和 t3 这三个时刻开器定时器中断。 但是 t1~t2 和 t2~t3 这两个时间段是小于我们设置的定时器中断周期(也就是消抖时间, 比如 10ms), 所以虽然 t1 开启了定时器, 但是定时器定时时间还没到呢 t2 时刻就重置了定时器, 最终只有 t3 时刻开启的定时器能完整的完成整个定时周期并触发中断, 我们就可以在中断处理函数里面做按键处理了, 这就是定时器实现按键防抖的原理, Linux 里面的按键驱动用的就是这个原理!

除了使用定时器方式进行消抖, 也可以使用这里要学习的延迟工作。

二、数据结构

1. struct delayed_work

在 Linux 内核中, 使用 struct delayed_work 来描述延迟工作:

c
struct delayed_work {
	struct work_struct work; // 延迟工作的基本工作结构
	struct timer_list timer; // 定时器, 用于延迟执行工作

	/* target workqueue and CPU -> timer uses to queue -> work */
	struct workqueue_struct *wq;
	int cpu;
};
  • work: 这是一个 struct work_struct 类型的成员, 用于表示延迟工作的基本工作结构。 struct work_struct 是表示工作的常见数据结构, 用于定义要执行的工作内容。
  • timer: 这是一个 struct timer_list 类型的成员, 用于管理延迟工作的定时器。 struct timer_list 是 Linux 内核中的定时器结构, 用于设置延迟时间和触发工作执行的时机。

使用 struct delayed_work 结构体, 可以将需要执行的工作封装成一个延迟工作, 并使用定时器来控制工作的延迟执行。 通过设置定时器的延迟时间, 可以指定工作在一定时间后执行。

三、相关的 api

1. 初始化延迟工作函数

1.1 DECLARE_DELAYED_WORK

静态定义并初始化延迟工作使用宏 DECLARE_DELAYED_WORK

c
#define __DELAYED_WORK_INITIALIZER(n, f, tflags) {			\
	.work = __WORK_INITIALIZER((n).work, (f)),			\
	.timer = __TIMER_INITIALIZER(delayed_work_timer_fn,\
				     (tflags) | TIMER_IRQSAFE),		\
	}

#define DECLARE_DELAYED_WORK(n, f)					\
	struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)

n 代表延迟工作的变量名, f 是延迟工作的处理函数。

1.2 INIT_DELAYED_WORK

动态定义并初始化延迟工作使用宏 INIT_DELAYED_WORK

c
#define __INIT_DELAYED_WORK(_work, _func, _tflags)			\
	do {								\
		INIT_WORK(&(_work)->work, (_func));			\
		__init_timer(&(_work)->timer,				\
			     delayed_work_timer_fn,			\
			     (_tflags) | TIMER_IRQSAFE);		\
	} while (0)

#define INIT_DELAYED_WORK(_work, _func)					\
	__INIT_DELAYED_WORK(_work, _func, 0)

n 代表延迟工作的变量名, f 是延迟工作的处理函数。

2. 调度/取消调度

2.1 schedule_delayed_work()

共享工作队列上调度延迟工作, 使用函数 schedule_delayed_work()

c
static inline bool schedule_delayed_work(struct delayed_work *dwork,
					 unsigned long delay)
{
	return queue_delayed_work(system_wq, dwork, delay);
}

该函数是一个内联函数, 用于在给定的延迟时间后调度延迟工作执行。

参数说明】:

  • dwork:是指向延迟工作的指针, 即要被调度的延迟工作。
  • delay:表示延迟的时间长度, 以内核时钟节拍数 jiffies 为单位。

2.2 queue_delayed_work()

自定义工作队列上调度延迟工作,使用函数 queue_delayed_work()

c
static inline bool queue_delayed_work(struct workqueue_struct *wq,
				      struct delayed_work *dwork,
				      unsigned long delay)
{
	return queue_delayed_work_on(WORK_CPU_UNBOUND, wq, dwork, delay);
}

该函数是一个内联函数, 用于将延迟工作加入工作队列后在指定的延迟时间后执行。

参数说明】 :

  • wq:是指向工作队列结构的指针, 即要将延迟工作加入的目标工作队列。
  • dwork:指向延迟工作的指针, 也就是要被加入工作队列的延迟工作。
  • delay:表示延迟的时间长度, 以内核时钟节拍数 jiffies 为单位。

2.3 cancel_delayed_work_sync()

取消延迟工作调度可以用 cancel_delayed_work_sync() 函数:

c
bool cancel_delayed_work_sync(struct delayed_work *dwork)
{
	return __cancel_work_timer(&dwork->work, true);
}
EXPORT_SYMBOL(cancel_delayed_work_sync);

该函数是一个外部声明的函数, 用于取消延迟工作并等待其完成。

参数说明】:

  • dwork:指向延迟工作的指针, 也就是要被取消的延迟工作。

返回值】返回 true, 说明成功取消延迟工作并等待其完成。 返回 false, 说明无法取消延迟工作或等待其完成。

四、延迟工作 demo

1. demo 源码

demo 源码可以看这里:13_interrupt/09_nodts_workqueue_custom_delay

2. 开发板测试

我们拷贝驱动到开发板中,加载驱动:

shell
insmod sdriver_demo.ko
image-20250323130632382

然后按下按键再抬起,可以看到如下打印信息:

image-20250323130738788

上面的测试是按了一次,延迟 3 秒后执行工作,工作中又延迟 1s,所以一共是 4s。下面是连按了两次,会发现只执行了一次工作处理函数。这是因为我们都是同一个硬件中断,上半部肯定会执行 2 次,但是下半部有可能只执行一次,在软中断那节的笔记中有提到。

参考资料:

中断处理下半部机制-CSDN 博客

软中断(softirq)机制_软中断机制-CSDN 博客