【系列文章】Linux中的并发与竞争[05]-互斥量

该文章为系列文章:Linux中的并发与竞争中的第5篇
该系列的导航页连接:
【系列文章】Linux中的并发与竞争-导航页


文章目录

  • 【系列文章】Linux中的并发与竞争[05]-互斥量
    • 一、互斥锁
    • 二、实验程序的编写
      • 2.1驱动程序编写
      • 2.2编写测试 APP
      • 2.3运行测试


一、互斥锁

在上一文章中,将信号量量值设置为 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_OWNERstruct optimistic_spin_queue osq; /* Spinner MCS lock */#endifstruct list_head wait_list;#ifdef CONFIG_DEBUG_MUTEXESvoid *magic;#endif#ifdef CONFIG_DEBUG_LOCK_ALLOCstruct lockdep_map dep_map;#endif
};

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

函数描述
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。

二、实验程序的编写

2.1驱动程序编写

由于互斥体在同一时间内只允许一个任务对共享资源进行,所以除了在 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_teststatic 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 类型字符串变量 kbufprintk("\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 类型字符串全局变量 kbufstatic 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 和次设备号 minorstruct 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_nameprintk("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()函数初始化 cdev_test 结构体,并链接到fops_test 结构体cdev_init(&dev1.cdev_test,&fops_test);//将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块dev1.cdev_test.owner = THIS_MODULE;cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用 cdev_add()函数进行字符设备的添加//使用 class_create 进行类的创建,类名称为class_testdev1.class_test = class_create(THIS_MODULE,"class_test");//使用 device_create 进行设备的创建,设备名称为 device_testdevice_create(dev1.class_test,0,dev1.dev_num,0,"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_testunregister_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");

2.2编写测试 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};//定义读取缓冲区 str1fd = 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;
}

2.3运行测试

使用以下命令运行测试 app,运行结果如下图所示:

./app /dev/device_test topeet

在这里插入图片描述
可以看到传递的 buf 值为 topeet,然后输入以下命令在后台运行两个 app,来进行竞争测试,运行结果如下图所示:

./app /dev/device_test topeet &
./app /dev/device_test itop

在这里插入图片描述
两个 app 被同时运行,最终打印信息正常,证明数据被正确传递了,没有发生共享资源的竞争,证明互斥量就起到了作用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/96763.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/96763.shtml
英文地址,请注明出处:http://en.pswp.cn/web/96763.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TensorRT 10.13.3: Limitations

Limitations Shuffle-op can not be transformed to no-op for perf improvement in some cases. For the NCHW32 format, TensorRT takes the third-to-last dimension as the channel dimension. When a Shuffle-op is added like [N, ‘C’, H, 1] -> [‘N’, C, H], the…

Python与Go结合

Python与Go结合的方法Python和Go可以通过多种方式结合使用&#xff0c;通常采用跨语言通信或集成的方式。以下是几种常见的方法&#xff1a;使用CFFI或CGO进行绑定Python可以通过CFFI&#xff08;C Foreign Function Interface&#xff09;调用Go编写的库&#xff0c;而Go可以通…

C++ 在 Visual Studio Release 模式下,调试运行与直接运行 EXE 的区别

前言 在 Visual Studio (以下简称 VS) 中开发 C 项目时&#xff0c;我们常常需要在 Debug 和 Release 两种构建模式之间切换。Debug 模式适合开发和调试&#xff0c;而 Release 模式则针对生产环境&#xff0c;进行代码优化以提升性能。然而&#xff0c;即使在 Release 模式下&…

南京方言数据集|300小时高质量自然对话音频|专业录音棚采集|方言语音识别模型训练|情感计算研究|方言保护文化遗产数字化|语音情感识别|方言对话系统开发

引言与背景 随着人工智能技术的快速发展&#xff0c;语音识别和自然语言处理领域对高质量方言数据的需求日益增长。南京方言作为江淮官话的重要分支&#xff0c;承载着丰富的地域文化和语言特色&#xff0c;在语言学研究和方言保护方面具有重要价值。本数据集精心采集了300小时…

基于LSTM深度学习的电动汽车电池荷电状态(SOC)预测

基于LSTM深度学习的电动汽车电池荷电状态&#xff08;SOC&#xff09;预测 摘要 电动汽车&#xff08;EV&#xff09;的普及对电池管理系统&#xff08;BMS&#xff09;提出了极高的要求。电池荷电状态&#xff08;State of Charge, SOC&#xff09;作为BMS最核心的参数之一&am…

Golang语言之数组、切片与子切片

一、数组先记住数组的核心特点&#xff1a;盒子大小一旦定了就改不了&#xff08;长度固定&#xff09;&#xff0c;但盒子里的东西能换&#xff08;元素值可变&#xff09;。就像你买了个能装 3 个苹果的铁皮盒&#xff0c;想多装 1 个都不行&#xff0c;但里面的苹果可以换成…

速通ACM省铜第四天 赋源码(G-C-D, Unlucky!)

目录 引言&#xff1a; G-C-D, Unlucky! 题意分析 逻辑梳理 代码实现 结语&#xff1a; 引言&#xff1a; 因为今天打了个ICPC网络赛&#xff0c;导致坐牢了一下午&#xff0c;没什么时间打题目了&#xff0c;就打了一道题&#xff0c;所以&#xff0c;今天我们就只讲一题了&…

