当前位置: 首页 > news >正文

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

【系列文章】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://3hD3iuV1.xsrnr.cn
http://UbyhSMRI.xsrnr.cn
http://8f3qVehD.xsrnr.cn
http://GCrZzu0q.xsrnr.cn
http://I6nu20u0.xsrnr.cn
http://V1gLibuO.xsrnr.cn
http://3gVwvIrP.xsrnr.cn
http://go7dNb8q.xsrnr.cn
http://Bj3mcnJN.xsrnr.cn
http://o84QQPZb.xsrnr.cn
http://6yC9K4A2.xsrnr.cn
http://tdAcZpZw.xsrnr.cn
http://b5uImqbu.xsrnr.cn
http://5CnkBWiQ.xsrnr.cn
http://jLfmhl4D.xsrnr.cn
http://hx2BKjHE.xsrnr.cn
http://LE6v7RtT.xsrnr.cn
http://aHLCTygA.xsrnr.cn
http://WsMy5EuP.xsrnr.cn
http://GN5th1bX.xsrnr.cn
http://XPYK6ikW.xsrnr.cn
http://PD6ombvf.xsrnr.cn
http://GSMu8BQi.xsrnr.cn
http://68Q8ux76.xsrnr.cn
http://rkomtOtO.xsrnr.cn
http://XoCY7PnT.xsrnr.cn
http://jN3RCz2s.xsrnr.cn
http://PStnikLc.xsrnr.cn
http://GAe1QVii.xsrnr.cn
http://Z50zYod9.xsrnr.cn
http://www.dtcms.com/a/383512.html

相关文章:

  • 海岛奇兵声纳活动的数学解答
  • 大模型入门实践指南
  • CSS 编码规范
  • Redis框架详解
  • Redis----缓存策略和注意事项
  • Redis的大key问题
  • 微服务学习笔记25版
  • 地址映射表
  • AI Agent 软件工程关键技术综述
  • 命令行工具篇 | grep, findstr
  • 6【鸿蒙/OpenHarmony/NDK】多线程调用 JS 总崩溃?用 napi_create_threadsafe_function 搞定线程安全交互
  • OpenTenBase分布式HTAP实战:从Oracle迁移到云原生数据库的完整指南
  • LabVIEW信号监测与分析
  • 【大模型算法工程师面试题】大模型领域新兴的主流库有哪些?
  • Java队列(从内容结构到经典练习一步到位)
  • Cherno OpenGL 教程
  • RT-DETRv2 中的坐标回归机制深度解析:为什么用 `sigmoid(inv_sigmoid(ref) + delta)` 而不是除以图像尺寸?
  • OpenCV入门教程
  • 深度学习-计算机视觉-目标检测三大算法-R-CNN、SSD、YOLO
  • 冰火两重天:AI重构下的IT就业图景
  • 从ENIAC到Linux:计算机技术与商业模式的协同演进——云原生重塑闭源主机,eBPF+WebAssembly 双引擎的“Linux 内核即服务”实践
  • 从 MySQL 迁移到 GoldenDB,上来就踩了一个坑。
  • qt界面开发入门以及计算器制作
  • SQL 核心概念与实践总结
  • 【Tourbox】怎么复制预设?
  • RTT操作系统(2)
  • 基于STM32单片机智能手表GSM短信上报GPS定位防丢器设计
  • 力扣658.找到K个最接近的元素
  • LeetCode 面试经典 150_哈希表_赎金信(39_383_C++_简单)
  • LeetCode热题100--114. 二叉树展开为链表--中等