RK3568驱动指南|第三篇-并发与竞争-第24章 互斥锁实验

瑞芯微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主板


第24章 互斥锁实验

在上一章节中对信号量进行了学习,而本章节要学习的互斥锁可以说是“量值”为 1 的信号量,最终实现的效果相同,既然有了信号量,那为什么还要有互斥锁呢,带着疑问,让我们来进行本章节的学习吧!

24.1 互斥锁

在上一章节中,将信号量量值设置为1,最终实现的就是互斥效果,与本章节要学习的互斥锁功能相同,虽然两者功能相同但是具体的实现方式是不同的,但是使用互斥锁效率更高、更简洁,所以如果使用到的信号量“量值”为 1,一般将其修改为使用互斥锁实现。

当有多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定或者非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性,能够保证多个线程访问共享数据不会出现资源竞争及数据错误。

为了方便大家理解,这里举个例子来说明。比如公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的。那么怎么解决这种情况呢?只要我在打印着的时候别人是不允许打印的,只有等我打印结束后别人才允许打印。这个过程有点类似于,把打印机放在一个房间里,给这个房间安把锁,这个锁默认是打开的。当 A 需要打印时,他先过来检查这把锁有没有锁着,没有的话就进去,同时上锁在房间里打印。而在这时,刚好 B 也需要打印,B 同样先检查锁,发现锁是锁住的,他就在门外等着。而当 A 打印结束后,他会开锁出来,这时候 B 才进去上锁打印。看了这个例子,相信大家已经理解了互斥锁。

互斥锁会导致休眠,所以在中断里面不能用互斥锁。同一时刻只能有一个线程持有互斥锁,并且只有持有者才可以解锁,并且不允许递归上锁和解锁。

内核中以mutex结构体来表示互斥体,定义在“内核源码/include/linux/mutex.h”文件中,如下所示:

struct mutex {
     atomic_long_t       owner;
     spinlock_t      wait_lock;
 #ifdef CONFIG_MUTEX_SPIN_ON_OWNER
     struct optimistic_spin_queue osq; /* Spinner MCS lock */
 #endif
     struct list_head    wait_list;
 #ifdef CONFIG_DEBUG_MUTEXES
     void            *magic;
 #endif
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
     struct lockdep_map  dep_map;
 #endif
 };

一些和互斥体相关的API函数也定义在mutex.h文件中,常用API函数如下(表24-1)所示:

函数描述
DEFINE_MUTEX(name)定义并初始化一个 mutex 变量。
void mutex_init(mutex *lock)初始化 mutex。
void mutex_lock(struct mutex *lock)获取 mutex,也就是给 mutex 上锁。如果获取不到就进休眠。
void mutex_unlock(struct mutex *lock)释放 mutex,也就给 mutex 解锁。
int mutex_is_locked(struct mutex *lock)判断 mutex 是否被获取,如果是的话就返回1,否则返回 0。

至此,关于互斥体相关的知识就讲解完成了,在下一小节的实验中会对上述API函数进行运用。

24.2 实验程序的编写

24.2.1 驱动程序编写

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

本小节实验将使用互斥体对19章的并发与竞争实验进行改进,由于互斥体在同一时间内只允许一个任务对共享资源进行,所以除了在atomic_init()函数内加入初始化互斥锁函数之外,只需要在open()函数中加入互斥锁加锁函数,在release()函数中加入互斥锁解锁函数即可。

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

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/mutex.h>

struct mutex mutex_test;//定义mutex类型的互斥锁结构体变量mutex_test
static int open_test(struct inode *inode,struct file *file)
{
	printk("\nthis is open_test \n");
	mutex_lock(&mutex_test);//互斥锁加锁
	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)
{
	mutex_unlock(&mutex_test);//互斥锁解锁
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)
{
	mutex_init(&mutex_test);//对互斥体进行初始化
	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");

24.2.2 编写测试 APP

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

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

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
 #include <unistd.h>
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;
}

