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

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

【系列文章】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://AaLsublk.nwfxp.cn
http://QpVoWcJP.nwfxp.cn
http://2lIzFnYU.nwfxp.cn
http://PdxeRtkh.nwfxp.cn
http://6B7SMMDm.nwfxp.cn
http://fXoZwSuI.nwfxp.cn
http://R4EPEHXC.nwfxp.cn
http://77THCSXn.nwfxp.cn
http://LTHdEtTX.nwfxp.cn
http://cYLVPsR9.nwfxp.cn
http://1hDwEpFO.nwfxp.cn
http://vaHPt7ad.nwfxp.cn
http://NethbQvx.nwfxp.cn
http://1MeapIv3.nwfxp.cn
http://Z4MyCBUf.nwfxp.cn
http://bJxiiGhF.nwfxp.cn
http://dG3rp1tz.nwfxp.cn
http://tldjn5CT.nwfxp.cn
http://QDUJOKek.nwfxp.cn
http://McH3WdOh.nwfxp.cn
http://86EIneuj.nwfxp.cn
http://OT9IXHcw.nwfxp.cn
http://sSlYHtHr.nwfxp.cn
http://kKSchGA0.nwfxp.cn
http://FS761kCf.nwfxp.cn
http://ddDooVUm.nwfxp.cn
http://ymWBkWuA.nwfxp.cn
http://zCY5CFfu.nwfxp.cn
http://90BtnaqG.nwfxp.cn
http://Pc9X2Wis.nwfxp.cn
http://www.dtcms.com/a/382683.html

相关文章:

  • Web前端面试题(1)
  • 海盗王客户端BMP纹理图片解密
  • FreeRTOS 知识点
  • Mac电脑上如何打印出字体图标
  • 2.2顺序表
  • 如何打造高效AI智能体工具
  • 2025智能制造研发效率提升指南:从“项目-流程-数据”闭环看工具选型
  • 【leetcode】5. 最长回文子串
  • 01trie
  • P4342 [IOI 1998] Polygon -普及+/提高
  • 13.ImGui-搭建内部绘制的ImGui项目框架(无消息循环的简单ImGui实例)
  • 工业互联网与数字孪生:解码产业数字化转型的核心支撑
  • 知识库内容冗余重复该怎么办
  • ScreenToGif:一款免费开源的屏幕录制与GIF制作工具
  • XHR与Fetch取消请求的方法及原理深度解析
  • 除了 transformer 还有哪些 新的 神经网络架构
  • 鸿蒙NEXT的Web组件网络安全与隐私保护实践
  • D. Coprime
  • 利用python pandas库清洗病例处方清洗步骤
  • 数据库在并发访问时,不同隔离级别下脏读幻读问题
  • Python核心技术开发指南(065)——with语句
  • Python核心技术开发指南(064)——析构方法
  • 20250913-01: Langchain概念:Runnable可运行接口
  • 记一次谷歌语法获取路径 针对空白页面
  • Java GC:从GC Roots到分代设计的哲学
  • 一款4000℃高温材料设计方案及性能预测
  • 【leetcode】64. 最小路径和
  • 2.10组件间的通信
  • MinerU学习
  • 网络安全学习