linux同步互斥,自旋锁、互斥锁区别

一句话理解同步与互斥:我等你用完厕所,我再用厕所。什么叫同步?就是条件不允许,我要等等。什么是互斥?你我早起都要用厕所,谁先抢到谁先用,中途不被打扰。

同步与互斥经常放在一起讲,是因为它们之间的关系很大,“互斥”操作可以用“同步”来实现。我“等”你用完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?伪代码如下:

void 抢厕所(void)

{

   if(有人在用)

   {

      我眯一会;

   }

   用厕所;

   喂,醒醒,有人用厕所吗;

}

假设A、B两人早起抢厕所,A先行一步占用了;B慢了一步,于是就眯一会;当A用完后叫醒B,B也就愉快的上厕所了。

这个过程中,A、B是互斥地访问“厕所”,“厕所”被称之为临界资源。我们使用了“休眠-唤醒”的同步机制实现了“临界资源”的“互斥访问”。(摘自韦东山)

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

加锁(locking)是一种广泛应用的同步技术。当内核控制路径必须访问共享数据结构或进入临界区时,就需要为自己获取一把“锁”。由锁机制保护的资源非常类似于限制于房间内的资源,当某人进入房间时,就把门锁上。如果内核控制路径希望访问资源,就试图获取钥匙“打开门”。当且仅当资源空闲时,它才能成功。然后,只要它还想使用这个资源,门就依然锁着。当内核控制路径释放了锁时,门就打开,另一个内核控制路径就可以进入房间。

下图显示了锁的使用。5个内核控制路径(P0,PI,P2,P3和P4)试图访问两个临界区(C1和C2)。内核控制路径P0正在C1中,而P2和P4正等待进人C1。同时,P1正在C2中,而P3正在等待进入C2。注意P0和P1可以并行运行。临界区C3的锁现在打开着,因为没有内核控制路径需要进人C3。

 

 

Linux锁的应用之一在多处理器环境中,取名叫自旋锁(spin lock)。如果内核控制路径发现自旋锁“开着”,就获取锁并继续自己的执行。相反,如果内核控制路径发现锁由运行在另一个CPU上的内核控制路径“锁着”,就在周围“旋转”,反复执行一条紧凑的循环指令,直到锁被释放。

自旋锁的循环指令表示“忙等”。即使等待的内核控制路径无事可做(除了浪费时间),它也在CPU上保持运行。不过,自旋锁通常非常方便,因为很多内核资源只锁1毫秒的时间片段;所以说,等待自旋锁的释放不会消耗太多CPU的时间。

一般来说,由自旋锁所保护的每个临界区都是禁止内核抢占的。在单处理器系统上,这种锁本身并不起锁的作用,自旋锁技术仅仅是用来禁止或启用内核抢占。请注意,在自旋锁忙等期间,因为并没有进入临界区,所以内核抢占还是有效的,因此,等待自旋锁释放的进程有可能被更高优先级的所取代。这种设计是合理的,因为不能因为占用CPU太久而使系统死锁。

 

在Linux中,每个自旋锁都用spinlock_t结构表示:

 

 

typedef struct

{

 raw_spinlock_t raw_lock;

 #if defined(CONFIG_PREEMPT) && defined(CONFIG_SMP)

 unsigned int break_lock;

 #endif

 #ifdef CONFIG_DEBUG_SPINLOCK

 unsigned int magic, owner_cpu;

 void *owner;

 #endif

 #ifdef CONFIG_DEBUG_LOCK_ALLOC

 struct lockdep_map dep_map;

 #endif

 } spinlock_t;

 

 typedef struct

{

 volatile unsigned int slock;

} raw_spinlock_t;

 

其中包含两个重要的字段意义如下:

slock:该字段表示自旋锁的状态:值为1表示“未加锁”状态,而任何负数和0都表示“加锁”状态。
break_lock:表示进程正在忙等自旋锁(只在内核支持SMP和内核抢占的情况下使用该标志)。

内核提供六个宏用于初始化、测试及设置自旋锁。所有这些宏都是基于原子操作的,这样可以保证即使有多个运行在不同CPU上的进程试图同时修改自旋锁,自旋锁也能够被正确地更新。

 

1、spin_lock_init —— 初始化自旋锁,并把自旋锁的lock->raw_lock置为1(未锁)

 

# define spin_lock_init(lock) /

