【系列文章】Linux中的并发与竞争[03]-自旋锁

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


文章目录

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


在上一篇文章中对原子操作进行了讲解,并使用原子整形操作对并发与竞争实验进行了改进,但是原子操作只能对整形变量或者位进行保护,而对于结构体或者其他类型的共享资源,原子操作就力不从心了,这时候就轮到自旋锁的出场了。

一、自旋锁

自旋锁是为了保护共享资源提出的一种锁机制。自旋锁(spin lock)是一种非阻塞锁,也就是说,如果某线程需要获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗 CPU 的时间,不停的试图获取锁。

在有些场景中,同步资源(用来保持一致性的两个或多个资源)的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果计算机有多个CPU 核心,能够让两个或以上的线程同时并行执行,这样我们就可以让后面那个请求锁的线程不放弃 CPU 的执行时间,直到持有锁的线程释放锁,后面请求锁的线程才可以获取锁。为了让后面那个请求锁的线程“稍等一下”,我们需让它进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么该线程便不必阻塞,并且直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。我们再举个形象生动的例子,以现实生活中银行 ATM 机办理业务为例,ATM 机防护舱在同一时间内只允许一个人进入,当有人进入 ATM 机防护舱之后,两秒钟之后自动上锁,其他也想要存取款的人员,只能在外部等待,办理完相应的存取款业务之后,舱内人员需要手动打开防护锁,其他人才能进入其中,办理业务。而自旋锁在驱动
中的使用和上述 ATM 机办理业务流程相同,当一个任务要访问某个共享资源之前需要先获取相应的自旋锁,自旋锁只能被一个任务持有,在该任务持有自旋锁的过程中,其他任务只能原地等待该自旋锁的释放,在等待过程中的任务同样会持续占用 CPU,消耗 CPU 资源,所以临界区的代码不能太多。

内核中以 spinlock_t 结构体来表示自旋锁,定义在“内核源码/include/linux/spinlock_types.h” 文件中,如下所示:

typedef struct spinlock {union {struct raw_spinlock rlock;#ifdef CONFIG_DEBUG_LOCK_ALLOC# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))struct {u8 __padding[LOCK_PADSIZE];struct lockdep_map dep_map;};#endif};
} spinlock_t;

自旋锁相关 API 函数定义在“内核源码/include/linux/spinlock.h”文件中

函数描述
DEFINE_SPINLOCK(spinlock_t lock)定义并初始化自旋锁。
int spin_lock_init(spinlock_t *lock)初始化自旋锁。
void spin_lock(spinlock_t *lock)获取指定的自旋锁,也叫做加锁。
void spin_unlock(spinlock_t *lock)释放指定的自旋锁。
int spin_trylock(spinlock_t *lock)尝试获取指定的自旋锁,如果没有获取到就返回0
int spin_is_locked(spinlock_t *lock)检查指定的自旋锁是否被获取,如果没有被获取就返回非0,否则返 回 0。

除了上述 API 之外还有其他与终端相关的自旋锁 API 函数,会在接下来的自旋锁死锁进行讲解。

自旋锁的使用步骤:
1 在访问临界资源的时候先申请自旋锁
2 获取到自旋锁之后就进入临界区,获取不到自旋锁就“原地等待”。
3 退出临界区的时候要释放自旋锁。

二、实验程序的编写

2.1驱动程序编写

与上一篇使用原子整形操作避免并发与竞争逻辑相同,在驱动入口函数初始化自旋锁,然后在 open 函数中使用自旋锁实现对设备的互斥访问,最后在 release 函数中解锁,表示设备被释放了,可以被其他的应用程序使用。上述操作都将共享资源由自旋锁进行保护,从而实现同一时间内只允许一个应用打开该设备节点,以此来防止共享资源竞争的产生。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/spinlock.h>static spinlock_t spinlock_test;//定义 spinlock_t 类型的自旋锁变量 spinlock_test
static int flag = 1;//定义 flag 标准为,flag 等于 1 表示设备没有被打开,等于 0 则证明设备已经被打开了static int open_test(struct inode *inode,struct file *file)
{//printk("\nthis is open_test \n");spin_lock(&spinlock_test);//自旋锁加锁if(flag != 1){//判断标志位 flag 的值是否等于 1spin_unlock(&spinlock_test);//自旋锁解锁return -EBUSY;}flag = 0;//将标志位的值设置为 0spin_unlock(&spinlock_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 类型字符串全局变量 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");spin_lock(&spinlock_test);//自旋锁加锁flag = 1;spin_unlock(&spinlock_test);//自旋锁解锁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)
{spin_lock_init(&spinlock_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

#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

在这里插入图片描述
可以看到应用程序在打开第二次/dev/device_test 文件的时候,出现了“file open failed”打印,证明文件打开失败,只有在第一个应用关闭相应的文件之后,下一个应用才能打开。本次实验的自旋锁只是对标志位 flag 进行保护,flag 用来表示设备的状态,确保同一时间内,该设备只能被一个应用程序打开。进而对共享资源进行保护。

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

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

相关文章

开始 ComfyUI 的 AI 绘图之旅-Cosmos Predict2世界模型文生图(全网首发,官网都没有更新)(十三)

文章标题一、Cosmos-Predict21.Cosmos Predict2 Text2Image 工作流1.1 下载工作流文件1.2 手动模型安装1.3 按步骤完成工作流运行本文介绍了如何在 ComfyUI 中完成 Cosmos-Predict2 文生图的工作流 一、Cosmos-Predict2 Cosmos-Predict2 是由 NVIDIA 推出的新一代物理世界基础模…

深度学习优化器进化史:从SGD到AdamW的原理与选择

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;注册即送-H卡级别算力&#xff0c;80G大显存&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生更享专属优惠。 引言&#xff1a;优化器——深度学习的引擎 在深度学习…

工商业屋顶分布式光伏监控系统助力园区企业错峰有序用电

一、行业痛点与需求分析分布式光伏发电作为清洁能源的重要形式&#xff0c;近年来在工商业屋顶、户用场景中快速普及。然而&#xff0c;其“小而散”的特性导致电网适应性、运维效率、安全管控等方面面临显著挑战&#xff1a;1.电网适应性难题&#xff1a;高渗透率场景下&#…

华为初级认证培训需要吗?HCIA考试考什么内容?自学还是报班?

大家好&#xff0c;这里是G-LAB IT实验室。 在信息技术发展日新月异的今天&#xff0c;华为的ICT认证逐渐成为了行业内重要的技术标杆。而HCIA&#xff08;Huawei Certified ICT Associate&#xff09;作为华为初级认证&#xff0c;对于ICT技术从业者来说&#xff0c;既是职业发…

元宇宙与旅游产业:沉浸式体验重构旅行全流程

1 元宇宙重构旅游核心场景1.1 目的地体验&#xff1a;从 “实地观光” 到 “虚实融合深度探索”传统旅游目的地体验受限于时间、空间与物理条件&#xff0c;元宇宙通过 “数字孪生 超现实创作”&#xff0c;打造 “超越实地” 的沉浸式目的地体验。在文化遗产体验中&#xff0…

sqlite3移植和使用(移植到arm上)

s3c2440 方法一&#xff1a; 在代码中编写插入命令 1.复制源代码并解压 源代码链接&#xff1a;SQLite Download Page 2.生成动态库 3.将动态库复制到根目录下的/usr/lib/下 4.编写一个操作文件sq_insert.c 5.编译sq_insert.c 6.将生成的运行文件 复制到根目录下 7.运行./s…

抗量子密码学算法

抗量子密码学算法的核心目标是抵抗量子计算机&#xff08;尤其是能运行Shor算法、Grover算法的大规模量子计算机&#xff09;的攻击&#xff0c;其安全性不依赖于传统的“大整数分解”“离散对数”等易被量子算法破解的数学问题&#xff0c;而是基于量子计算机难以高效求解的新…

设计模式(C++)详解—工厂方法模式(2)

<摘要> 工厂方法模式就像一个万能玩具工厂&#xff0c;爸爸&#xff08;抽象工厂&#xff09;定义了制作玩具的标准流程&#xff0c;但让儿子们&#xff08;具体工厂&#xff09;决定具体生产哪种玩具。这种模式解决了"既要规范生产流程&#xff0c;又要灵活适应变化…

187. Java 异常 - 什么是异常?

文章目录187. Java 异常 - 什么是异常&#xff1f;&#x1f6a8; 什么是“异常”&#xff1f;✅ 定义&#xff1a;&#x1f9f1; 异常对象中包含什么&#xff1f;Java 是怎么“处理”异常的&#xff1f;&#x1f9ef; 什么是异常处理器&#xff08;Exception Handler&#xff0…

3D Tiles 工具

概述 3D Tiles 工具是一组用于转换、优化、处理和分析 3D Tiles 数据的工具和实用程序。 安装 要在本地目录中安装 3D Tiles 工具&#xff0c;请运行以下命令&#xff1a; npm install 3d-tiles-tools如果想直接使用 Git 仓库的克隆版本&#xff0c;请参阅开发者设置。 命…

【编号520】全国4500多个地震灾害点位数据(2021.2-2025.8)

今天小编整理分享的是 全国4500多个地震灾害点位数据&#xff08;2021.2-2025.8)。概况数据概况全国4500多个地震灾害点位数据&#xff08;2021.2-2025.8&#xff09;地质灾害点位数据-地震&#xff01;数据含发生时间、地点、经纬坐标、灾害规模等。数据为shp格式和excel表格…

DriftingBlues: 4靶场渗透

DriftingBlues: 4 来自 <https://www.vulnhub.com/entry/driftingblues-4,661/> 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.128&#xff0c;靶场IP192.168.23…

GEO 优化专家孟庆涛以 AI 技术建体系,赋能多行业智能化转型

在生成式 AI 重塑全球搜索生态的浪潮中&#xff0c;中国 GEO&#xff08;生成式引擎优化&#xff09;领域的开拓者孟庆涛以 "智能决策革命" 的技术框架&#xff0c;颠覆了传统 "发发文章" 的简单认知。作为辽宁粤穗网络科技有限公司总经理兼 GEO 实验室主任…

架构很简单:从架构的角度学习源码

缘起最近出差比较多&#xff0c;在路上思考&#xff1a;如何学习源码&#xff1f;是的&#xff0c;面试官问你看了哪些源码&#xff1f;讲一讲&#xff1f;更高级的&#xff1a;说一下&#xfeff;Netty黏包拆包怎么实现的&#xff1f;或者再问的偏一点&#xff1f;讲一下某个功…

R的安装与使用

工作与学习需要&#xff0c;对R需要有一定的熟悉&#xff0c;特此记录 目录R的安装与使用R的安装R的安装与使用 R的安装 1.找到R的官网&#xff0c;百度直接搜索&#xff0c;或者www.bing.com国际版搜索即可&#xff0c;下载R与Rstudio&#xff0c;Rstudio是R的开发集成工具 …

界面规范10-树

字体字号和其他地方一致&#xff1a;.el-tree-node__label{font-size: $defaultTxtSize;font-family: $defaultFontFamily;}选中效果&#xff1a;.el-tree-node.is-current>.el-tree-node__content {background-color: #ffffff !important;color: #0000ff !important;font-w…

Win 11 ARM 版搭建ESP-IDF环境问题记录

整理我在ARM版 WIN 11 上配置ESP-IDF 环境的记录 WIN 11环境下搭建ESP-IDF 命令环境&#xff0c;有几种办法&#xff1a; 下载ESP-IDF 安装程序 从https://dl.espressif.com/dl/esp-idf/ 下载安装程序&#xff0c;下载的程序只能在AMD64位CPU上&#xff0c;不能在ARM 芯片上使用…

FreeBSD系统使用freebsd-update命令从14.2升级到14.3

FreeBSD系统使用freebsd-update命令从14.2升级到14.3 升级操作 先升级小版本命令 # 检查系统漏洞补丁 sudo freebsd-update fetch sudo freebsd-update install再升级到14.3 sudo freebsd-update upgrade -r 14.3-RELEASE 速度很慢啊 执行install # freebsd-update inst…

一键拖动去除文件夹及文件名称的空格

经常会碰见文件夹&#xff0c;以及其中文件命名有空格的情况&#xff1a;这些空格对有些代码程序不友好&#xff0c;做了一个一键拖动去除文件名称空格 只用把文件夹或者多个文件拖动到bat代码上就可以一键搞定 分为两个文件&#xff0c;需要放在一个目录下&#xff1a; replac…

Roo Code:用自然语言编程的VS Code扩展

在编程的世界里&#xff0c;常常需要记忆各种复杂的命令和语法&#xff0c;与机器进行“对话”。但 如果使用Roo Code&#xff0c;就可以让编程交流变得像日常对话一样简单自然。用自然语言“打字”交流 Roo Code的核心设计理念是理解自然语言。这意味着&#xff0c;无需掌握任…