0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

RK3568驱动指南|第三篇-并发与竞争-第19章 并发与竞争实验

北京迅为电子 2025-02-24 16:26 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

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

wKgZPGe8LQ6ASKzbAAjhsFOvqKY735.png

迅为RK568邮票孔版开发板:
还在为工控机性能不足而烦恼?
还在为开发周期长而焦虑?
迅为RK568邮票孔版开发板,为您提供一站式解决方案!

强劲性能,赋能工业未来:
搭载瑞芯微RK568高性能处理器,采用先进制程工艺,提供澎湃算力,轻松应对复杂工业场景。
支持多种操作系统,如Android、Linux等,满足不同应用需求。
丰富接口,扩展性强,可连接多种工业设备,构建稳定可靠的工业控制系统。

邮票孔设计,灵活易用:
采用邮票孔连接方式,方便快捷,节省空间,易于集成到各种工控设备中。
提供配套外壳,防护等级高,适应各种恶劣工业环境。
提供完善的技术支持和开发资料,助您快速上手,缩短开发周期。

广泛应用于工业自动化控制、机器视觉、智能网关、工业机器人、物联网终端等。

wKgZPGe8LQ6AJ2VnAAIvCcsa3kc409.png

【公众号】迅为电子

第19章 并发与竞争实验

在前面章节的学习中,相信大家已经对用户空间与内核空间数据传递进行了实验,假如要传递的数据被存放在了全局变量,该数据就可以作为共享资源被多个任务共同读写,从而造成数据的错误传输,多个程序同时访问一个共享资源产生的问题就叫做竞争。竞争产生的根本原因就是Linux系统的并发访问。

在本章节中首先会对并发与并行的概念进行讲解,随后对竞争产生的原因进行总结,最后以一个实际的竞争实验加深大家的理解。下面就让我们开始本章节的学习吧。

19.1并发与竞争

19.1.1并发

早期计算机大多只有一个CPU核心,一个CPU在同一时间只能执行一个任务,当系统中有多个任务等待执行时,CPU只能执行完一个再执行下一个。而计算机的很多指令会涉及I/O操作,执行速度远远低于CPU内高速存储器的存取速度,这就导致CPU经常处于空闲状态,只能等待I/O操作完成后才能继续执行后面的指令。为了提高CPU利用率,减少等待时间,提出了CPU并发工作理论。

所谓并发,就是通过算法将CPU资源合理地分配给多个任务,当一个任务执行I/O操作时,CPU可以转而执行其它的任务,等到I/O操作完成以后,或者新的任务遇到I/O操作时,CPU再回到原来的任务继续执行。

下图(图19-1)展示了两个任务并发执行的过程(为了容易理解,这里以两个任务并发执行为例,当然一个CPU核心并不仅仅只能两个任务并发):

wKgZPGe8LQ2ATpY8AAEBBQfSMt4318.png

虽然CPU在同一时刻只能执行一个任务,但是通过将CPU的使用权在恰当的时机分配给不同的任务,使得多个任务看起来是一起执行的(CPU的执行速度极快,多任务切换的时间也极短)。

至此关于并发的概念就讲解完成了。

19.1.2并行

并发是针对单核CPU提出的,而并行则是针对多核CPU提出的。和单核CPU不同,多核CPU真正实现了“同时执行多个任务”。多核CPU的每个核心都可以独立地执行一个任务,而且多个核心之间不会相互干扰。在不同核心上执行的多个任务,是真正地同时运行,这种状态就叫做并行。双核CPU的工作状态如下图(图19-2)所示:

wKgZO2e8LQ2ANcaDAAG12uC-O9o637.png

双核CPU执行两个任务时,每个核心各自执行一个任务,和单核CPU在两个任务之间不断切换相比,它的执行效率更高。

至此对于并行的概念就讲解完成了。

19.1.3并发+并行

在并行的工作状态中,两个CPU分别执行两个任务,是一种理想状态。但是在实际场景中,处于运行状态的任务是非常多的,以实际办公电脑为例,windows系统在开机之后会运行几十个任务,而CPU往往只有4核、8核等,远远低于任务的数量,这个时候就会同时存在并发和并行两种情况,即所有核心在并行工作的同时,每个核心还要并发工作。