do { /

 static struct lock_class_key __key; /

 __spin_lock_init((lock), #lock, &__key); /

 } while (0)

 

void __spin_lock_init(spinlock_t *lock, const char *name,struct lock_class_key *key)

{

#ifdef CONFIG_DEBUG_LOCK_ALLOC

/*

* Make sure we are not reinitializing a held lock:

*/

debug_check_no_locks_freed((void *)lock, sizeof(*lock));

lockdep_init_map(&lock->dep_map, name, key, 0);

#endif

lock->raw_lock = (raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;

lock->magic = SPINLOCK_MAGIC;

lock->owner = SPINLOCK_OWNER_INIT;

lock->owner_cpu = -1;

}

 

#define __RAW_SPIN_LOCK_UNLOCKED { 1 }

#define SPINLOCK_MAGIC 0xdead4ead

#define SPINLOCK_OWNER_INIT ((void *)-1L)


2、spin_unlock —— 把自旋锁置为1(未锁)

 

 

#if defined(CONFIG_DEBUG_SPINLOCK) || defined(CONFIG_PREEMPT) || /

!defined(CONFIG_SMP)

# define spin_unlock(lock) _spin_unlock(lock)

#else //我们还是重点关注后面的吧

# define spin_unlock(lock) __raw_spin_unlock(&(lock)->raw_lock)

#endif

 

void __lockfunc _spin_unlock(spinlock_t *lock)

{

spin_release(&lock->dep_map, 1, _RET_IP_);

_raw_spin_unlock(lock);

preempt_enable();

}

 

# define _raw_spin_unlock(lock) __raw_spin_unlock(&(lock)->raw_lock)

 

static inline void __raw_spin_unlock(raw_spinlock_t *lock)

{

__asm__ __volatile__(

__raw_spin_unlock_string

);

}

 

#define __raw_spin_unlock_string /

"movb $1,%0" /

:"+m" (lock->slock) : : "memory"


spin_unlock宏释放以前获得的自旋锁,上面的代码本质上执行下列汇编语言指令:
movb $1, slp->slock
并在随后调用preempt_enable()(如果不支持内核抢占,preempt_enable()什么都做)。注意,因为现在的80x86微处理器总是原子地执行内存中的只写访问,所以不用lock字节。

3、spin_unlock_wait —— 等待,直到自旋锁变为1(未锁)

 

 

#define spin_unlock_wait(lock) __raw_spin_unlock_wait(&(lock)->raw_lock)

#define __raw_spin_unlock_wait(lock) /

do { while (__raw_spin_is_locked(lock)) cpu_relax(); } while (0)

#define __raw_spin_is_locked(x) /

(*(volatile signed char *)(&(x)->slock) <= 0) //如果大于0则为真,表示未锁,则跳出while循环

#define cpu_relax() rep_nop() //在循环中执行一条空指令:

static inline void rep_nop(void)

{

__asm__ __volatile__("rep;nop": : :"memory");

}

 

4、spin_is_locked( ) —— 如果自旋锁被置为1(未锁),返回0;否则,返回1
 

 

#define spin_is_locked(lock) __raw_spin_is_locked(&(lock)->raw_lock)

#define __raw_spin_is_locked(x) /

(*(volatile signed char *)(&(x)->slock) <= 0)


5、spin_trylock( ) —— 把自旋锁置为0(锁上),如果原来锁的值是1,则返回1;否则,返回0

 

#define spin_trylock(lock) __cond_lock(_spin_trylock(lock))

int __lockfunc _spin_trylock(spinlock_t *lock)

{

preempt_disable();

if (_raw_spin_trylock(lock)) {

spin_acquire(&lock->dep_map, 0, 1, _RET_IP_);

return 1;

}

 

preempt_enable();

return 0;

}

 

6、spin_lock —— 加锁:循环,直到自旋锁变为1(未锁),然后,把自旋锁置为0(锁上)

 spin_lock是最重要的一个宏。首先,我们看到在include/spinlock.h头文件里,有:

 

 

#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)

# include <linux/spinlock_api_smp.h> //多处理器情况

#else

# include <linux/spinlock_api_up.h> //单处理器情况

#endif

 

 

 

#define spin_lock(lock) _spin_lock(lock)

#ifdef __LINUX_SPINLOCK_API_UP_H

#define _spin_lock(lock) __LOCK(lock) //单处理器情况

#else


注意,该代码上边有一句#if !defined(CONFIG_PREEMPT) || !defined(CONFIG_SMP) || /
    defined(CONFIG_DEBUG_LOCK_ALLOC)
别去管它,因为上边的英文注释写得很清楚了,这句代码的意思是即使没有定义内核抢占或SMP,或者是自旋锁调试,只要lockdep激活了,也就是我们在刚才spinlock_t定义的那里看到的#ifdef CONFIG_DEBUG_LOCK_ALLOC,都会假设在整个锁的调试期间保持关中断。这句#if就是这个意思,千万别理解成没有定义内核抢占、SMP或自旋锁调试了,切记切记。
/*
 * If lockdep is enabled then we use the non-preemption spin-ops
 * even on CONFIG_PREEMPT, because lockdep assumes that interrupts are
 * not re-enabled during lock-acquire (which the preempt-spin-ops do):
 */
//多处理器情况,并且允许内核抢占: 
void __lockfunc _spin_lock(spinlock_t *lock)
{
    //禁止抢占
    preempt_disable();
    //这个函数在没有定义自旋锁调试的时候是空函数,我们不去管它
    spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
    //相当于_raw_spin_lock(lock)
    LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
}
     
在没有定义自旋锁调试的时候,LOCK_CONTENDED宏定义如下
#define LOCK_CONTENDED(_lock, try, lock) /
    lock(_lock)

我们看到其实就是调用_raw_spin_lock宏(位于include/linux/spinlock.h):
# define _raw_spin_lock(lock)        __raw_spin_lock(&(lock)->raw_lock)

于是,定位到include/asm-i386/Spinlock.h
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
    asm(__raw_spin_lock_string : "+m" (lock->slock) : : "memory");
}

展开:
#define __raw_spin_lock_string /
    "/n1:/t" /
    //原子减,如果不为负,跳转到3f,3f后面没有任何指令,即为退出
    LOCK_PREFIX " ; decb %0/n/t" /    
    "jns 3f/n" /
    "2:/t" /
    //重复执行nop,nop是x86的小延迟函数,执行空操作
    "rep;nop/n/t" /
    //比较0与lock->slock的值,如果lock->slock不大于0,跳转到标号2,即继续重复执行nop
    "cmpb $0,%0/n/t" /
    "jle 2b/n/t" /
    //如果lock->slock大于0,跳转到标号1,重新判断锁的slock成员
    "jmp 1b/n" /
    "3:/n/t"

在上面的函数中,大家可能对"jmp 1b/n“比较难以理解。在我们一般的观念里,获得一个锁,将其值减1;释放锁时将其值加1;实际上在自旋锁的实现中lock->slock只有两个可能值,一个是0. 一个是1。释放锁的时候并不是将lock->slock加1,而是将其赋为1。(转载于BruceZhang

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值