【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第六十章 中断下文之工作队列

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、

【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)


六十中断下文之工作队列

本章导读

60.1章节讲解了工作队列理论基础

60.2章节讲解了工作队列相关API

60.3章节 以IMX8MM开发板为例,进行驱动试验

本章内容对应视频讲解链接(在线观看):

工作队列  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=39

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\017-工作队列实验”路径下。

60.1 工作队列理论基础

工作队列(workqueue)是实现中断下文的机制之一,是一种将工作推后执行的形式。那工作队列和我们之前学的tasklet机制有什么不同呢?tasklet也是实现中断下文的机制。他们两个最主要的区别是tasklet不能休眠,而工作队列是可以休眠的。所以,tasklet可以用来处理比较耗时间的事情,而工作队列可以处理非常复杂并且更耗时间的事情。

Linux系统在启动期间会创建内核线程,该线程创建以后就处于sleep状态,然后这个线程会一直去队列里面读,看看有没有任务,如果有就执行,如果没有就休眠。工作队列的实现机制实际上是非常复杂的,初学阶段只需要了解这些基本概念接口。

类比理解:

流水线上的机器:Linux系统自动会创建一个。多种不同的物料使用同一个流水线机械,那么这个就是共享工作队列的概念。

如果当前的流水线机械不能满足我们加工的物料,那么需要重新定制一台流水线机器,这个就是自定义工作队列的概念。共享队列虽然不需要自己创建,但是如果前面的工作比较耗时间,就会影响后面的工作。而且自定义工作队列需要自己创建,系统开销大。优点是不会受到其他工作的影响。(因为这个流水线就是专门加工这一种零件的。)

60.2 工作队列相关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相关的工作结构队列链表上,等待工作者线程处理。

注意

需要注意的是,如果调度完工作,并不会马上执行,只是加到了共享的工作队列里面去,等轮到他才会执行。如果我们多次调用相同的任务,假如上一次的任务还没有处理完成,那么多次调度相同的任务是无效的

60.3 工作队列实验

60.3.1 驱动程序编写

我们在IMX8MM开发板上为例,新建driver.c,并拷贝Makefile和build.sh到Ubuntu的/home/topeet/imx8mm/17目录下。完整的驱动代码,如下所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>

#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
//定义结构体表示我们的节点
struct device_node *test_device_node;

//要申请的中断号
int irq;
// GPIO 编号
int gpio_nu;

//定义tasklet结构体
//struct tasklet_struct key_test;

//定义工作结构体
struct work_struct key_test;
/**
 * @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");
    //tasklet_schedule(&key_test);
    schedule_work(&key_test);
    printk("end\n");
    return IRQ_HANDLED;
}
/****************************************************************************************
 * @brief led_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,
 * @param inode : 文件索引
 * @param file  : 文件
 * @return 成功返回 0           
 ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{
    int ret = 0;
    // 打印匹配成功进入probe函数
    printk("led_probe\n");
    test_device_node = of_find_node_by_path("/test");
    if (test_device_node == NULL)
    {
        //查找节点失败则打印信息
        printk("of_find_node_by_path is error \n");
        return -1;
    }
    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为输入模式
    gpio_direction_input(gpio_nu);
    //获取GPIO对应的中断号
    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 */
    //tasklet_init(&key_test,test,100);
    INIT_WORK(&key_test, test);

    return 0;
}

int led_remove(struct platform_device *pdev)
{
    printk("led_remove\n");
    return 0;
}
const struct platform_device_id led_idtable = {
    .name = "keys",
};
const struct of_device_id of_match_table_test[] = {
    {.compatible = "keys"},
    {},
};
struct platform_driver led_driver = {
    //3. 在led_driver结构体中完成了led_probe和led_remove
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "led_test",
        .of_match_table = of_match_table_test},
    //4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配
    .id_table = &led_idtable};

/**
 * @description: 模块初始化函数
 * @param {*}
 * @return {*}
 */
static int led_driver_init(void)
{
    //1.我们看驱动文件要从init函数开始看
    int ret = 0;
    //2.在init函数里面注册了platform_driver
    ret = platform_driver_register(&led_driver);
    if (ret < 0)
    {
        printk("platform_driver_register error \n");
    }
    printk("platform_driver_register ok \n");
    return 0;
}

/**
 * @description: 模块卸载函数
 * @param {*}
 * @return {*}
 */
static void led_driver_exit(void)
{
    free_irq(irq, NULL);
    platform_driver_unregister(&led_driver);
    printk("goodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");

将本章节编写的驱动程序编译为驱动模块,如下图所示:

60.3.2 测试实验

我们启动IMX8MM开发板,我们检查一下有没有我们在第五十七章添加的节点,如下图所示:

cd /proc/device-tree

cd test/

ls

 

然后我们挂载nfs共享目录,挂载好后进入共享目录,我们加载驱动模块,如下图所示: 

我们然后按开发板上的音量+按键,打印100次输出,如下图所示: 

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值