linux内核中的锁

转载 2018年04月17日 13:05:18

1.atomic 原子操作

就是执行起来不能被打断的操作。构成物质的最小颗粒。需要硬件支持。汇编实现。用volatile修饰优化器在用到这个变量时必须每次都小心地从内存重新读取这个变量的值,而不是使用保存在寄存器里的备份

包括各种  atomic_xxx 操作的对象是atomic *v。

通常用来实现引用计数

2.spanlock 自旋锁

如果有人占用了锁就不行地询问所有没有被释放。适合于保有锁很短的进程。不会引起睡眠。另外自旋锁适用于任何(进程、中断)上下文(信号量只适用于进程上下文)。自旋锁无法被抢占(和信号量相比)。只能有一个单元获得锁。

DEFINE_SPINLOCK(x)等同于spinlock_tx = SPIN_LOCK_UNLOCKED

spin_lock  获得自旋锁  spin_trylock不做等待

spin_lock_irqsave 失效本地中断,并保存寄存器的值到flags

spin_lock_bh(lock) 该宏在得到自旋锁的同时失效本地软中断

被保护的共享资源只在进程上下文访问和软中断上下文访问,那么当在进程上下文访问共享资源时,可能被软中断打断,从而可能进入软中断上下文来对被保护的共享资源访问,因此对于这种情况,对共享资源的访问必须使用spin_lock_bh和spin_unlock_bh来保护。当然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它们失效了本地硬中断,失效硬中断隐式地也失效了软中断。

如果被保护的共享资源只在进程上下文和tasklet或timer上下文访问,那么应该使用与上面情况相同的获得和释放锁的宏,因为tasklet和timer是用软中断实现的。

如果被保护的共享资源只在一个tasklet或timer上下文访问,那么不需要任何自旋锁保护,因为同一个tasklet或timer只能在一个CPU上运行

spin_lock用于阻止在不同CPU上的执行单元对共享资源的同时访

3.mutex(互斥锁)

内核互斥锁是在原子API之上实现的,并且互斥锁不能用于中断上下文。但是互斥锁比当前的内核信号量选项更快。

数据结构的字段:

1、atomic_t count; --指示互斥锁的状态:1没有上锁,可以获得;0被锁定,不能获得;

2、spinlock_t wait_lock;--等待获取互斥锁中使用的自旋锁。在获取互斥锁的过程中,操作会在自旋锁的保护中进行。初始化为为锁定。

3、struct list_head wait_list;--等待互斥锁的进程队列。

获取互斥锁。实际上是先给count做自减操作,然后使用自旋锁进入临界区。首先取得count的值,再将count置为-1,判断如果原来count的值为1,也即互斥锁可以获得,则直接获取,跳出。否则进入循环反复测试互斥锁的状态。在循环中,也是先取得互斥锁原来的状态,再将其置为-1,判断如果可以获取(等于1),则退出循环,否则设置当前进程的状态为不可中断状态,解锁自身的自旋锁,进入睡眠状态,待被在调度唤醒时,再获得自身的自旋锁,进入新一次的查询其自身状态(该互斥锁的状态)的循环。

释放被当前进程获取的互斥锁。该函数不能用在中断上下文中,而且不允许去释放一个没有上锁的互斥锁。

4.semaphore 信号量

和互斥锁类似,但是支持多个任务访问共享资源

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。  

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源  

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

5.rw_semaphore

读者在保持读写信号量期间只能对该读写信号量保护的共享资源进行读访问, 写要单独获得写身份。读者数目不受限制

写是排他的,独占的

6.seqlock

写进临界区+1,出临界区+1

只是互斥写写和写先发生的写读。

如此一来奇数时是在进行写操作。读要在偶数时进行。 读时如果当前值和退出值不一样就需要重新读。

可以通过自旋锁实现

seqlock的实现非常简单:

写操作进入临界区时:

voidwrite_seqlock(seqlock_t *sl)

