天天看点

Linux内核中自旋锁同步分析

<Linux内核设计与实现>6-7-8章关于自旋锁同步中提出

”中断处理下半部的操作中使用自旋锁尤其需要小心:下半部处理和进程上下文共享数据时,由于下半部的处理可以抢占进程上下文的代码,

  所以进程上下文在对共享数据加锁前要禁止下半部的执行,解锁时再允许下半部的执行“

  以下为此处提出的注意点的个人理解,水平有限希望路过的大神能留下宝贵的点评 谢谢~

  1.首先看下in_interrupt()的定义:

#define in_interrupt()        (irq_count())

   #define irq_count()    (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \

                  | NMI_MASK))      

  2.再看下preempt_count()的定义:

  #define preempt_count()    (current_thread_info()->preempt_count)

  preempt_count分4个域:NMI-HARDIRQ-SOFTIRQ-PREEMPT 整个字段跟抢占相关.当preempt_count!=0禁止抢断,

  否则允许抢断

  3.上文提到自旋锁,来看下自旋锁的操作

spin_lock(spinlock_t* lock)

   {

     raw_spin_lock(&lock->rlock);

   }

   raw_spin_lock(raw_spinlock_t* lock)

   {

     //抢占相关,

     preempt_disable();

     //x86上是锁总线操作

     do_raw_spin_lock(lock);

   }

   preempt_disable()->inc_preempt_count()->add_preempt_count(1)->

   #define add_preempt_count(val) \

   do{ \ 

   preempt_count() += (val); \

   } while (0);      

 执行preempt_count() += (val); 后,spin_lock()使得current_thread_info()->preempt_count字段中的PREEMPT域不为零,进一步说,它禁止进程抢断。

结合上面的分析,来看下共享数据时可能发生抢占的情景:

  1)进程与中断共享数据:中断没有task结构,不接受进程调度,用mutex等互斥睡去后,再也醒不来,因此只能选择自旋锁;

  由于进程随时会被中断打断,自旋锁不能保护共享数据,因此进程在进入临界区前用spin_lock_irq屏蔽中断来替换spin_lock。

  2)进程与tasklet共享数据:tasklet有时是在do_irq中运行的(虽然有时候由ksoftirqd运行),还未完全退出到中断恢复之后,所以

  也是一睡就醒不过来;tasklet被执行的条件是if(!in_interrupt()&&local_softirq_pending()){...}。

  如果进程被中断打断,执行到do_softirq中,进程还是被打断的状态,虽然上了互斥锁(即preempt_count的PREEMPT+1),但是按上文分析in_interrupt()

  只使用NMI-HARDIRQ-SOFTIRQ这3个字段,因此顺利通过if判断,开始执行软中断,使得保护共享数据的原意失败。如果,进程为了保护共享数据,得调用local_bh_disable();

  增加preempt_count:SOFTIRQ域的值,使!in_interrupt()失败;