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 博客