信号量、互斥锁、自旋锁、原子操作

linux内核中有多种内核锁,内核锁的作用是:

多核处理器下,会存在多个进程处于内核态的情况,而在内核态下,进程是可以访问所有内核数据的,因此要对共享数据进行保护,即互斥处理;

linux内核锁机制有信号量互斥锁自旋锁还有原子操作

一、信号量(struct semaphore):

是用来解决进程/线程之间的同步和互斥问题的一种通信机制,是用来保证两个或多个关键代码不被并发调用。

信号量(Saphore)由一个值和一个指针组成,指针指向等待该信号量的进程。信号量的值表示相应资源的使用情况。信号量S>=0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个资源,因此S的值减1;当S<0时,表示已经没有可用资源,S的绝对值表示当前等待该资源的进程数。请求者必须等待其他进程释放该类资源,才能继续运行。而执行一个V操作意味着释放一个资源,因此S的值加1;若S<0,表示有某些进程正在等待该资源,因此要唤醒一个等待状态的进程,使之运行下去。

信号量是选择睡眠的方式来对共享工作停止访问的。

也就是说信号量通过PV操作同步解决了进程/线程对临界资源利用的冲突问题;

二、互斥锁:(mutex_lock)

互斥锁同样也是对线程间(不能对进程)同步和互斥的一种另一种机制。

互斥锁更多的是强调对共享资源的锁定作用,当一个线程占用了当前共享资源,使用互斥锁将其lock住之后,其他线程就无法访问,必须等到unlock之后,其他线程才能利用共享资源里面的内容;

 互斥锁是选择睡眠的方式来对共享工作停止访问的。

也就是说互斥锁通过对共享资源的锁定和互斥解决利用资源冲突问题;

 三、自旋锁(spin_lock):

是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

四、原子操作:

Reference:

http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html

http://blog.csdn.net/vividonly/article/details/6599502

http://blog.csdn.net/williamwang2013/article/details/8517380

http://blog.csdn.net/yikai2009/article/details/8650221

4.1、Linux原子概念:

所谓原子操作,就是“不可中断的一个或一系列操作”。

原子操作,就是不能被更高等级中断抢夺优先的操作。你既然提这个问题,我就说深一点。由于操作系统大部分时间处于开中断状态,所以,一个程序在执行的时候可能被优先级更高的线程中断。而有些操作是不能被中断的,不然会出现无法还原的后果,这时候,这些操作就需要原子操作。就是不能被中断的操作。

硬件级的原子操作:在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是“原子操作”,因为中断只发生在指令边缘。在多处理器结构中(Symmetric Multi-Processor)就不同了,由于系统中有多个处理器独立运行,即使能在单条指令中完成的操作也有可能受到干扰。在X86平台生,CPU提供了在指令执行期间对总线加锁的手段。CPU上有一根引线#HLOCK pin连到北桥,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。对于其他平台的CPU,实现各不相同,有的是通过关中断来实现原子操作(sparc),有的通过CMPXCHG系列的指令来实现原子操作(IA64)。本文主要探讨X86平台下原子操作的实现。

4.2、Linux内核两组原子操作接口:

1、原子整数操作

原子操作通常针对int或bit类型的数据,但是Linux并不能直接对int进行原子操作,而只能通过atomic_t的数据结构来进行。

定义于#include<asm/atomic.h>

图1.1      内核中的整数原子操作函数

2、内核中提供的一些主要位原子操作函数。同时内核还提供了一组与上述操作对应的非原子位操作函数,名字前多两下划线。由于不保证原子性,因此速度可能执行更快。

定义于#include<asm/bitops.h>

图1.2      内核中的位原子操作函数

 1 void atomic_set(atomic_t *v,int i);    //设置原子变量v的值为i
 2 atomic_t v = ATOMIC_INIT(0);     //定义原子变量v,并初始化为0;
 3 
 4 atomic_read(atomic_t* v);     //返回原子变量v的值;
 5 
 6 void atomic_add(int i, atomic_t* v);     //原子变量v增加i;
 7 void atomic_sub(int i, atomic_t* v);    
 8 
 9 void atomic_inc(atomic_t* v);     //原子变量增加1;
