瑞芯微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主板
第48章 并发管理工作队列实验
在现代的软件开发中,我们常常面临着需要同时处理多个任务的挑战。这些任务可能是并行的、独立的,或者需要以某种顺序进行处理。为了高效地管理这些并发任务,我们需要一种有效的机制来协调它们的执行。这就是并发管理工作队列发挥作用的地方。本章节我们来学习并发管理工作队列。
48.1工作队列的实现
在44章节和45章节,我们学习了共享工作队列和自定义工作队列,在使用工作队列时,我们首先定义一个work结构体,然后将work添加到workqueue(工作队列)中,最后worker thread 执行workqueue。当工作队列中有新work产生时,工作线程(worker thread)会执行工作队列中每个work。当执行完结束的时候,worker thread会睡眠,等到新的中断产生,work再继续添加到工作队列,然后工作线程执行每个工作,周而复始。
在单核线程的系统中,通常会为每个 CPU(核心)初始化一个工作线程并关联一个工作队列。这种默认设置确保每个CPU都有一个专门的线程来处理与其绑定的工作队列上的工作项。如下图(48-1)所示:
在多核线程系统中,工作队列的设计与单核线程系统有所不同。在多核线程系统中,通常会存在多个工作队列,每个工作队列与一个工作线程(Worker Thread)绑定。这样可以充分利用多个核心的并行处理能力。如下图(48-2)所示:
当有新的工作项产生时,系统需要决定将其分配给哪个工作队列。一种常见的策略是使用负载均衡算法,根据工作队列的负载情况来平衡分配工作项,以避免某个工作队列过载而导致性能下降。每个工作队列独立管理自己的工作项。当有新的工作项添加到工作队列时,工作线程会从其关联的工作队列中获取待执行的工作项,并执行相应的处理函数。在多核线程系统中,多个工作线程可以同时执行各自绑定的工作队列中的工作项。这样可以实现并行处理,提高系统的整体性能和响应速度。
了解了工作队列是如何实现的,接下来我们看看传统的工作队列有什么弊端呢?
48.2 workqueue队列弊端
假如说有三个work放到了同一个工作队列上,接下来CPU会启动工作线程去执行这三个work,如下图(48-3)所示:
在上图中,工作项w0、w1、w2被排队到同一个CPU上的绑定工作队列上。w0工作项执行的时候,先工作 5毫秒,然后睡觉10毫秒,然后再工作CPU 5毫秒,然后完成。工作项w1和w2都是工作5ms,然后睡眠10 ms,然后完成。传统工作队列的弊端如下所示:
1 在工作项w0 工作甚至是睡眠时,工作项w1 w2是排队等待的,在繁忙的系统中,工作队列可能会积累大量的待处理工作项,导致任务调度的延迟,这可能会影响系统的响应性能,并增加工作项的处理时间。
2 在工作队列中,不同的工作项可能具有不同的处理时间和资源需求。如果工作项的处理时间差异很大,一些工作线程可能会一直忙于处理长时间的工作项,而其他工作线程则处于空闲状态,导致资源利用不均衡。
3 在多线程环境下,多个工作线程同时访问和修改工作队列可能会导致竞争条件的发生。为了确保数据的一致性和正确性,需要采用适当的同步机制,如锁或原子操作,来保护共享数据,但这可能会引入额外的同步开销。
4 工作队列通常按照先进先出(FIFO)的方式处理工作项,缺乏对工作项优先级的细粒度控制。在某些场景下,可能需要根据工作项的重要性或紧急程度进行优先级调度,而工作队列本身无法提供这种级别的优先级控制。
5 当工作线程从工作队列中获取工作项并执行时,可能需要频繁地进行上下文切换,将处理器的执行上下文从一个线程切换到另一个线程。这种上下文切换开销可能会影响系统的性能和效率。
48.2 什么是并发管理工作队列
通过上一小节的学习,我们认识到传统的工作队列无论是单核系统还是多核系统上都是有缺陷的。比如无法充分利用多核处理器的计算能力以及对于不同优先级的工作项无法提供公平的调度。为了解决这些问题,Con Kolivas提出了CMWQ调度算法。
CMWQ 全称是concurrency Managed Workqueue,意为并发管理工作队列。并发管理工作队列是一种并发编程模式,用于有效地管理和调度待执行的任务或工作项。它通常用于多线程或多进程环境中,以实现并发执行和提高系统的性能。CMWQ 工作实现如下图(48-4)所示:
当我们需要在一个系统中同时处理多个任务或工作时,使用并发管理工作队列是一种有效的方式。
想象一下,你是一个餐厅的服务员,有很多顾客同时来到餐厅用餐。为了提高效率,你需要将顾客的点菜请求放到一个队列中,这就是工作队列。然后,你和其他服务员可以从队列中获取顾客的点菜请求,每个服务员独立地为顾客提供服务。通过这种方式,你们可以并发地处理多个顾客的点菜请求,而不需要等待上一个顾客点完菜再去处理下一个顾客的请求。每个服务员可以独立地从队列中获取任务,并根据需要执行相应的服务。这种独立获取任务的过程就是从工作队列中取出任务并执行的过程。
通过并发管理工作队列,你们能够更高效地处理顾客的点菜请求,提高服务的速度和质量。同时,这种方式也能够更好地利用你们的工作能力,因为每个服务员都可以独立处理任务,而不会相互干扰或等待。
总的来说,通过并发管理工作队列,我们可以同时处理多个任务或工作,提高系统的并发性和性能。每个任务独立地从队列中获取并执行,这种解耦使得整个系统更加高效、灵活,并且能够更好地应对多任务的需求。
48.3 并发管理工作队列接口函数
alloc_workqueue是Linux内核中的一个函数,用于创建和分配一个工作队列。工作队列是一种用于管理和调度工作项的机制,可用于实现并发处理和异步任务处理。alloc_workqueue函数的原型如下:
struct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags, int max_active);
参数说明:
fmt:指定工作队列的名称格式。
flags:指定工作队列的标志,可以控制工作队列的行为和属性,如WQ_UNBOUND表示无绑定的工作队列,WQ_HIGHPRI表示高优先级的工作队列等。
max_active:指定工作队列中同时活跃的最大工作项数量。
函数返回一个指向工作队列结构体(struct workqueue_struct)的指针,或者返回NULL表示创建失败。
在下一小节中将使用上述API进行相应的实验。
48.4 实验程序的编写
48.4.1 驱动程序编写
本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\38_CMWQ\module。
本实验在35自定义工作队列实验的基础上进行修改,使用alloc_workqueue函数创建和分配一个工作队列。编写完成的interrupt.c代码如下所示,添加的代码已加粗表示。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>int irq;
struct workqueue_struct *test_workqueue;
struct work_struct test_workqueue_work;// 工作项处理函数
void test_work(struct work_struct *work)
{msleep(1000);printk("This is test_work\n");
}// 中断处理函数
irqreturn_t test_interrupt(int irq, void *args)
{printk("This is test_interrupt\n");queue_work(test_workqueue, &test_workqueue_work); // 提交工作项到工作队列return IRQ_RETVAL(IRQ_HANDLED);
}static int interrupt_irq_init(void)
{int ret;irq = gpio_to_irq(101); // 将GPIO映射为中断号printk("irq is %d\n", irq);// 请求中断ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);if (ret < 0){printk("request_irq is error\n");return -1;}// 用于创建和分配一个工作队列test_workqueue = alloc_workqueue("test_workqueue", WQ_UNBOUND, 0);INIT_WORK(&test_workqueue_work, test_work); // 初始化工作项return 0;
}static void interrupt_irq_exit(void)
{free_irq(irq, NULL); // 释放中断cancel_work_sync(&test_workqueue_work); // 取消工作项flush_workqueue(test_workqueue); // 刷新工作队列destroy_workqueue(test_workqueue); // 销毁工作队列printk("bye bye\n");
}module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");
48.5 运行测试
48.5.1 编译驱动程序
在上一小节中的interrupt.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:
export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += interrupt.o #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:make -C $(KDIR) M=$(PWD) modules #make操作
clean:make -C $(KDIR) M=$(PWD) clean #make clean操作
对于Makefile的内容注释已在上图添加,保存退出之后,来到存放interrupt.c和Makefile文件目录下,如下图(图48-5)所示:
然后使用命令“make”进行驱动的编译,编译完成如下图(图48-6)所示:
编译完生成interrupt.ko目标文件,如下图(图48-7)所示:
至此驱动模块就编译成功了,接下来进行测试。
48.5.2 运行测试
开发板启动之后,使用以下命令进行驱动模块的加载,如下图(图48-8)所示:
insmod interrupt.ko
驱动加载之后,可以看到申请的中断号被打印了出来,然后用手触摸连接的LVDS 7寸屏幕,打印如下图(48-9)所示:
我们按一下屏幕,立即输入“ps -aux | grep test_workqueue”,可以看到工作线程,如下图(48-10)所示,u代表无绑定的工作队列。
最后可以使用以下命令进行驱动的卸载,如下图(图48-11)所示:
rmmod interrupt
至此,并发管理工作队列实验就完成了。