工作队列理论基础
工作队列(workqueue) 是实现中断下文的机制之一, 是一种将工作推后执行的形式。 tasklet 也是实现中断下文的机制。 他们俩个最主要的区别是 tasklet不能休眠, 而工作队列是可以休眠的。 所以, tasklet 可以用来处理比较耗时间的事情, 而工作队列可以处理非常复杂并且更耗时间的事情。
Linux 系统在启动期间会创建内核线程, 该线程创建以后就处于 sleep 状态, 然后这个线程会一直去队列里面读, 看看有没有任务, 如果有就执行, 如果没有就休眠。 工作队列的实现机制实际上是非常复杂的,初学阶段只需要了解这些基本概念接口。
类比理解:
流水线上的机器: Linux 系统自动会创建一个。 多种不同的物料使用同一个流水线机械, 那么这个就是共享工作队列的概念。
如果当前的流水线机械不能满足我们加工的物料, 那么需要重新定制一台流水线机器, 这个就是自定义工作队列的概念。 共享队列虽然不需要自己创建, 但是如果前面的工作比较耗时间, 就会影响后面的工作。 而且自定义工作队列需要自己创建, 系统开销大。 优点是不会受到其他工作的影响。 (因为这个流水线就是专门加工这一种零件的。 )
工作队列相关 API
尽管工作队列的实现机制非常复杂, 但是我们使用工作队列其实就是在这个流水线上添加自己的物料,然后等待执行即可。
Linux 内核使用 work_struct 结构体表示一个工作, 内容如下。
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
在这个结构体里面我们只需要关注 func 这个成员就可以了, 他是一个函数指针, 因为要将需要完成的工作写在这个函数里面。
这些工作组织成工作队列, 工作队列使用 workqueue_struct 结构体表示, 内容如下:
struct workqueue_struct {
struct list_head pwqs;
struct list_head list;
struct mutex mutex;
int work_color;
int flush_color;
atomic_t nr_pwqs_to_flush;
struct wq_flusher *first_flusher;
struct list_head flusher_queue;
struct list_head flusher_overflow;
struct list_head maydays;
struct worker *rescuer;
int nr_drainers;
int saved_max_active;
struct workqueue_attrs *unbound_attrs;
struct pool_workqueue *dfl_pwq;
char name[WQ_NAME_LEN];
struct rcu_head rcu;
unsigned int flags ____cacheline_aligned;
struct pool_workqueue __percpu *cpu_pwqs;
struct pool_workqueue __rcu *numa_pwq_tbl[];
};
每个 worker 都有一个工作队列, 工作的线程处理自己工作队列中的所有工作。 在实际的驱动开发中,我们只需要定义工作(work_struct)即可, 关于工作队列和工作者线程我们基本不用去管。 简单创建工作很简单, 直接定义一个 work_struct 结构体变量即可, 然后使用 INIT_WORK 宏来初始化工作, INIT_WORK 宏定义如下:
#define INIT_WORK(_work, _func)
work 表示要初始化的工作, _func 是工作对应的处理函数。 也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化, 宏定义如下:
#define DECLARE_WORK(n, f)
n 表示定义的工作(work_struct), f 表示工作对应的处理函数。
举例:
struct work_struct test;
在模块的初始化函数中:
INIT_WORK(&test, func) ;
相当于:
DECLARE_WORK(test, func);
和 tasklet 一样, 工作也是需要调度才能运行的, 工作的调度函数为 schedule_work, 函数原型如下所示:
函数 | int schedule_work(struct work_struct *work); |
_work | 工作队列地址 |
返回值 | 0 成功, 其他值 失败 |
功能 | 调度工作, 把 work_struct 挂到 CPU 相关的工作结构队列链表上, 等待工作者线程处理。 |
注意 | 需要注意的是, 如果调度完工作, 并不会马上执行, 只是加到了共享的工作队列里面去, 等轮到他才会执行。 如果我们多次调用相同的任务, 假如上一次的任务还没有处理完成, 那么多次调度相同的任务是无效的 |
驱动程序编写
driver.c文件如下所示:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/workqueue.h>
//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
// 定义工作结构体
struct work_struct key_test;
//要申请的中断号
int irq;
//GPIO 编号
int gpio_nu;
/**
* @description: 工作队列的处理函数
* @param {unsignedlong} data:要传递给 func 函数的参数
* @return {*}无
*/
void test(struct work_struct *data)
{
int i = 100;
printk("i is %d \n", i);
while (i--)
printk("test_key is %d \n", i);
}
/**
* @description: 中断处理函数 test_key
* @param {int} irq : 要申请的中断号
* @param {void} *args :
* @return {*}IRQ_HANDLED
*/
irqreturn_t test_key(int irq, void *args)
{
printk("start\n");
schedule_work(&key_test);
printk("end\n");
return IRQ_HANDLED;
}
/**
* @brief beep_probe : 与设备信息层(设备树) 匹配成功后自动执行此函数,
* @param inode : 文件索引
* @param file : 文件
* @return 成功返回 0
*/
int beep_probe(struct platform_device *pdev)
{
int ret = 0;
//进入 probe 函数
printk("beep_probe\n");
//of_find_node_by_path 函数通过路径查找节点, /test_key 是设备树下的节点路径
test_device_node = of_find_node_by_path("/test_key");
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error \n");
return -1;
}
//of_get_named_gpio 函数获取 GPIO 编号
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
if (gpio_nu < 0)
{
printk("of_get_namd_gpio is error \n");
return -1;
}
gpio_direction_input(gpio_nu);
//获得中断号
//irq = gpio_to_irq(gpio_nu);
irq = irq_of_parse_and_map(test_device_node, 0);
printk("irq is %d \n", irq);
/*申请中断, irq:中断号名字
test_key: 中断处理函数
IRQF_TRIGGER_RISING: 中断标志, 意为上升沿触发
"test_key": 中断的名字*/
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
if (ret < 0)
{
printk("request_irq is error \n");
return -1;
}
//初始化工作队列
//tasklet_init(&key_test,test, 100);
//INIT_WORK 宏来初始化工作
INIT_WORK(&key_test, test);
return 0;
}
int beep_remove(struct platform_device *pdev)
{
printk("beep_remove\n");
return 0;
}
const struct platform_device_id beep_idtable = {
.name = "keys",
};
const struct of_device_id of_match_table_test[] = {
{
.compatible = "keys"},
{},
};
struct platform_driver beep_driver = {
//3. 在 beep_driver 结构体中完成了 beep_probe 和 beep_remove
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "beep_test",
.of_match_table = of_match_table_test
},
//4 .id_table 的优先级要比 driver.name 的优先级要高, 优先与.id_table 进行匹配
.id_table = &beep_idtable
};
static int beep_driver_init(void)
{
//1.我们看驱动文件要从 init 函数开始看
int ret = 0;
//2. 在 init 函数里面注册了 platform_driver
ret = platform_driver_register(&beep_driver);
if (ret < 0)
{
printk("platform_driver_register error \n");
}
printk("platform_driver_register ok \n");
return 0;
}
static void beep_driver_exit(void)
{
free_irq(irq, NULL);
//tasklet_kill(&key_test);
platform_driver_unregister(&beep_driver);
printk("gooodbye! \n");
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");
编译为驱动模块。
测试实验
启动 imx6ull 开发板, 我们检查一下有没有我们在第六十章添加的节点, 如下图所示:
cd /proc/device-tree
ls
cd test_key/
ls
加载驱动模块, 如下图所示:
然后按开发板上的 Key0 按键, 打印 100 次打印。