24.3 运行测试

24.3.1 编译驱动程序

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

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += mutex.c    #此处要和你的驱动源文件同名
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的内容注释已在上图添加,保存退出之后,来到存放mutex.c和Makefile文件目录下,如下图(图24-2)所示:

img

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

img

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

img

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

24.3.2 编译应用程序

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

img

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

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

img

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

24.3.3 运行测试

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

insmod mutex.ko

img

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

ls /dev/device_test

img

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

./app /dev/device_test topeet

img

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

./app /dev/device_test topeet &

./app /dev/device_test itop 

img

​ 与23章实验测试现象相同,两个app被同时运行,最终打印信息正常,证明数据被正确传递了,没有发生共享资源的竞争,证明互斥量就起到了作用。

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

rmmod mutex.ko

img

至此,互斥体实验就完成了。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我了解了。下面是一个简单的示例,使用Huawei LiteOs模拟生产者-消费者交互过程。 生产者线程代码: ```c #include "los_sem.h" #include "los_mux.h" #define QUEUE_SIZE 10 UINT32 g_queue[QUEUE_SIZE]; UINT32 g_write_index = 0; UINT32 g_read_index = 0; LOS_Mux g_queue_mutex; LOS_Semaphore g_queue_sem; VOID ProducerTask(VOID) { while(1) { // 生产者获取队列互斥锁 LOS_MuxPend(&g_queue_mutex, LOS_WAIT_FOREVER); // 当队列满时,生产者等待信号量 if((g_write_index + 1) % QUEUE_SIZE == g_read_index) { LOS_MuxPost(&g_queue_mutex); LOS_SemPend(&g_queue_sem, LOS_WAIT_FOREVER); LOS_MuxPend(&g_queue_mutex, LOS_WAIT_FOREVER); } // 生产者将数据写入队列 g_queue[g_write_index] = produce_data(); g_write_index = (g_write_index + 1) % QUEUE_SIZE; // 生产者释放队列互斥锁 LOS_MuxPost(&g_queue_mutex); // 通知消费者有数据可用 LOS_SemPost(&g_queue_sem); } } ``` 消费者线程代码: ```c #include "los_sem.h" #include "los_mux.h" #define QUEUE_SIZE 10 UINT32 g_queue[QUEUE_SIZE]; UINT32 g_write_index = 0; UINT32 g_read_index = 0; LOS_Mux g_queue_mutex; LOS_Semaphore g_queue_sem; VOID ConsumerTask(VOID) { while(1) { // 消费者获取队列互斥锁 LOS_MuxPend(&g_queue_mutex, LOS_WAIT_FOREVER); // 当队列为空时,消费者等待信号量 if(g_read_index == g_write_index) { LOS_MuxPost(&g_queue_mutex); LOS_SemPend(&g_queue_sem, LOS_WAIT_FOREVER); LOS_MuxPend(&g_queue_mutex, LOS_WAIT_FOREVER); } // 消费者从队列中读取数据 consume_data(g_queue[g_read_index]); g_read_index = (g_read_index + 1) % QUEUE_SIZE; // 消费者释放队列互斥锁 LOS_MuxPost(&g_queue_mutex); // 通知生产者有空间可用 LOS_SemPost(&g_queue_sem); } } ``` 在这个示例中,生产者和消费者共享一个固定大小的队列。生产者在向队列中写入数据之前获取队列互斥锁,并在写入数据后释放该锁。如果队列已满,则生产者等待信号量,这表示有消费者已经读取了一些数据,腾出了一些空间。消费者在从队列中读取数据之前获取队列互斥锁,并在读取数据后释放该锁。如果队列为空,则消费者等待信号量,这表示生产者已经写入了一些数据,队列中已经有数据可供读取。 需要注意的是,在实际应用中,需要根据具体的需求和操作系统提供的API来实现信号量和互斥锁的操作。这里的示例代码仅供参考。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值