Chinaunix首页 | 论坛 | 博客
  • 博客访问: 933157
  • 博文数量: 63
  • 博客积分: 568
  • 博客等级: 中士
  • 技术积分: 3435
  • 用 户 组: 普通用户
  • 注册时间: 2012-10-05 11:44
文章分类
文章存档

2016年(4)

2015年(6)

2014年(3)

2013年(27)

2012年(23)

分类: LINUX

2013-11-02 23:06:33

自旋锁的实现

 

内核中的自旋锁的作用是保护一段临界区域的操作是独占的。可以这么来理解这个作用。对于多核的系统,主要考虑当一个cpu进入到了临界代码区域之后,其它cpu不能再次进入这个临界代码区中。对于但核的cpu,主要的情景是一个进程进入了临界区域后,不能被其它进程抢占,导致其它抢占进入的cpu会再次进入这个临界代码区域中。

 

自旋锁的实现主要可以从两个方面来分析,一个方面是单核系统上面自旋锁的实现,另外一个方面是多核系统上面自旋锁的实现。下面会从这两个方面分别进行分析。

 

单核系统自旋锁的实现

对于单核系统,不存在多个cpu竞争的情况。内核中一段代码(进程上下文)进入临界区域后能够打断这个这个进程执行临界区域代码的只有两种情况。一种是中断,也就是中断中也存在访问临界区域的操作,一种情况就是系统开启了抢占,中断推出后,原来执行的进程被抢占,换入进程进入了临界区域。对于中断也会进入临界区域的这种情况,本质上自旋锁是不能保护这样的情景的。遇到这样的情况,进入临界区域前,系统直接关掉中断就得了,不能使用自旋锁。对于第二种情况,自旋锁在单核系统上的实现实际上就是关闭抢占,使得进入临界区域的进程能被中断打断,但是不能被抢占,也就避免了其它进程同时进入到临界区中。

 

下面是spin_lock在单核系统上的实现,等同于禁止抢占操作

#define spin_lock(lock)            _spin_lock(lock) 
 
#define _spin_lock(lock)            __LOCK(lock) 
 
#define __LOCK(lock) \ 
  do { preempt_disable(); __acquire(lock); (void)(lock); } while (0
 
# define __acquire(x) (void)0 

 

对于多核系统,自旋锁的作用是阻止多个cpu同时进入临界区代码。如果一个cpu已经进入了临界代码区域,这个时候,另外一个cpu试图来获取临界区的锁,这个cpu会获取锁不成功并且一直在轮询锁的状态一直到另外一个cpu释放掉这个锁。这个过程保护了临界代码的单入性。但是副作用是造成另外一个cpu的空转,降低了效率。

下面是spin_lock在多核系统的实现,最主要的是调用__raw_spin_lock函数。下面就这个函数做详细的分析。

ldrex %0, [%1]

&(lock->lock)地址的值载入到tmp寄存器中

 

teq %0, #0

测试tmp寄存器中的值是不是等于0,如果等于0就执行下一条语句,如果不等于0,说明锁被占用,又重新跳转到1b位置重新开始判断

 

strexeq %0, %2, [%1]

1存入到&(lock->lock)地址中,%0保存了执行的结果

bne 1b

如果执行结果为0,则执行成功,退出函数,如果执行不成功则重新到1b位置重新上面的过程。

void __lockfunc _spin_lock(spinlock_t *lock) 

    /*首先禁止抢占,多核cpu也要避免被本cpu中其它进程 
      抢占的情况发生*/ 
    preempt_disable(); 
    spin_acquire(&lock->dep_map, 00, _RET_IP_); 
    /*根据下面的定义最终调用到了_raw_spin_lock*/ 
    LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock); 

 
#define LOCK_CONTENDED(_lock, try, lock) \ 
    lock(_lock) 
 
# define _raw_spin_lock(lock)        __raw_spin_lock(&(lock)->raw_lock) 
 
static inline void __raw_spin_lock(raw_spinlock_t *lock) 

    unsigned long tmp; 
 
    __asm__ __volatile__( 
        "1:    ldrex    %0, [%1]\n" 
        "    teq    %0, #0\n" 
#ifdef CONFIG_CPU_32v6K 
        "    wfene\n" 
#endif 
        "    strexeq    %0, %2, [%1]\n" 
        "    teqeq    %0, #0\n" 
        "    bne    1b" 
        : "=&r" (tmp) 
        : "r" (&lock->lock), "r" (1
        : "cc"); 
 
    smp_mb(); 

 

对于arm处理器中的ldrexstrex指令可以通过arm官方首次介绍了解更多的细节。概述的说着两个指令联合起来使用可以判断,cpuload内存到寄存器,到反存数值到内存的这个期间有没有其它cpu对这个地址的内存进行了读写操作,如果存在这样的情况,写入操作strex操作会不成功,并通过相应的寄存器报告写入操作失败。

自旋锁的变体介绍

前面的分析过程中,我们已经提到了一种情况就是如果进程中使用了加锁操作,同时硬件中断中也使用了加锁操作。就有可能出现硬中断中死锁的情况。为了避免这种情况,内核衍生出来两个中断安全的加锁接口

#define spin_lock_irq(lock) _spin_lock_irq(lock)

# define spin_unlock_irq(lock) _spin_unlock_irq(lock)

这两个接口的原理实际上是在获取锁之前先关闭掉硬中断,然后再获取锁。这样进程在临界区域中不会被硬中断打断。

 

同时还有类似的情况就是,如果软件中断中有代码有加锁的代码,同样会有可能导致死锁的情况,主要原因是硬件中断处理完成后,会接着处理软件中断。这个时候内核提供了相关的接口。

#define spin_lock_bh(lock) _spin_lock_bh(lock)

#define spin_unlock_bh(lock) _spin_unlock_bh(lock)

阅读(5130) | 评论(0) | 转发(3) |
给主人留下些什么吧!~~