并发与竞争(三)自旋锁
文章目录
- 自旋锁的概念
- 什么是自旋锁?
- 自旋锁的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 |
自旋锁的使用步骤
- 在访问临界资源的时候先申请自旋锁
- 获取到自旋锁以后就进入临界区,获取不到自旋锁就“原地等待”
- 退出临界区的时候要释放自旋锁
其他自旋锁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) | 打开下半部,获取自旋锁 |
自旋锁的注意事项
- 由于自旋锁会“原地等待”,因为“原地等待”会继续占用CPU,会消耗CPU资源。所以锁的时间不能太长,也就是临界区的代码不能太多。
- 在自旋锁保护的临界区里面不能调用可能会导致线程休眠的函数,否则可能会发生死锁。
- 自旋锁一般是用在多核的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打开。
并发与竞争(三)自旋锁相关推荐
- 读书笔记 — Java高并发程序设计 — 第三章 — 锁
2019独角兽企业重金招聘Python工程师标准>>> 1. 重入锁 重入锁可以完全替代synchronized关键字.使用java.util.concurrent.locks.Re ...
- 并发编程 Java 三把锁(Synchronized、ReentrantLock、ReadWriteLock)
Synchronized synchronized 的 3 种用法: 指定加锁对象(代码块):对给定对象加锁,进入同步代码前要获得给定对象的锁. void resource1() {synchroni ...
- [内核同步]自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析
关于进程上下文,中断上下文,请看这篇文章 Linux进程上下文和中断上下文内核空间和用户空间 自旋锁的初衷:在短期间内进行轻量级的锁定.一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋 ...
- 读写自旋锁,第1部分(来自IBM)
读写自旋锁简介 什么是读写自旋锁 由于互斥的特点,使用自旋锁的代码毫无线程并发性可言,多处理器系统的性能受到限制.通过观察线程在临界区的访问行为,我们发现有些线程只是简单地读取信息,并不修改任何东西, ...
- 读写自旋锁详解:TODO
林 昊翔 2011 年 7 月 21 日发布 Table of Contents 读写自旋锁简介 什么是读写自旋锁 读写自旋锁的属性 以自动机的观点看读写自旋锁 读写自旋锁的实现细节 读写自旋锁的接口 ...
- linux内核的自旋锁
前言 感谢宋老师. 自旋锁(Spin Lock)是一种典型的对临界资源进行互斥访问的手段,其名称来源于它的工作方式.为了获得一个自旋锁,在某CPU上运行的代码需先执行一个原子操作,该操作测试并设置(T ...
- Linux并发与竞争介绍(原子操作、自旋锁、信号量、互斥体)
目录 并发与竞争 并发与竞争简介 保护内容是什么 原子操作 原子操作简介 原子整形操作API函数(atomic_t 结构体) 原子位操作API 函数 自旋锁 自旋锁简介 自旋锁API函数 线程与线程 ...
- linux 信号量 自旋锁 测试 实验,「正点原子Linux连载」第四十八章Linux并发与竞争实验...
1)实验平台:正点原子Linux开发板 2)摘自<正点原子I.MX6U嵌入式Linux驱动开发指南> 关注官方微信号公众号,获取更多资料:正点原子 第四十八章Linux并发与竞争实验 在上 ...
- java让线程空转_Java锁:悲观/乐观/阻塞/自旋/公平锁/闭锁,锁消除CAS及synchronized的三种锁级别...
JAVA LOCK 大全 [TOC] 一.广义分类:乐观锁/悲观锁 1.1 乐观锁的实现CAS (Compare and Swap) 乐观锁适合低并发的情况,在高并发的情况下由于自旋,性能甚至可能悲观 ...
最新文章
- 硬核!一文梳理经典图网络模型
- window wamp中配置安装xhprof步骤(windows)
- android uber启动动画,模仿Uber的启动画面(上)
- PB代码动态解析执行器
- Android -- 自定义View小Demo,绘制四位数随机码(一)
- 4 5区别 angular 和_初探Angular的更新机制
- 在SQL Server中创建用户角色及授权(使用SQL语句)
- 单元测试Error creating bean with name org.springframework.web.servlet.resource.Resource
- 09年最值得期待7大IT收购:思科收购VMware
- .net core linux 界面,C# dotnet core + AvaloniaUI 开发桌面软件,hello world
- 信息系统项目管理师快速记忆口诀
- 苹果系统简易音乐播放器
- GB/T 20984-2022《信息安全技术 信息安全风险评估方法》解读
- Go语言中的complex(复数)
- MyBatis:万能Map和模糊查询(狂神)
- C++的封装、继承和多态
- 林深时见鹿,海蓝时见鲸
- ZB雕刻用鼠标和数位板的差别大不大?怎么买手绘板?ZB雕刻手绘是不是很重要?
- android 给图片蒙上蒙层_Android 新手引导蒙层效果实现代码示例
- Layui父页面和子页面直接互相传值