例如一个双核 CPU要执行四个任务,它的工作状态如下图(图19-3)所示:

wKgZPGe8LQ2AJxzpAAGU7E7MGq4039.png

为了容易理解,这里是以两个任务并发执行为例,当然一个CPU核心并不仅仅只能两个任务并发,并发任务的数量和操作系统的分配方式、以及每个任务的工作状态有关系。

至此,对于并发+并行的概念讲解就结束了。

并发可以看作是并行的理想状态,为了便于讲解和避免产生歧义,之后的章节无论是并发还是并行,都会统称为并发。

19.1.4竞争

并发可能会造成多个程序同时访问一个共享资源,这时候由并发同时访问一个共享资源产生的问题就叫做竞争。

竞争产生的原因如下所示:

(1)多线程的并发访问。由于Linux是多任务操作系统,所以多线程访问是竞争产生的基本原因。

(2)中断程序的并发访问。中断任务产生后,CPU会立刻停止当前工作,从而去执行中断中的任务,如果中断任务对共享资源进行了修改,就会产生竞争。

(3)抢占式并发访问。linux2.6及更高版本引入了抢占式内核,高优先级的任务可以打断低优先级的任务。在线程访问共享资源的时候,另一个线程打断了现在正在访问共享资源的线程同时也对共享资源进行操作,从而造成了竞争。

(4)多处理器(SMP)并发访问。多核处理器之间存在核间并发访问。

19.1.5共享资源的保护

竞争是由并发访问同一个共享资源产生的。为了防止“竞争”的产生就要对共享资源进行保护,这里提到的共享资源又是什么呢?

以实际生活中的共享资源为例,可以是公共电话,也可以是共享单车、共享充电宝等公共物品,以上都属于共享资源的范畴,以公共电话为例,每个人都可以对它进行使用,但在同一时间内只能由一个人进行使用,如果两个人都要对电话进行使用,则产生了竞争。而在实际的驱动的代码中,共享资源可以是全局变量,也可以是驱动中的设备结构体等,需要根据具体的驱动程序来进行分析。在下一小节的实验中,会以全局变量为例,进行并发与竞争实验。

19.2实验程序的编写

19.2.1驱动程序编写

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

本实验将编写并发与竞争的驱动代码,首先完善字符设备驱动框架,然后通过copy_from_user(…)函数接收用户空间传递到内核空间的数据并进行判断,如果接收到的字符串数据为“topeet”会在睡眠4秒钟后打印接收到的数据,如果接收到的字符串数据为“itop”会在睡眠2秒钟后打印接收到的数据。

编写完成的example.c代码如下所示

#include

#include

#include

#include

#include

#include

#include

static int open_test(struct inode *inode,struct file *file)

{

printk("\nthis is open_test \n");

return 0;

}

static ssize_t read_test(struct file *file,char __user *ubuf,size_t len,loff_t *off)

{

int ret;

char kbuf[10] = "topeet";//定义char类型字符串变量kbuf

printk("\nthis is read_test \n");

ret = copy_to_user(ubuf,kbuf,strlen(kbuf));//使用copy_to_user接收用户空间传递的数据

if (ret != 0){

printk("copy_to_user is error \n");

}

printk("copy_to_user is ok \n");

return 0;

}

static char kbuf[10] = {0};//定义char类型字符串全局变量kbuf

static ssize_t write_test(struct file *file,const char __user *ubuf,size_t len,loff_t *off)

{

int ret;

ret = copy_from_user(kbuf,ubuf,len);//使用copy_from_user接收用户空间传递的数据

if (ret != 0){

printk("copy_from_user is error\n");

}

if(strcmp(kbuf,"topeet") == 0 ){//如果传递的kbuf是topeet就睡眠四秒钟

ssleep(4);

}

else if(strcmp(kbuf,"itop") == 0){//如果传递的kbuf是itop就睡眠两秒钟

ssleep(2);

}

printk("copy_from_user buf is %s \n",kbuf);

return 0;

}

