内核工作队列
定时器、下半部 tasklet,它们都是在中断上下文中执行,它们无法休眠。当要处理更复杂的事情时,往往更耗时。这些更耗时的工作放在定时器或是下半部中,会使得系统很卡;并且循环等待某件事情完成也太浪费CPU 资源了。
如果使用线程来处理这些耗时的工作,那就可以解决系统卡顿的问题:因为线程可以休眠。
在内核中,我们并不需要自己去创建线程,可以使用“工作队列 ”(workqueue)。内核初始化工作队列是,就为它创建了内核线程。以后我们要使用“工作队列”,只需要把“工作”放入“工作队列中”,对应的内核线程就会取出“工作”,执行里面的函数。
工作队列的应用场合:要做的事情比较耗时,甚至可能需要休眠,那么可以使用工作队列。
缺点:多个工作(函数)是在某个内核线程中依序执行的,前面函数执行很慢,就会影响到后面的函数。
在多CPU 的系统下,一个工作队列可以有多个内核线程,可以一定程度上缓解这个问题
下图是应用程序休眠和唤醒的过程
内核线程也类似的:
内核函数
内核线程、工作队列(workqueue)都由内核创建了,我们只是使用。使用的核心是一个 work_struct 结构体,定义如下:
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
typedef void (*work_func_t)(struct work_struct *work);
使用队列时,步骤如下:
第一步:构造一个 work_struct
结构体,里面有函数;
第二步:把这个 work_struct
结构体放入工作队列,内核线程就会运行 work
中的函数。
1. 定义 work
参考内核头文件 include\linux\workqueue.h
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
#define DECLARE_DELAYED_WORK(n, f) \
struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)
- 第1个宏是用来定义一个
work_struct
结构体,要指定它的函数。 - 第2个宏用来定义一个
delayed_work
结构体,也要指定它的函数。所以delayed
,意思就是说要让它运行时,可以指定:某段时间之后再执行。
如果要在代码中初始化 work_struct
结构体,可以使用下面的宏:
#define INIT_WORK(_work, _func) \
__INIT_WORK((_work), (_func), 0)
2. 使用work:schedule_work
调用 schedule_work
时,就会把 work_struct
结构体放入队列中,并唤醒对应的内核线程。内核线程就会从队列里把 work_struct
结构体取出来,执行里面的函数。
/**
* schedule_work - put work task in global workqueue
* @work: job to be done
*
* Returns %false if @work was already on the kernel-global workqueue and
* %true otherwise.
*
* This puts a job in the kernel-global workqueue if it was not already
* queued and leaves it in the same position on the kernel-global
* workqueue otherwise.
*/
static inline bool schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
}
3 其他函数
序号 | 函数 | 说明 |
---|---|---|
1 | create_workqueue | 在 Linux 系统中已经有了现成的 system_wq 等工作队列,你当然也可以自己调用 create_workqueue 创建工作队列,对于 SMP 系统,这个工作队列会有多个内核线程与它对应,创建工作队列时,内核会帮这个工作队列创建多个内核线程 |
2 | create_singlethread_workqueue | 如果想只有一个内核线程与工作队列对应,可以用本函数创建工作队列,创建工作队列时,内核会帮这个工作队列创建一个内核线程 |
3 | destroy_workqueue | 销毁工作队列 |
4 | schedule_work | 调度执行一个具体的 work,执行的 work 将会被挂入 Linux 系统提供的工作队列 |
5 | schedule_delayed_work | 延迟一定时间去执行一个具体的任务,功能与 schedule_work 类似,多了一个延迟时间 |
6 | queue_work | 跟 schedule_work 类似,schedule_work 是 在系统默认的工 作队列上执行一 个work,queue_work 需要自己指定工作队列 |
7 | queue_delayed_work | 跟 schedule_delayed_work 类似,schedule_delayed_work 是在系统默认的工作队列上执行一个 work,queue_delayed_work 需要自己指定工作队列 |
8 | flush_work | 等待一个 work 执行完毕,如果这个 work 已经被放入队列,那么本函数等它执行完毕,并且返回 true;如果这个work 已经执行完华才调用本函数,那么直接返回false |
9 | flush_delayed_work | 等待一个 delayed_work 执行完毕,如果这个 delayed_work 已经被放入队列,那么本函数等它执行完毕,并且返回 true;如果这个 delayed_work 已经执行完华才调用本函数,那么直接返回 false |
驱动程序
- 在原来的
gpio_key
结构体中添加一个workqueue
属性,每个按键都有一个workqueue
;在probe
函数中初始化workqueue
;在remove
函数中删除workqueue
; - 在
tasklet
处理函数中打印数据; - 在中断服务程序,启动
workqueue
。
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>