并发与竞争(三)自旋锁

news2024/11/18 15:33:32

文章目录

  • 自旋锁的概念
    • 什么是自旋锁?
    • 自旋锁的API函数(一)
    • 自旋锁的使用步骤
    • 其他自旋锁API函数(二)
    • 自旋锁的注意事项
    • 内核中自旋锁的实例
  • 自旋锁死锁
  • 写代码
    • 临界区在哪?
    • 最简单的实现逻辑
    • 完整实现

自旋锁的概念

什么是自旋锁?

 自旋锁是为了实现保护共享资源提出的一种锁机制,也是内核中比较常见的锁机制。自旋锁是以“原地等待”的方式解决资源冲突。即当线程A获取到自旋锁以后,此时线程B也想获取到自旋锁。但是线程B获取不到,只能“原地打转”(仍然占用CPU,不会休眠),不断尝试获取自旋锁,直到获取成功,然后才退出循环。

只有一个厕所,A想去厕所,B已经在厕所了,所以A只能在外面等待B,知道B上完厕所出来,A才能进去上厕所。

自旋锁的API函数(一)

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

自旋锁的使用步骤

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

其他自旋锁API函数(二)

函数描述
void spin_lock_irq(spinlock_t *lock)关闭中断并获取自旋锁
void spin_unlock_irq(spinlock_t *lock)打开中断并释放自旋锁
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags)保存中断状态,关闭中断并获取自旋锁
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)恢复之前保存的中断状态,打开中断并释放自旋锁
void spin_lock_bh(spinlock_t *lock)关闭下半部,获取自旋锁
void spin_unlock_bh(spinlock_t *lock)打开下半部,获取自旋锁

自旋锁的注意事项

  1. 由于自旋锁会“原地等待”,因为“原地等待”会继续占用CPU,会消耗CPU资源。所以锁的时间不能太长,也就是临界区的代码不能太多。
  2. 在自旋锁保护的临界区里面不能调用可能会导致线程休眠的函数,否则可能会发生死锁。
  3. 自旋锁一般是用在多核的SOC上面。

内核中自旋锁的实例

在这里插入图片描述

自旋锁死锁

 在多核CPU或者支持抢占的单核CPU中,被自旋锁保护的临界区不能调用任何能够引起睡眠或者阻塞的函数,否则可能会发生死锁。

 使用自旋锁会禁止抢占。比如在单核CPU中,A进程获取到自旋锁以后暂时关闭内核抢占,如果A进程此时进入了休眠(放弃了CPU的使用权),B进程此时也想获取到自旋锁,但是此时自旋锁被进程A持有,而且此时CPU的抢占被禁止了。因为是单核,进程B就无法被调度出去,只能在“原地旋转”等在锁被A释放。但是进程A无法运行,锁也就无法释放。死锁就发生了。

 多核CPU不会发生上面的情况。因为其他的核会调度其他的进程。

 当进程A获取到自旋锁以后,如果产生了中断,并且在中断里面也要访问共享资源(中断里面可以用自旋锁),此时中断里面无法获取到自旋锁,只能“原地旋转”,产生死锁。为了避免这种情况发生,可以使用spin_lock_irqsave等API来禁止中断并获取自旋锁。

写代码

临界区在哪?

在这里插入图片描述

...
static spinlock_t spinlock;
static int flag = 1;

int misc_open(struct inode *inode,struct file *file)
{
	spin_lock(&spinlock);
	/***********临界区开始***********/
  
	/**********临界区结束***********/
	spin_unlock(&spinlock);

	printk("hello misc_open\n ");
	return 0;
}
...

如上,在加自旋锁和解自旋锁中间的区域就是“临界区域”。

最简单的实现逻辑

static spinlock_t spinlock;
static int flag = 1;

int misc_open(struct inode *inode,struct file *file)
{
	spin_lock(&spinlock);
	if(flag != 1) // 第一次程序进来不会有任何问题
	{
		spin_unlock(&spinlock);
		return -EBUSY;
	}
	flag = 0; // 第一次进来的程序会置该标志位,让后面的程序无法进来。
	spin_unlock(&spinlock);

	printk("hello misc_open\n ");
	return 0;
}

int misc_release(struct inode *inode,struct file *file)
{
	spin_lock(&spinlock);
	if(flag != 1)
	flag = 1;
	spin_unlock(&spinlock);
	
	printk("hello misc_relaease bye bye \n ");
	return 0;
}
  • 如上,这是一个设备节点的驱动,第一次程序A打开了该驱动是可以顺利打开的不会有问题,并且打开后就会把全局变量flag = 0,这样其他程序再想打开该驱动就会失败并返回错误。
  • 程序A用完了该驱动就会将flag = 1,从而后面的程序可以打开该驱动。
  • 逻辑相当简单,就是操作与判断flag的逻辑必须在临界区里面执行。