static int release_test(struct inode *inode,struct file *file)

{

//printk("\nthis is release_test \n");

return 0;

}

struct chrdev_test {

dev_t dev_num;//定义dev_t类型变量dev_num来表示设备号

int major,minor;//定义int类型的主设备号major和次设备号minor

struct cdev cdev_test;//定义struct cdev类型结构体变量cdev_test,表示要注册的字符设备

struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类

};

struct chrdev_test dev1;//创建chrdev_test类型的

struct file_operations fops_test = {

.owner = THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块

.open = open_test,//将open字段指向open_test(...)函数

.read = read_test,//将read字段指向read_test(...)函数

.write = write_test,//将write字段指向write_test(...)函数

.release = release_test,//将release字段指向release_test(...)函数

};

static int __init atomic_init(void)

{

if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自动获取设备号,设备名chrdev_name

printk("alloc_chrdev_region is error \n");

}

printk("alloc_chrdev_region is ok \n");

dev1.major = MAJOR(dev1.dev_num);//使用MAJOR()函数获取主设备号

dev1.minor = MINOR(dev1.dev_num);//使用MINOR()函数获取次设备号

printk("major is %d,minor is %d\n",dev1.major,dev1.minor);

cdev_init(&dev1.cdev_test,&fops_test);//使用cdev_init()函数初始化cdev_test结构体,并链接到fops_test结构体

dev1.cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块

cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用cdev_add()函数进行字符设备的添加

dev1.class_test = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_test

device_create(dev1.class_test,0,dev1.dev_num,0,"device_test");//使用device_create进行设备的创建,设备名称为device_test

return 0;

}

static void __exit atomic_exit(void)

{

device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备

class_destroy(dev1.class_test);//删除创建的类

cdev_del(&dev1.cdev_test);//删除添加的字符设备cdev_test

unregister_chrdev_region(dev1.dev_num,1);//释放字符设备所申请的设备号

printk("module exit \n");

}

module_init(atomic_init);

module_exit(atomic_exit)

MODULE_LICENSE("GPL v2");

MODULE_AUTHOR("topeet");

对于重要逻辑部分已经加粗,后续章节的实验都是对上述并发与竞争实验的改进,以不同的方式来避免竞争的产生。

19.2.2编写测试APP

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

本测试app较为简单,需要输入两个参数,第一个参数为对应的设备节点,第二个参数为“topeet”或者“itop”,分别代表向设备写入的数据,编写完成的应用程序app.c内容如下所示:

#include

#include

#include

#include

#include

#include

int main(int argc, char *argv[])

{

int fd;//定义int类型的文件描述符

char str1[10] = {0};//定义读取缓冲区str1

fd = open(argv[1],O_RDWR);//调用open函数,打开输入的第一个参数文件,权限为可读可写

if(fd < 0 ){

printf("file open failed \n");

return -1;

}

/*如果第二个参数为topeet,条件成立,调用write函数,写入topeet*/

if (strcmp(argv[2],"topeet") == 0 ){

write(fd,"topeet",10);

}

/*如果第二个参数为itop,条件成立,调用write函数,写入itop*/

else if (strcmp(argv[2],"itop") == 0 ){

write(fd,"itop",10);

}

close(fd);

return 0;

}

19.3运行测试

19.3.1编译驱动程序

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

export ARCH=arm64#设置平台架构

export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀

obj-m += example.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的内容注释已在上图添加,保存退出之后,来到存放example.c和Makefile文件目录下,如下图所示:

wKgZPGe8LQuALbRIAACG4QnbV6M606.png

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

wKgZO2e8LQ6AZwabAAKEHCIrLuY942.png

编译完生成example.ko目标文件,如下图(图19-6)所示:

wKgZO2e8LQuASpHnAAD2ogIkBP0752.png

至此驱动模块就编译成功了,下面进行应用程序的编译。

19.3.2编译应用程序

来到应用程序app.c文件的存放路径如下图(图19-7)所示:

wKgZPGe8LQuAa85QAABYfgBxKDc393.png

然后使用以下命令对app.c进行交叉编译,编译完成如下图(图19-8)所示:

