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

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 条评论
登录 后参与评论

相关文章

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

03.线程安全/同步/线程通讯

03.线程安全/同步/线程通讯 一.一个典型的Java线程安全例子 ? ? 上面例子很容易理解,有一张银行卡,里面有1000的余额,程序模拟你和你老婆同时在取款...

4107
来自专栏海纳周报

synchronized关键字的语义

上一篇文章,我们讲到,如果发生了多个线程共同访问一个全局变量的时候,就会发生各种意料之外的情况。其实现实生活中有很多这样的例子。我举一个例子。 一群人都要过河,...

3407
来自专栏轮子工厂

Java多线程学习

提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

852
来自专栏小二的折腾日记

多线程

进程:是一个正在执行中的程序。 每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。 线程:就是进程中的一个独立的控制单元。 线程在控制...

281
来自专栏流柯技术学院

Python多线程学习

1、  函数式:调用thread模块中的start_new_thread()函数来产生新线程。如下例:

541
来自专栏AILearning

多线程的基础学习

进程:是一个正在执行中的程序, 每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。 线程:是进程中的一个独立的控制单元, 线程在控制中进...

1717
来自专栏林德熙的博客

win10 uwp 初始屏幕

对于本地 127.0.0.1 就是一个内部IP,之外,还有10.0.0.0/24 ,172.16.0.0/16 , 192.168.0.0/16 , 169.2...

282
来自专栏林德熙的博客

win10 uwp 判断本地ip

对于本地 127.0.0.1 就是一个内部IP,之外,还有10.0.0.0/24 ,172.16.0.0/16 , 192.168.0.0/16 , 169.2...

371
来自专栏程序员宝库

10 分钟理解 JS 引擎的执行机制

作者: ziwei3749 原文:https://segmentfault.com/a/1190000012806637 首先,请牢记2点: JS是单线程语言 ...

3378
来自专栏向治洪

volatile和synchronized的区别和联系

volatile 它所修饰的变量不保留拷贝,直接访问主内存中的。    在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄...

1838

扫描关注云+社区