{

spin_lock(&sl->lock); //上写写互斥锁

++sl->sequence;      //sequence++

}

写操作退出临界区时:

voidwrite_sequnlock(seqlock_t *sl)

{

sl->sequence++;        //sequence再++

spin_unlock(&sl->lock);// 释放写写互斥锁

}

 

读操作进入临界区时:

unsignedread_seqbegin(const seqlock_t *sl)

{

unsigned ret;

repeat:

ret=sl->sequence;      // 读sequence

if(unlikely(ret & 1)) { // 如果sequence为奇数自旋等待

gotorepeat;

}

return ret;

}

读操作尝试退出临界区时:

intread_seqretry(const seqlock_t *sl,unsigned start)

{

return(sl->sequence != start); //看看sequence与进入临界区时是否发生过改变

}

而读操作一般会这样进行:

do{

seq =read_seqbegin(&seq_lock);     // 进入临界区

do_something();

}while (read_seqretry(&seq_lock, seq));//尝试退出临界区,存在冲突则重试


7.rcu(read-compy-update)

RCU也是用于能够区分读与写的场合,并且也是读多写少,但是读操作的优先权大于写操作(与seqlock相反)。

RCU也是用于能够区分读与写的场合,并且也是读多写少,但是读操作的优先权大于写操作(与seqlock相反)。

RCU的实现思路是,读操作不需要互斥、不需要阻塞、也不需要原子指令,直接读就行了。而写操作在进行之前需要把被写的对象copy一份,写完之后再更新回去。其实RCU所能保护的并不是任意的临界区,它只能保护由指针指向的对象(而不保护指针本身)。读操作通过这个指针来访问对象(这个对象就是临界区);写操作把对象复制一份,然后更新,最后修改指针使其指向新的对象。由于指针总是一个字长的,对它的读写对于CPU来说总是原子的,所以不用担心更新指针只更新到一半就被读取的情况(指针的值为0x11111111,要更新为0x22222222,不会出现类似0x11112222这样的中间状态)。所以,当读写操作同时发生时,读操作要么读到指针的旧值,引用了更新前的对象、要么读到了指针的新值,引用了更新后的对象。即使同时有多个写操作发生也没关系(是否需要写写互斥跟写操作本身的场景相关)。

为解决旧对象释放的问题,RCU提供了四个函数(另外还有一些它们的变形):

rcu_read_lock(void)、rcu_read_unlock(void)

synchronize_rcu(void)、call_rcu(struct rcu_head *head, void (*func)(structrcu_head*head))。

当读操作要调用rcu_dereference访问对象之前,需要先调用rcu_read_lock;当不再需要访问对象时,调用rcu_read_unlock。

当写操作调用rcu_assign_pointer完成对对象的更新之后,需要调用synchronize_rcu或call_rcu。其中synchronize_rcu会阻塞等待在此之前所有调用了rcu_read_lock的读操作都已经调用rcu_read_unlock,synchronize_rcu返回后写操作一方就可以将被它替换掉的旧对象释放了;而call_rcu则是通过注册回调函数的方式,由回调函数来释放旧对象,写操作一方将不需要阻塞等待。同样,等到在此之前所有调用了rcu_read_lock的读操作都调用rcu_read_unlock之后,回调函数将被调用。

 

如果你足够细心,可能已经注意到了这样一个问题。synchronize_rcu和call_rcu会等待的是“在此之前所有调用了rcu_read_lock的读操作都已经调用了rcu_read_unlock”,然而在rcu_assign_pointer与synchronize_rcu或call_rcu之间,可能也有读操作发生(调用了rcu_read_lock),它们引用到的是写操作rcu_assign_pointer后的新对象。按理说写操作一方想要释放旧对象时,是不需要等待这样的读操作的。但是由于这些读操作发生在synchronize_rcu或call_rcu之前,按照RCU的机制,还真得等它们都rcu_read_unlock。这岂不是多等了一些时日?

8.读写锁