1

aarch64-linux-gnu-gcc -o app app.c -static

wKgZO2e8LQuAOCGzAADBqYwoMio972.png

生成的app文件就是之后放在开发板上运行的可执行文件,至此应用程序的编译就完成了。

19.3.3运行测试

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

1

insmod example.ko

wKgZO2e8LQuAXHpIAADMVfqOgBE144.png

可以看到申请的主设备号和次设备号就被打印了出来,然后使用以下代码对自动生成的设备节点device_test进行查看,如下图(图19-10)所示:

1

ls /dev/device_test

wKgZPGe8LQuANV-EAAB7UXUI4Ik807.png

可以看到device_test节点已经被自动创建了,然后使用以下命令运行测试app,运行结果如下图(图19-11)所示:

1

./app /dev/device_test topeet

wKgZO2e8LQ2ACcJEAAECoKTrPqg955.png

可以看到传递的buf值为topeet,然后输入以下命令在后台运行两个app,来进行竞争测试,运行结果如下图(图19-12)所示:

./app /dev/device_test topeet &

./app /dev/device_test itop &

wKgZO2e8LQ2ALCJ9AAHzmWTROCM773.png

在不存在竞争的情况下,传递的两个字符串数据应该是topeet和itop,而在上图中的打印信息为两个itop,原因是第二个app应用程序运行之后对共享资源进行了修改,两个app应用程序就产生了竞争关系,会在之后的章节中使用不同的方法对上述驱动程序进行改进,从而避免竞争的产生。

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

1

rmmod example.ko

wKgZPGe8LQ2Ab3_cAABvdcKT5jw156.png

