嵌入式Linux设备驱动中断下文之工作队列
工作队列(workqueue) 是实现中断下文的机制之一, 是一种将工作推后执行的形式。tasklet 也是实现中断下文的机制。 他们俩个最主要的区别是 tasklet不能休眠, 而工作队列是可以休眠的。 所以, tasklet 可以用来处理比较耗时间的事情, 而工作队列可以处理非常复杂并且更耗时间的事情。
工作队列理论基础
工作队列(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 次打印。
更多推荐
所有评论(0)