spinlock

我们使用ULK所介绍的Linux内核2.6.11版本。
 
在linux内核代码中持有spinlock时为什么不能够睡眠

首先,本质原因是spinlock的设计目的是保证数据修改的原子性,因此没有理由在spinlock锁住的区域内停留。

然后我们来看具体实现上的原因。阅读内核源码之后发现,持有spinlock时为什么不能够睡眠的原因与SMP和内核抢占紧密相关。所以我们分以下四种情况来讨论:

1. 单处理器不可抢占(!CONFIG_SMP &&!CONFIG_PREEMPT):
 
这种配置下,在内核中与spinlock相关的具体实现如下
 
            #define_spin_lock(lock)              \
 
            do { \
 
                          preempt_disable(); \空
 
                          _raw_spin_lock(lock); \空
 
                          __acquire(lock); \空
 
            } while(0)
 
            #definepreempt_disable()                            do { } while (0)
 
            #define_raw_spin_lock(lock)      do { (void)(lock); } while(0)

 
即,实际上此时spin_lock()是空操作。
 
对于spinlock来说,在此种配置下,正常情况:当前进程只能够被中断抢占(如果使用spin_lock_irq()甚至中断都不能够抢占),其他任何进程都不能换入,直到本进程完成临界区的执行。如果持有spinlock时睡眠:则会换入其他进程,如果这个进程正好也需要
这个锁,最好的情况下该进程要等待很长的时间,最坏的情况下系统出现了死锁状态。


2. 单处理器可抢占(!CONFIG_SMP &&CONFIG_PREEMPT):
 
这种配置下,在内核中与spinlock相关的具体实现如下
 
            #define_spin_lock(lock)              \
 
            do { \
 
                          preempt_disable(); \
 
                          _raw_spin_lock(lock); \空
 
                          __acquire(lock); \空
 
            } while(0)
 
            #define preempt_disable() \
 
            do { \
 
                          inc_preempt_count(); \
 
                          barrier(); \
 
            } while (0)

 
实际此时spin_lock()上仅仅做了禁止抢占的操作,而且在禁止抢占之后单独占有CPU,就与第一种单处理器不可抢占的情况完全相同了。

3. 多处理器不可抢占(CONFIG_SMP &&!CONFIG_PREEMPT):
 
这种配置下,在内核中与spinlock相关的具体实现如下
 
            void __lockfunc _spin_lock(spinlock_t *lock)
 
            {
 
                          preempt_disable();
 
                          _raw_spin_lock(lock);
 
            }
 
            #definepreempt_disable()                            do { } while (0)
 
            static inline void _raw_spin_lock(spinlock_t *lock)
 
            {
 
            __asm__ __volatile__(
 
                                          spin_lock_string
 
                                          :"=m" (lock->slock) : : "memory");
 
            }
 
            #define spin_lock_string \
 
                          "\n1:\t" \
 
                          "lock ; decb %0\n\t" \
 
                          "jns 3f\n" \
 
                          "2:\t"\
 
                          "rep;nop\n\t" \
 
                          "cmpb $0,%0\n\t" \
 
                          "jle 2b\n\t"\
 
                          "jmp 1b\n" \
 
                          "3:\n\t"

 
spin_lock()在这种配置下的操作实际上是做了对lock的原子减1,特点是在自旋等待获取lock时不可被抢占。
 
a.对于spinlock来说,正常情况:当前进程在本处理器上只能被中断抢占,在其他处理器上若有进程想要访问该临界区,则由于试图获取被当前进程持有的lock而自旋等待,直到本进程执行完临界区,其他处理器上的等待进程才能进入临界区。如果当前进程在持有spinlock的时候睡眠:则本处理器上换入了其他进程,如果之后换入了一个想要获取同一个自旋锁执行同一段临界区的进程,则会停在自旋检测lock的值处,若多处理器上均换入了这样的进程,而原始的进程始终没得到机会执行并跳出临界区,则此时系统会死锁崩溃。
 
b.对于信号量来说:假设当前进程持有信号量然后睡眠,因为其他试图执行相同临界区的进程在获取信号量的时候不会自旋等待,这些进程会直接把自己放入等待队列然后调度自己,等待原始进程在执行完临界区之后做唤醒操作,所以不会产生死锁。