读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
自旋锁的一种扩展

详细内容见:https://blog.csdn.net/ygm_linux/article/details/50358739

Linux的几种内核锁及其作用

mutex(互斥锁): 互斥锁主要用于实现内核中的互斥访问功能。对它的访问必须遵循一些规则:同一时间只能有一个任务持有互斥锁,而且只有这个任务可以对互斥锁进行解锁。互斥锁不能进行递归...
  • rest_in_peace
  • rest_in_peace
  • 2017-09-05 10:09:12
  • 581

linux内核-锁机制

在linux内核中,有很多同步机制。比较经典的有原子操作、spin_lock(忙等待的锁)、mutex(互斥锁)、semaphore(信号量)等。并且它们几乎都有对应的rw_XXX(读写锁),以便在能...
  • yangguangmeng
  • yangguangmeng
  • 2015-12-19 16:15:21
  • 3319

大话Linux内核中锁机制之RCU、大内核锁

大话Linux内核中锁机制之RCU、大内核锁 在上篇博文中笔者分析了关于完成量和互斥量的使用以及一些经典的问题,下面笔者将在本篇博文中重点分析有关RCU机制的相关内容以及介绍目前已被淘汰出内核的大内...
  • xiaohaozi7107
  • xiaohaozi7107
  • 2012-06-17 12:11:20
  • 1122

linux内核锁的几点理解

我们知道,在linux内核中,为了防止竞争的产生,需要加锁。我们最常见的是两种锁,信号量的semphore和自旋锁spin_lock。 semaphore 信号量semaphore这个锁是经常在进...
  • zhaolianxun1987
  • zhaolianxun1987
  • 2017-01-09 23:31:31
  • 882

linux内核自旋锁和中断知识讲解

一、并发与竞态三个要点 1、只要并发的执行单元同时访问共享内存是就会出现竞态 2、解决竞态的唯一途径是保证共享资源的互斥访问,即一个执行单元在访问共享资源时,其他的执行单元被禁止访...
  • wangteng12345678
  • wangteng12345678
  • 2016-08-02 16:42:20
  • 1285

Linux内核中锁机制之内存屏障、读写自旋锁及顺序锁

在上一篇博文中笔者讨论了关于原子操作和自旋锁的相关内容,本篇博文将继续锁机制的讨论,包括内存屏障、读写自旋锁以及顺序锁的相关内容。下面首先讨论内存屏障的相关内容。 三、内存屏障 不知读者是是否记得...
  • chinazhangzhong123
  • chinazhangzhong123
  • 2016-06-26 00:22:14
  • 649

Linux内核中的spin_lock理解

Linux kernel spin_lock
  • sunlei0625
  • sunlei0625
  • 2017-03-01 11:00:05
  • 222

Linux内核自旋锁

自旋锁 自旋锁(spinlock)是用在多个CPU系统中的锁机制,当一个CPU正访问自旋锁保护的临界区时,临界区将被锁上,其他需要访问此临界区的CPU只能忙等待,直到前面的CPU已访问完临界区,将...
  • Tommy_wxie
  • Tommy_wxie
  • 2012-02-22 14:33:41
  • 7646

3.Linux休眠锁

1.Android的休眠机制Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一有效的wake_lock,系统就不能进入深度休眠,但可以进行设备的浅度休眠操作。wake_lock...
  • jun451403404
  • jun451403404
  • 2018-01-02 17:38:29
  • 77

Linux内核中的锁

1. 为什么要保证原子性 处理器分两种:cisc(复杂指令集,可以直接在内存上进行操作,如x86,一条汇编指令可以原子的完整读内存、计算、写内存)和rics(精简指令集,所有操作都必须是在CPU内部...
  • jasonchen_gbd
  • jasonchen_gbd
  • 2018-03-07 00:05:34
  • 86
收藏助手
不良信息举报
您举报文章:linux内核中的锁
举报原因:
原因补充:

(最多只允许输入30个字)