一种特殊情况:

  • 程序A打开了驱动,代码跑到了flag = 0; 也就是flag还没有完全赋值的时候,来了程序B也要打开驱动,由于自旋锁锁定机制,它只能在临界区外spin_lock(&spinlock); //在这里等待等待。直到程序A跑完临界区,flag也被设置为了1,然后程序B还是无法打开驱动了!目的就达到了。

完整实现

led.c

#include <linux/init.h>         //初始化头文件
#include <linux/module.h>       //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h>   //包含了miscdevice结构的定义及相关的操作函数。
#include <linux/fs.h>           //文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include <linux/uaccess.h>      //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/io.h>           //包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/kernel.h>		//驱动要写入内核,与内核相关的头文件

#include <linux/atomic.h>
#include <asm/atomic.h> 

#define GPIO_DR 0xfdd60000     //LED物理地址,通过查看原理图得知
unsigned int *vir_gpio_dr;     //存放映射完的虚拟地址的首地址

static spinlock_t spinlock;
static int flag = 1;

int misc_open(struct inode *inode,struct file *file)
{
	spin_lock(&spinlock);
	if(flag != 1)
	{
		spin_unlock(&spinlock);
		return -EBUSY;
	}
	flag = 0;
	spin_unlock(&spinlock);

	printk("hello misc_open\n ");
	return 0;
}

int misc_release(struct inode *inode,struct file *file)
{
	spin_lock(&spinlock);
	flag = 1;
	spin_unlock(&spinlock);

	printk("hello misc_relaease bye bye \n ");
	return 0;
}

ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
	printk("misc_read\n ");
	return 0;
}

ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{	
    /*应用程序传入数据到内核空间,然后控制蜂鸣器的逻辑,在此添加*/
	// kbuf保存的是从应用层读取到的数据
    char kbuf[64] = {0};
    // copy_from_user 从应用层传递数据给内核层
	if(copy_from_user(kbuf,ubuf,size)!= 0) 
	{
        // copy_from_user 传递失败打印
		printk("copy_from_user error \n ");
		return -1;
	}
    //打印传递进内核的数据
    //printk("kbuf is %d\n ",kbuf[0]); 
	if(kbuf[0]==1) //传入数据为1 ,LED亮
	{
		*vir_gpio_dr = 0x80008000; 
	}
	else if(kbuf[0]==0) //传入数据为0,LED灭
		*vir_gpio_dr = 0x80000000;
	return 0;
}

//文件操作集
struct file_operations misc_fops={
	.owner = THIS_MODULE,
	.open = misc_open,
	.release = misc_release,
	.read = misc_read,
	.write = misc_write,
};
//miscdevice结构体
struct miscdevice  misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "hello_misc",
	.fops = &misc_fops,
};
static int misc_init(void)
{
	int ret;
    //注册杂项设备
	ret = misc_register(&misc_dev);
	if(ret<0)
	{
		printk("misc registe is error \n");
	}
	printk("misc registe is succeed \n");
    //将物理地址转化为虚拟地址
	vir_gpio_dr = ioremap(GPIO_DR,4);
	if(vir_gpio_dr == NULL)
	{
	printk("GPIO_DR ioremap is error \n");
	return EBUSY;
	}
	printk("GPIO_DR ioremap is ok \n");	
	return 0;
}
static void misc_exit(void){
    //卸载杂项设备
	misc_deregister(&misc_dev);
	iounmap(vir_gpio_dr);
	printk(" misc gooodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m += led.o
KDIR =/home/liefyuan/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
	make -C  $(KDIR) M=$(PWD) modules modules ARCH=arm64 CROSS_COMPILE=/usr/local/arm64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
clean:
	rm -rf modules.order *.o workqueue.o  Module.symvers *.mod.c *.ko

编译模块:

$ export ARCH=arm64
$ export CROSS_COMPILE=aarch64-linux-gnu-
$ make

测试应用:

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
	int fd;
	char buf[64] = {0};//定义buf缓存
	char val[1];
	//打开设备节点
	fd = open("/dev/hello_misc",O_RDWR);
	if(fd < 0)
	{
		//打开设备节点失败
		perror("open error \n"); 
		return fd;
	}
    sleep(10);
	close(fd);
	return 0;
}

app2.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
	int fd;
	char buf[64] = {0};//定义buf缓存
	char val[1];
	//打开设备节点
	fd = open("/dev/hello_misc",O_RDWR);
	if(fd < 0)
	{
		//打开设备节点失败
		perror("open error \n"); 
		return fd;
	}
	//把缓冲区数据写入文件中
	while(1)
	{
		val[0] = 1;
		write(fd, val, sizeof(val));
		sleep(1);
		val[0] = 0;
		write(fd, val, sizeof(val));
		sleep(1);
	}
	close(fd);
	return 0;
}

