• RK3568驱动指南|第五期-中断-第44章 共享工作队列实验


    瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


    【公众号】迅为电子

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

    【视频观看】嵌入式学习之Linux驱动(第五期_中断_全新升级)_基于RK3568

    【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


    第44章 共享工作队列实验

    在上个章节我们学习了中断下文的一种实验方式——软中断,本章节我们来学习中断下文的另一种实现方式——工作队列。工作队列是操作系统中管理和调度异步任务执行的一种机制,接下来开始学习工作队列吧。

    44.1 什么是工作队列

    工作队列是实现中断下半部分的机制之一,是一种用于管理任务的数据结构或机制。它通常用于多线程,多进程或分布式系统中,用于协调和分配待处理的任务可用的工作线程或工作进程

    工作队列的基本原理是将需要执行的任务按顺序排列在队列中,并提供一组工作线程或者工作进程来处理队列中的任务。当有新的任务到达时,它们会被添加到队列的末尾,工作线程或工作进程从队列的头部获取任务,并执行相应的处理操作。

    工作队列和之前学习的tasklet有什么不同呢?tasklet也是实现中断下半部分的机制之一。他们最主要的区别是tasklet不能休眠,而工作队列是可以休眠的,所以tasklet可以用来处理比较耗时间的事情,而工作队列可以处理更耗时间的事情。

    工作队列将工作推后以后,会交给内核线程去执行。Linux在启动过程中会创建一个工作者内核线程,这个线程创建以后处于sleep状态。当有工作需要处理的时候,会唤醒这个线程去处理工作。

    在内核中,工作队列包括共享工作队列和自定义工作队列这俩种类型。这两种类型的工作队列具有不同的特点和用途。

    1  共享队列是由内核管理的全局工作队列,用于处理内核中一些系统级任务。共享工作队列是内核中一个默认工作队列,可以由多个内核组件和驱动程序共享使用。

    2 自定义工作队列是由内核或驱动程序创建的特定工作队列,用于处理特定的任务。自定义工作队列通常与特定的内核模块或驱动程序相关联,用于执行该模块或驱动程序相关的任务。

     本章节我们先来学习共享工作队列相关的知识。

    在Linux 内核中,使用 work_struct 结构体表示一个工作项,这些工作组织成工作队列,工作队列使用 workqueue_struct 结构体表示,如下图所示,流水线相当于工作队列,流水线上一个个等待处理的物料相当于一个个工作。机器相当于内核线程或进程。

    图 44-1

    work_struct 结构体表示一个工作项,定义在include/linux/workqueue.h中,如下所示:

    1. struct work_struct {
    2. atomic_long_t data;
    3. struct list_head entry;
    4. work_func_t func; /* 工作队列处理函数 */
    5. };
    6. typedef void (*work_func_t)(struct work_struct *work); //工作函数

    44.2 工作队列相关接口函数

    44.2.1 初始化函数

    在实际的驱动开发中,我们只需要定义工作项(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。简单创建工作很简单,直接定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作,INIT_WORK 宏定义如下:

    #define INIT_WORK(_work,_func)

    INIT_WORK 宏接受两个参数:_work 和 _func,分别表示要初始化的工作项和工作项的处理函数。

    也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:

    #define DECLARE_WORK(n, f)

    参数n 表示定义的工作(work_struct),f 表示工作对应的处理函数。

    44.2.2 调度/取消调度工作队列函数

    和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原型如下所示:

    static inline bool schedule_work(struct work_struct *work)

    参数是指向工作项的指针。这个函数作用是将工作项提交到工作队列中,并请求调度器在合适的时机执行工作项。该函数会返回一个布尔值,表示工作项是否成功被提交到工作队列。

    如果想要取消该工作项的调度,使用以下函数:

    bool cancel_work_sync(struct work_struct *work);

    参数是指向工作项的指针。这个函数的作用是取消该工作项的调度。如果工作项已经在工作队列中,它将被从队列中移除。如果工作项已经在工作队列中,它将被从队列中移除,并等待工作项执行完成。函数返回一个布尔值,表示工作项是否成功取消。

    44.3 实验程序的编写

    44.3.1 驱动程序编写

    本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\34_workqueue\module

    本实验将实现注册显示屏触摸中断,每按当触摸LCD显示屏就会触发中断服务函数,在中断服务函数中提交工作项到共享工作队列中,打印“This id test_interrupt”和“This is test_work”。

    在驱动程序中的模块初始化函数中,我们将GPIO转换为中断号,并使用request_irq函数请求中断,然后初始化工作项。当中断被触发时,中断处理函数被调用,并将工作项提交到共享工作队列中,最终由工作项处理函数异步执行。编写完成的interrupt.c代码如下所示,添加的代码已加粗表示。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int irq;
    8. struct work_struct test_workqueue;
    9. // 工作项处理函数
    10. void test_work(struct work_struct *work)
    11. {
    12. msleep(1000);
    13. printk("This is test_work\n");
    14. }
    15. // 中断处理函数
    16. irqreturn_t test_interrupt(int irq, void *args)
    17. {
    18. printk("This is test_interrupt\n");
    19. // 提交工作项到工作队列
    20. schedule_work(&test_workqueue);
    21. return IRQ_RETVAL(IRQ_HANDLED);
    22. }
    23. static int interrupt_irq_init(void)
    24. {
    25. int ret;
    26. irq = gpio_to_irq(101); // 将GPIO映射为中断号
    27. printk("irq is %d\n", irq);
    28. // 请求中断
    29. ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);
    30. if (ret < 0)
    31. {
    32. printk("request_irq is error\n");
    33. return -1;
    34. }
    35. // 初始化工作项
    36. INIT_WORK(&test_workqueue, test_work);
    37. return 0;
    38. }
    39. static void interrupt_irq_exit(void)
    40. {
    41. free_irq(irq, NULL); // 释放中断
    42. printk("bye bye\n");
    43. }
    44. module_init(interrupt_irq_init);
    45. module_exit(interrupt_irq_exit);
    46. MODULE_LICENSE("GPL");
    47. MODULE_AUTHOR("topeet");

    44.4 运行测试

    44.4.1 编译驱动程序

    在上一小节中的interrupt.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:

    1. export ARCH=arm64#设置平台架构
    2. export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
    3. obj-m += interrupt.o #此处要和你的驱动源文件同名
    4. KDIR :=/home/topeet/Linux/linux_sdk/kernel #这里是你的内核目录
    5. PWD ?= $(shell pwd)
    6. all:
    7. make -C $(KDIR) M=$(PWD) modules #make操作
    8. clean:
    9. make -C $(KDIR) M=$(PWD) clean #make clean操作

    对于Makefile的内容注释已在上图添加,保存退出之后,来到存放interrupt.c和Makefile文件目录下,如下图(图44-2)所示:

    图 44-2

    然后使用命令“make”进行驱动的编译,编译完成如下图(图44-3)所示:

    图 44-3

    编译完生成interrupt.ko目标文件,如下图(图44-4)所示:

    图 44-4

    至此驱动模块就编译成功了,接下来进行测试。

    44.4.2 运行测试

    开发板启动之后,使用以下命令进行驱动模块的加载,如下图(图44-5)所示:

    insmod interrupt.ko

    图44-5

    加载驱动之后,可以看到申请的中断号被打印了出来,然后用手触摸连接的LVDS 7寸屏幕,打印如下图(44-6)所示:

    在上图中,可以看到打印中断处理函数中添加的打印“This is test_interrupt”被多次打印,说明触发了好几次中断上文,那么中断上文会多次调度中断下文,所以也会打印工作项处理函数中添加的打印“This is test_work”。但是为什么只会打印俩次“This is test_work”呢?这是因为在中断上文调度工作项处理函数之后,内核没有来得及去执行工作项处理函数,没有执行相当于无效操作,有效的执行则打印了俩次“This is test_work”。

    最后可以使用以下命令进行驱动的卸载,如下图(图44-7)所示:

    rmmod interrupt

     

    图 44-7

    至此,共享工作队列实验就完成了。


  • 相关阅读:
    R语言和医学统计学(13):协方差分析
    基于JavaScript的Web端股票价格查看器——大道
    Java高级互联网架构师之路:排查当前JVM错误的步骤
    学习python第二天(字符串内置函数)
    ElasticSearch从入门到精通:常用操作
    【LeetCode】刷题模版/套路合集(持续更新)
    基于Java+Spring+vue+element社区疫情服务平台设计和实现
    【SCSS篇】Vite+Vue3项目全局引入scss文件
    8.定义算法中的函数
    俄罗斯方块
  • 原文地址:https://blog.csdn.net/BeiJingXunWei/article/details/133312253