数据链路层总结

目录 &#xff08;一&#xff09;以太网&#xff08;IEEE 802.3&#xff09; &#xff08;1&#xff09;以太网的帧格式 &#xff08;2&#xff09;帧协议类型字段 ①ARP协议 &#xff08;横跨网络层和数据链路层的协议&#xff09; ②RARP协议 &#xff08;二&#xff…

Scala 新手实战三案例:从循环到条件,搞定基础编程场景

Scala 新手实战三案例&#xff1a;从循环到条件&#xff0c;搞定基础编程场景 对 Scala 新手来说&#xff0c;单纯记语法容易 “学完就忘”&#xff0c;而通过小而精的实战案例巩固知识点&#xff0c;是掌握语言的关键。本文精选三个高频基础场景 ——9 乘 9 乘法口诀表、成绩等…

java学习笔记----标识符与变量

1.什么是标识符?Java中变量、方法、类等要素命名时使用的字符序列&#xff0c;称为标识符。 技巧:凡是自己可以起名字的地方都叫标识符。 比如:类名、方法名、变量名、包名、常量名等 2.标识符的命名规则由26个英文字母大小写&#xff0c;0-9&#xff0c;或$组成 数字不可以开…

AI产品经理面试宝典第93天:Embedding技术选型与场景化应用指南

1. Embedding技术演进全景解析 1.1 稀疏向量:关键词匹配的基石 1.1.1 问:请说明稀疏向量的适用场景及技术特点 答:稀疏向量适用于关键词精确匹配场景,典型实现包括TF-IDF、BM25和SPLADE。其技术特征表现为50,000+高维向量且95%以上位置为零值,通过余弦或点积计算相似度…

【Mermaid.js】从入门到精通:完美处理节点中的空格、括号和特殊字符

文章标签&#xff1a; Mermaid, Markdown, 前端开发, 数据可视化, 流程图 文章摘要&#xff1a; 你是否在使用 Mermaid.js 绘制流程图时&#xff0c;仅仅因为节点文本里加了一个空格或括号&#xff0c;整个图就渲染失败了&#xff1f;别担心&#xff0c;这几乎是每个 Mermaid 新…

多技术融合提升环境生态水文、土地土壤、农业大气等领域的数据分析与项目科研水平

一&#xff1a;空间数据获取与制图1.1 软件安装与应用1.2 空间数据介绍1.3海量空间数据下载1.4 ArcGIS软件快速入门1.5 Geodatabase地理数据库二&#xff1a;ArcGIS专题地图制作2.1专题地图制作规范2.2 空间数据的准备与处理2.3 空间数据可视化&#xff1a;地图符号与注记2.4 研…

【音视频】Android NDK 与.so库适配

一、名词解析 名词全称核心说明Android NDKNative Development Kit在SDK基础上增加“原生”开发能力&#xff0c;支持使用C/C编写代码&#xff0c;用于开发需要调用底层能力的模块&#xff08;如音视频、加密算法等&#xff09;.so库Shared Object即共享库&#xff0c;由NDK编…

SpringBoot 轻量级一站式日志可视化与JVM监控

一、项目初衷Java 应用开发的同学都知道&#xff0c;项目上线后&#xff0c;日志的可视化查询与 JVM 的可视化监控是一件非常重要的事。 市面上成熟方案一般是采用 ELK/EFK 实现日志可视化&#xff0c;采用 Actuator Prometheus Grafana 实现 JVM 监控。 这两套都是非常优秀的…

【Leetcode hot 100】101.对称二叉树

问题链接 101.对称二叉树 问题描述 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;…

Zynq开发实践(FPGA之选择开发板)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】我们之所以选用zynq开发板&#xff0c;就在于它支持arm软件开发&#xff0c;也支持fpga开发&#xff0c;甚至可以运行linux&#xff0c;这是之前没有…

Flutter Riverpod 3.0 发布,大规模重构下的全新状态管理框架

在之前的 《注解模式下的 Riverpod 有什么特别之处》我们聊过 Riverpod 2.x 的设计和使用原理&#xff0c;同时当时我们就聊到作者已经在开始探索 3.0 的重构方式&#xff0c;而现在随着 Riverpod 3.0 的发布&#xff0c;riverpod 带来了许多细节性的变化。 当然&#xff0c;这…

Xcode 上传 ipa 全流程详解 App Store 上架流程、uni-app 生成 ipa 文件上传与审核指南

对于 iOS 开发者而言&#xff0c;应用开发完成后最重要的一步就是将应用打包为 ipa 文件&#xff0c;并上传至 App Store Connect 进行分发或上架。 其中&#xff0c;Xcode 上传 ipa 是最常见的方法&#xff0c;但很多开发者在实际操作中常常遇到卡住、上传失败或签名错误等问题…

快速选中对象

图片要求 图片背景单纯&#xff0c;对象边缘比较清晰 对象选择工具 选择对象选择工具后&#xff0c;画出大致区域&#xff0c;系统将自动分析图片内容&#xff0c;从而实现快速选择图片中的一个惑多个对象他有两种模式&#xff0c;分别是举行与套索模式。使用时可以先选中对象的…