编译:

aarch64-linux-gnu-gcc app.c -o app.armelf
aarch64-linux-gnu-gcc app2.c -o app2.armelf

测试:

[root@RK356X:/opt]# insmod led.ko
[  787.424679] misc registe is succeed
[  787.425141] GPIO_DR ioremap is ok

[root@RK356X:/opt]# ./app.armelf &

[root@RK356X:/opt]# ./app2.armelf
open error
: Device or resource busy
[root@RK356X:/opt]# ./app2.armelf [  805.822972] hello misc_open
[  805.822972]

[  815.824861] hello misc_relaease bye bye
[  815.824861]
[  818.127234] hello misc_open
[  818.127234]

  • 实现的逻辑:程序app运行起来以后会占用驱动10秒钟,这十秒内app2是无法打开驱动的,直到app释放掉自旋锁以后才可以被app2打开。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python pip更新教程(两种方式)

1. 直接采用命令行模式更新 1.1 搜索框搜索cmd&#xff0c;然后以管理员模式打开 1.2 执行命令 python -m pip install --upgrade pip1.3 查看更新后的版本 pip --version注&#xff1a;如果更新失败&#xff0c;可能是因为网络的问题&#xff0c;则选择第二种更新方法。 2.…

零基础怎么入门python

本文由正厚软件陈老师提供 “编程零基础&#xff0c;可以学习Python吗&#xff1f;” 这是很多初学者经常问的一个问题&#xff0c;我的回答是可以&#xff01;现在很多小学、初中也开始学习编程语言&#xff0c;选择的入门语言就是python。 python其实和学中文没什么区别&…

Windows 10关闭快速启动的方法

在Windows 8及其更高版本的Windows系统中&#xff0c;快速启动功能会被默认开启&#xff0c;开机时间也能大大缩短。但是在某些情况下&#xff0c;快速启动功能的开启会带来部分系统问题&#xff0c;从而无法正常使用电脑。那么我们该如何关闭快速启动呢&#xff1f; 快速启动的…

Redis06:Redis进阶部分

Redis进阶部分Redis配置文件详解Redis持久化持久化之RDB操作rdb优缺点Redis配置文件详解 Redis持久化 Redis是内存数据库&#xff0c;如果不将内存中的数据状态保存到磁盘&#xff0c;那么一旦服务器进程退出&#xff0c;服务器中的数据库状态也会消失&#xff0c;所以Redis提供…

taro3.3.12升级至3.5.8解决支付宝小程序启动基础库2.0构建报错---slot 的祖先节点中未找到 element

项目&#xff1a;taro3 vue3 taro版本&#xff1a;3.3.12 支付宝在上传版本时要求用基础库2.0编译&#xff0c;否则报错&#xff0c;启动2.0编译后&#xff0c; 会有如下错误&#xff1a; 解决方案&#xff1a;社区里面说是要升级taro >3.4.0 步骤1、查看taro版本 – …

bmp位图格式详细介绍-1/4/8/16/24/32bit、存储格式等

目录 一、概述 二、.bmp格式文件详解  2.1 位图文件头  2.2 位图信息头  2.3 调色板  2.4 位图数据 三、位图的其他知识  3.1 压缩的位图 一、概述 bmp是英文Bitmap&#xff08;位图&#xff09;的简写&#xff0c;它是Windows操作系统中的标准图像文件格式&#xff0c;随…

FFplay音频滤镜分析

音频流的 滤镜是通过 configure_audio_filters() 函数来创建的&#xff0c;因为 ffplay 为了代码的通用性&#xff0c;即便命令行参数不使用滤镜&#xff0c;AVFrame 也会过一遍 空滤镜做下样子。 configure_audio_filters() 函数的流程图如下&#xff1a; configure_audio_fi…

HCIA OSI参考模型

一、前言 OSI七层模型是我们耳熟能详的&#xff0c;其实没有太多可以说的地方&#xff0c;我这里就按自己的理解做一下汇总。 二、OSI 七层模型 OSI七层模型是由“国际标准化组织”制定的“参考”模型。 1、物理层 实际上就是对网线、光纤等“连接”介质进行规定&#xff…

初学者必看的3D建模避坑技巧,高效3D制作

近来&#xff0c;随着3D技术的进步被认为任何对 3D 建模主题感兴趣的人打开了机会之门。现在&#xff0c;只要您拥有一台计算机和良好的空间分析技能&#xff0c;您就可以通过时间和持续练习来完善您的 3D 雕刻方式。 也就是说&#xff0c;该领域的许多初学者往往会犯建模错误&…