4.多处理器可抢占(CONFIG_SMP&&CONFIG_PREEMPT):
 
            void __lockfunc _##op##_lock(locktype##_t*lock)                                              \
 
                                                                                                                                                      \
 
                          preempt_disable();                                                                                          \
 
                          for (;;){                                                                                                          \
 
                                          if(likely(_raw_##op##_trylock(lock)))                                  \
 
                                                          break;                                                                                  \
 
                                          preempt_enable();                                                                            \
 
                                          if(!(lock)->break_lock)                                                              \
 
                                                          (lock)->break_lock =1;                                                \
 
                                          while (!op##_can_lock(lock) &&(lock)->break_lock)          \
 
                                                          cpu_relax();                                                                      \
 
                                          preempt_disable();                                                                          \
 
                                                                                                                                                    \
 
                                                                                                                                                      \
 
                                                                                                                                                          \
 
            EXPORT_SYMBOL(_##op##_lock);
 
            #define preempt_disable() \
 
            do { \
 
                          inc_preempt_count(); \
 
                          barrier(); \
 
            } while (0)
 
            static inline int _raw_spin_trylock(spinlock_t *lock)
 
            {
 
                          char oldval;
 
                          __asm__ __volatile__(
 
                                          "xchgb �,%1"
 
                                          :"=q" (oldval), "=m" (lock->slock)
 
                                          :"0" (0) : "memory");
 
                          return oldval > 0;
 
            }

 
spin_lock()在这种配置下的操作实际上是做了preempt_disable()和对lock的原子减1,特点是在自旋等待获取lock时可被抢占。
 
与第3种情况不同的是此种配置下加入了自旋等待时可以被抢占的特性,本来的目的是减少自旋占用的系统时间。这个特性带来了一定改变:发生前述可能会导致死锁的情况时,由于可被抢占,系统仍然能够响应优先级高的进程,假如原始进程优先级很高,则可能解开死锁状态;如果没有解开,则自旋等待获取lock的进程在耗尽时间片后可能会被调入过期队列,然后原始进程可能会获得执行,解开死锁状态。但都只是可能,仍然有死锁的可能性存在。
 
信号量的情况则与第3种相同。

总结:
 
spinlock的具体实现与对称多处理器和内核抢占相关,在SMP和PREEMPT分别开启关闭一共四种的配置情况下,持有spinlock的时候睡眠均有可能产生灾难性的后果。即使碰巧不出现死锁或破坏临界区的情况,在持有spinlock的时候睡眠仍然是对系统资源的严重浪费,会导致系统性能严重下降,这也是持有spinlock的时候不可以睡眠的原因之一。

补充:
CONFIG_PREEMPT_NONE表示内核不可抢占,适用于计算型任务系统,允许很长的延时。
CONFIG_PREEMPT表示内核允许抢占,及优先级高的进程可以抢占优先级低的进程。毫秒级低延时系统。



http://blog.sina.com.cn/s/blog_953826fc01011dtc.html

http://www.ibm.com/developerworks/cn/linux/l-real-time-linux/ (PS:发现IBM服务器上文章都不错啊)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中的spinlock是一种自旋锁机制,用于保护对共享资源的访问,以防止同时访问导致的数据竞争问题。spinlock使用了一种称为自旋的技术,即当一个线程需要获取锁时,它会一直等待,直到锁被释放。这种等待是循环的,即线程会不断地检查锁的状态,直到锁被释放为止。 spinlock相比于传统的互斥量(mutex)和信号量(semaphore)等锁机制,具有更高的性能和灵活性。spinlock不需要使用内核调度器,因此不会产生额外的上下文切换开销。此外,spinlock可以用于任何需要保护的临界区代码,而不仅仅是用于进程之间的同步。 使用spinlock时,需要将其初始化为0,以便其他线程可以安全地访问共享资源。当一个线程需要获取锁时,它可以使用spin_lock函数来锁定spinlock。如果锁已经被其他线程占用,该线程将进入自旋状态,不断检查锁的状态。当该线程获取到锁时,它可以将共享资源置于临界区并执行相关操作。在操作完成后,该线程可以使用spin_unlock函数释放锁。 spinlock机制适用于一些简单的同步场景,例如在并发访问共享资源时保护临界区代码。然而,对于一些复杂的同步需求,可能需要使用更高级的同步机制,如读写锁(rwlock)或条件变量(condition variable)。 总之,spinlock是一种轻量级的自旋锁机制,适用于简单的同步场景,具有较高的性能和灵活性。它适用于任何需要保护的临界区代码,而不仅仅是用于进程之间的同步。在使用spinlock时,需要注意避免死锁和过度自旋等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值