至此,并发与竞争的实验就完成了。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 驱动
    +关注

    关注

    12

    文章

    1928

    浏览量

    88203
  • 开发板
    +关注

    关注

    25

    文章

    6122

    浏览量

    113328
  • RK3568
    +关注

    关注

    5

    文章

    628

    浏览量

    7584
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    迅为RK3568开发板驱动指南Linux中通用SPI设备驱动

    迅为RK3568开发板驱动指南Linux中通用SPI设备驱动
    的头像 发表于 01-23 11:02 3427次阅读
    迅为<b class='flag-5'>RK3568</b>开发板<b class='flag-5'>驱动</b><b class='flag-5'>指南</b>Linux中通用SPI设备<b class='flag-5'>驱动</b>

    迅为RK3568开发板驱动指南GPIO子系统级节点操作函数实验

    迅为RK3568开发板驱动指南GPIO子系统级节点操作函数实验
    的头像 发表于 05-26 15:39 1267次阅读
    迅为<b class='flag-5'>RK3568</b>开发板<b class='flag-5'>驱动</b><b class='flag-5'>指南</b>GPIO子系统<b class='flag-5'>三</b>级节点操作函数<b class='flag-5'>实验</b>

    文档更新 |迅为 RK3568开发板驱动指南-第十五/十六

    第三篇 并发竞争 19 并发
    发表于 07-08 11:04

    文档更新 | 迅为RK3568驱动指南-第十七篇(串口)

    实验 14 内核空间与用户空间数据交互实验 15 文件私有数据
    发表于 09-24 10:42

    迅为iTOP-RK3568开发板驱动开发指南-第十八 PWM

    17 Linux错误处理实验 18 点亮LED灯实验第三篇
    发表于 10-29 10:13

    iTOP-RK3568开发板驱动指南第五-中断

    _字符设备基础 第三期_并发竞争 第四期_高级字符设备进阶 第五期_中断 第六期_平台总线 第七期_设备树 第八期_设备树插件 第九期_设备模型 第十期_热插拔 第十一期_pinctrl子系统 未完待续,持续更新中...哔哩哔
    发表于 09-04 10:53

    更新 | 持续开源 迅为RK3568驱动指南第十一篇-pinctrl子系统

    热插拔 11 pinctrl子系统 未完待续,持续更新中... 视频教程更新至十二期 第一期_驱动基础 第二期_字符设备基础 第三
    发表于 10-18 11:12

    RK3568驱动指南驱动基础进阶-进阶8 内核运行ko文件总结

    RK3568驱动指南驱动基础进阶-进阶8 内核运行ko文件总结
    的头像 发表于 01-31 14:58 2010次阅读
    <b class='flag-5'>RK3568</b><b class='flag-5'>驱动</b><b class='flag-5'>指南</b>|<b class='flag-5'>驱动</b>基础进阶<b class='flag-5'>篇</b>-进阶8 内核运行ko文件总结

    RK3568驱动指南驱动基础进阶-进阶5 自定义实现insmod命令实验

    RK3568驱动指南驱动基础进阶-进阶5 自定义实现insmod命令实验
    的头像 发表于 02-20 14:10 1446次阅读
    <b class='flag-5'>RK3568</b><b class='flag-5'>驱动</b><b class='flag-5'>指南</b>|<b class='flag-5'>驱动</b>基础进阶<b class='flag-5'>篇</b>-进阶5 自定义实现insmod命令<b class='flag-5'>实验</b>

    RK3568驱动指南驱动基础进阶-进阶7 向系统中添加一个系统调用

    RK3568驱动指南驱动基础进阶-进阶7 向系统中添加一个系统调用
    的头像 发表于 05-21 14:15 551次阅读
    <b class='flag-5'>RK3568</b><b class='flag-5'>驱动</b><b class='flag-5'>指南</b>|<b class='flag-5'>驱动</b>基础进阶<b class='flag-5'>篇</b>-进阶7 向系统中添加一个系统调用

    RK3568驱动指南|第十二 GPIO子系统-135 GPIO子系统与pinctrl子系统相结合实验

    RK3568驱动指南|第十二 GPIO子系统-135 GPIO子系统与pinctrl子系统
    的头像 发表于 05-23 13:47 746次阅读
    <b class='flag-5'>RK3568</b><b class='flag-5'>驱动</b><b class='flag-5'>指南</b>|第十二<b class='flag-5'>篇</b> GPIO子系统-<b class='flag-5'>第</b>135<b class='flag-5'>章</b> GPIO子系统与pinctrl子系统相结合<b class='flag-5'>实验</b>

    迅为RK3568驱动指南GPIO子系统 GPIO操作函数实验

    迅为电子RK3568开发板驱动指南GPIO子系统 GPIO操作函数实验
    的头像 发表于 05-28 15:24 1046次阅读
    迅为<b class='flag-5'>RK3568</b><b class='flag-5'>驱动</b><b class='flag-5'>指南</b>GPIO子系统 GPIO操作函数<b class='flag-5'>实验</b>

    RK3568驱动指南|第十二 GPIO子系统-130 GPIO的调试方法

    RK3568驱动指南|第十二 GPIO子系统-130 GPIO的调试方法
    的头像 发表于 06-03 11:32 988次阅读
    <b class='flag-5'>RK3568</b><b class='flag-5'>驱动</b><b class='flag-5'>指南</b>|第十二<b class='flag-5'>篇</b> GPIO子系统-<b class='flag-5'>第</b>130<b class='flag-5'>章</b> GPIO的调试方法

    迅为RK3568 重制版RK3568驱动指南全面升级

    迅为RK3568 重制版RK3568驱动指南全面升级
    的头像 发表于 07-28 15:25 1498次阅读
    迅为<b class='flag-5'>RK3568</b> 重制版<b class='flag-5'>RK3568</b><b class='flag-5'>驱动</b><b class='flag-5'>指南</b>全面升级

    【迅为工业RK3568稳定可靠】itop-3568开发板驱动开发4驱动模块传参实验

    【迅为工业RK3568稳定可靠】itop-3568开发板驱动开发4驱动模块传参
    的头像 发表于 11-06 14:25 173次阅读
    【迅为工业<b class='flag-5'>RK3568</b>稳定可靠】itop-<b class='flag-5'>3568</b>开发板<b class='flag-5'>驱动</b>开发<b class='flag-5'>第</b>4<b class='flag-5'>章</b><b class='flag-5'>驱动</b>模块传参<b class='flag-5'>实验</b>