嵌入式Linux设备驱动中断下文之工作队列

工作队列理论基础

工作队列(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 次打印。




 

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木士易

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值