10 void atomic_dec(atomic_t* v);     
11 
12 int atomic_inc_and_test(atomic_t* v);        //先自增1,然后测试其值是否为0,若为0,则返回true,否则返回false;
13 int atomic_dec_and_test(atomic_t* v);       //先自减1,然后测试其值是否为0,若为0,则返回true,否则返回false 
14 int atomic_sub_and_test(int i, atomic_t* v);     //先减i,然后测试其值是否为0,若为0,则返回true,否则返回false;
15 //注意:只有自加,没有加操作
16 
17 int atomic_add_return(int i, atomic_t* v);   //v的值加i后返回新的值;
18 int atomic_sub_return(int i, atomic_t* v);  
19 int atomic_inc_return(atomic_t* v);     //v的值自增1后返回新的值;
20 int atomic_dec_return(atomic_t* v);    

实例代码:

在scull_open 函数和scull_close函数中:

如果没有进程使用该驱动 ,原子变量值 为 1 ,将原子变量减 一 为 0 ,函数返回 true ,再 !true 为 假 ,if 里面的代码不执行这样打开了、并使用该驱动, 原子变量变为 0;

如果再有进程来打开驱动程序,0-1 = 负1,返回 false ,if 条件成立,运行里面的代码,将原子变量加一恢复到  0,程序返回;

最后, 在应用程序退出时 close 函数, 自增 恢复原子变量值为 1:

 1 static atomic_t scull_available = ATOMIC_INIT(1);      //init atomic
 2 
 3 int scull_open(struct inode *inode, struct file *filp)
 4 {
 5     struct scull_dev *dev;         // device information
 6 
 7     dev = container_of(inode->i_cdev, struct scull_dev, cdev);
 8     filp->private_data = dev;         // for other methods 
 9     if(!atomic_dec_and_test(&scull_available)){
10         atomic_inc(&scull_available);
11         return -EBUSY;
12     }
13     return 0;         // success 
14 }
15 
16 int scull_release(struct inode *inode, struct file *filp)
17 {
18     atomic_inc(&scull_available);
19     return 0;
20 }

以上总结几点:

互斥锁与信号量的区别:

1、信号量一般以同步的方式对共享资源进行控制,而互斥锁通过互斥的方式对共享资源对其进行控制;

2、信号量可以对进程的共享资源进行控制,而互斥锁不行;

3、信号量的值为非负整数,而互斥锁的值只能为0或1;

4、互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到;

自旋锁与互斥锁的区别:

1、因为自旋锁不会引起调用者睡眠,所以效率比较高

2、自旋锁比较适用于锁使用者保持锁时间比较短的情况。

3、自旋锁容易造成死锁,所以需要安全使用它;

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Urahara Blog

Web For Pentester - Directory traversal & File Include Part Tips

2586
来自专栏Java帮帮-微信公众号-技术文章全总结

FreeMarket模板引擎快速入门【面试+工作】

1956
来自专栏Linyb极客之路

Spring中@Async用法总结

引言: 在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来...

2182
来自专栏Golang语言社区

[译]使用 LLDB 调试 Go 程序

我一般调试Go程序都是通过log日志,性能调试的话通过 pprof 、trace、flamegraph等,主要是Go没有一个很好的集成的debugger,前两年...

2613
来自专栏前端杂货铺

node中的Stream-Readable和Writeable解读

在node中,只要涉及到文件IO的场景一般都会涉及到一个类-Stream。Stream是对IO设备的抽象表示,其在JAVA中也有涉及,主要体现在四个类-Inpu...

3779
来自专栏JavaEdge

Java内存模型与volatile关键字Java内存模型(JMM)指令重排序对于Long和double型变量的特殊规则内存屏障有序性(Ordering)先行发生原则

42010
来自专栏菩提树下的杨过

ZooKeeper 笔记(4) 实战应用之【消除单点故障】

关键节点的单点故障(Single Point of Failure)在大型的架构中,往往是致命的。比如:SOA架构中,服务注册中心(Server Registe...

2309
来自专栏Java技术栈

Spring 获取 request 的几种方法及其线程安全性分析

本文将介绍在Spring MVC开发的Web系统中,获取request对象的几种方法,并讨论其线程安全性。

934
来自专栏ml

C/C++ 如何劫持别人家的命令||函数||程序(只能对于window而言)

  要实现下面程序,首先我们需要三个文件 detours.h ,detours.lib ,detver.h(可以去网上下载)   1. 首先让我们看看,一个最简...

3295
来自专栏kl的专栏

skywalking源码分析之javaAgent工具ByteBuddy的应用

关于skywalking请看我上一篇博文,其使用javaAgent技术,使得应用接入监控0耦合。今天在分析skywaking过程中,对javaAgent技术有了...

7048

扫码关注云+社区