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

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

相关文章

来自专栏飞雪无情的博客

Go语言实战笔记(十六)| Go 并发示例-Pool

这篇文章演示使用有缓冲的通道实现一个资源池,这个资源池可以管理在任意多个goroutine之间共享的资源,比如网络连接、数据库连接等,我们在数据库操作的时候,比...

852
来自专栏我是攻城师

如何优雅的全量读取Elasticsearch索引里面的数据

1.3K7
来自专栏JAVA高级架构

Java 内存模型 JMM 浅析

JMM简介 Java Memory Model简称JMM, 是一系列的Java虚拟机平台对开发者提供的多线程环境下的内存可见性、是否可以重排序等问题的无关具体平...

3369
来自专栏Golang语言社区

Go语言基于共享变量的并发

一个特定类型的方法和操作函数是并发安全的,那么所有它的访问方法和操作都是并发安全的。导出包级别的函数一般情况下都是并发安全的,package级的变量没法被限制在...

3224
来自专栏开发之途

重拾Java(4)-线程

1314
来自专栏大内老A

WCF技术剖析之三十一: WCF事务编程[下篇]

在WCF事务编程模型下,通过服务契约确定事务流转的策略(参阅《上篇》),通过事务绑定实施事务的流转(参阅《中篇》)。但是,对于事务绑定接收到并成功创建的事务来说...

1818
来自专栏iKcamp

走进Node.js 之 HTTP实现分析

作者:正龙(沪江Web前端开发工程师) 本文为原创文章,转载请注明作者及出处 上文“走进Node.js启动过程”中我们算是成功入门了。既然Node.js的强...

2716
来自专栏Python绿色通道

Python的线程

Python的标准库提供了两个模块: thread 和threading,thread 是低级模块,threading是高级模块,对thread 进行了封装。绝...

873
来自专栏Phoenix的Android之旅

关于volatile的坑

Java的面试基础问题中,经常出现并发相关的问题。比如volatile关键字,是出现频率相当高的一个问题。 如果说volatile和synchronized的区...

593
来自专栏java一日一条

【Java并发编程】使用wait/notify/notifyAll实现线程间通信的几点重要说明

在 Java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信。在线程中调用 wait(...

623

扫码关注云+社区