信号傅里叶变换后频谱刻度设置问题-附Matlab代码

一、概述 时域信号经FFT变换后得到了频谱&#xff0c;在绘制频谱图时还必须设置正确的频率刻度&#xff0c;这样才能从图中得到正确的结果。 二、实例分析 有一余弦信号&#xff0c;信号频率为30Hz&#xff0c;采样频率128Hz&#xff0c;信号长128&#xff0c;原始信号如下图…

【JavaScript+自然语言处理+HTML+CSS】实现Web端的智能聊天问答客服实战(附源码 超详细必看)

需要源码请点赞关注收藏后评论区留言私信~~~ 智能客服的部署方式比较多样化&#xff0c;可以作为组件嵌入到其他应用程序&#xff0c;也可以部署到定制网站&#xff0c;下面分别介绍如何新创建智能客服应用&#xff0c;从而使其能够集成为网站功能的一部分&#xff0c;以及如何…

给你讲明白MySQL的乐观锁和悲观锁

乐观锁与悲观锁是一种广义上的概念。不管是 Java 语言&#xff0c;也或者是其他语言以及数据库都有这类概念对应的实际应用。想要学习乐观锁和悲观锁就要学习他们的基本知识&#xff0c;那么下面我们来学习一下。 锁 生活中&#xff1a;锁在我们身边无处不在&#xff0c;比如我…

PMO项目经理必备的简洁解决问题方案和报告模板

项目经理虽然有责无权&#xff0c;他的权力更多来源于汇报的权力和影响力&#xff0c;作为项目经理和PMO难免经常会进行报告或者是提供方案建议&#xff0c;能够最短时间的讲明白问题和建议才会更多的获得机会&#xff0c;那么如何才能简明扼要的把你的方案和报告说清楚呢&…

基于jsp+mysql+ssm基金信息管理系统-计算机毕业设计

项目介绍 本基金信息管理系统主要包括系统基金信息管理模块、用户管理模块、基金收藏管理、登录模块、和退出模块等多个模块,系统采用了jsp的mvc框架,SSM(springMvcspringMybatis)框架进行开发,本系统是独立的运行&#xff0c;不依附于其他系统&#xff0c;可移植&#xff0c;…

图扑软件数字孪生污水处理厂

随着人工智能、大数据、云计算、物联网和5G等新技术不断融入水务行业的各个环节&#xff0c;智慧水务已逐渐成为传统水务领域转型升级的重要方向。 图扑软件依托自主研发的 HT for Web 产品&#xff0c;并结合视频融合、BIM、5G、物联网、云计算及大数据等先进技术&#xff0c;…

IIS反向代理 设置IIS跨域访问

概念说明 浏览器的同源策略限制了对某些资源的跨域访问&#xff0c;其目的是保障用户数据安全&#xff0c;但同时也阻止了部分合理的跨域请求。为了绕过同源策略的限制&#xff0c;人们提出了多种跨域访问方案。 解决步骤 打开IIS&#xff0c;选中当前站点 -》右侧找到HTTP响…

手写js-防抖,节流

防抖 debounce 函数所做的事情就是&#xff0c;在用户停止某个操作一段时间之后才执行相应的监听函数&#xff0c;而不是在用户操作的过程当中&#xff0c;浏览器触发多少次事件&#xff0c;就执行多少次监听函数。 以最后一次操作为准开始计时器 应用场景: 登录、发短信等按钮…

JAVA SCRIPT设计模式--结构型--设计模式之Bridge桥接模式(7)

JAVA SCRIPT设计模式是本人根据GOF的设计模式写的博客记录。使用JAVA SCRIPT语言来实现主体功能&#xff0c;所以不可能像C&#xff0c;JAVA等面向对象语言一样严谨&#xff0c;大部分程序都附上了JAVA SCRIPT代码&#xff0c;代码只是实现了设计模式的主体功能&#xff0c;不代…

Linux 之七 内核架构、API/ABI 介绍、文件层次结构、Kernel 源码文件

Linux 内核最早是在 1991 年由芬兰大学生林纳斯托瓦兹为自己的个人电脑开发的&#xff0c;并在 GNU 通用公共许可证第 2 版&#xff08;也包含了其他兼容许可证&#xff09;之下发布的一种开源的类 Unix 操作系统宏内核。 注意&#xff0c;我们通常说的 Linux 系统是 Linux Ker…

FineReport数据图表制作教程-密码控件

1. 概述 1.1 版本 报表服务器版本 功能变更 11.0 -- 1.2 应用场景 1.2.1 填报控件 填报报表中可以通过该控件输入密码信息&#xff0c;录入密码&#xff0c;如下图所示&#xff1a; 1.2.2 参数控件 参数面板处可以通过该控件输入密码信息&#xff0c;键入查询参数&#…