l临界区:
我们知道,临界区是指某个代码区间,在该区间中需要访问某些共享的数据对象,又或者是总线,硬件寄存器等,通常这段代码区间的范围要控制在尽可能小的范围内。临界区内需要对这些数据对象和硬件对象的访问进行保护,保证在退出临界区前不会被临界区外的代码对这些对象进行修改。
出现以下几种情形时,我们需要使用临界区进行保护:(
在linux中,上面所说的三种情况都可以用自旋锁(spin_lock)解决。)
(1) 在可以抢占(preemption)的系统中,两个线程同时访问同一个对象;
(2) 线程和中断同时访问同一个对象;
(3) 在多核系统中(SMP),可能两个CPU可能同时访问同一个对象;
针对单处理器系统,对第一种情况,只要临时关闭系统抢占即可,
preempt_disable(); ……(访问共享资源的代码);preempt_enable();
针对单处理器系统,第二种情况,只要临时关闭中断即可,
local_irq_disable();……(访问共享资源的代码);local_irq_enable();
但是,针对多处理器的系统,这两种情况下的这两种方法不可行:
对于第一种情况,虽然抢占被禁止了,可是另一个CPU上还有线程在运行,如果这个线程也正好要访问该共享对象,上面的代码段显然是无能为力了。
对于第二种情况,虽然本地CPU的中断被禁止了,可是另一个CPU依然可以产生中断,如果他的中断服务程序也正好要访问该共享对象,上面的代码段也一样无法对共享对象进行保护。
所以,解决的办法浮现出来:“实际上,在linux中,上面所说的三种情况都可以用自旋锁(spin_lock)解决“。
基本的自旋锁函数对是:
spin_lock(spinlock_t *lock);
spin_unlock(spinlock_t *lock);
对于单处理器系统,在不打开调试选项时,spinlock_t实际上是一个空结构,把上面两个函数展开后,实际上就只是调用preempt_disable()和preempt_enable(),对于单处理器系统,关掉抢占后,其它线程不能被调度运行,所以并不需要做额外的工作,除非中断的到来,不过内核提供了另外的变种函数来处理中断的问题。
对于多处理器系统,spinlock_t实际上等效于内存单元中的一个整数,内核保证spin_lock系列函数对该整数进行原子操作,除了调用preempt_disable()和preempt_enable()防止线程被抢占外,还必须对spinlock_t上锁,这样,如果另一个CPU的代码要使用该临界区对象,就必须进行自旋等待。
对于
中断和
普通线程都要访问的对象,内核提供了另外两套变种函数:
spin_lock_irq(spinlock_t *lock);
spin_unlock_irq(spinlock_t *lock);
和:
spin_lock_irqsave(lock, flags);
spin_lock_irqrestore(lock, flags);
我们可以按以下原则使用上面的三对变种函数(宏):
如果只是在
普通线程之间
同时访问共享对象,使用
spin_lock()/spin_unlock();
如果是在
中断和普通线程之间
同时访问共享对象,并且确信退出临界区后要
打开中断,使用
spin_lock_irq()/spin_unlock_irq();
如果是在
中断和普通线程之间
同时访问共享对象,并且退出临界区后要
保持中断的状态,使用
spin_lock_irqsave()/spin_unlock_irqrestore();
在2.6.33之后的版本,内核加入了raw_spin_lock系列,使用方法和spin_lock系列一模一样,只是参数有spinlock_t变为了raw_spinlock_t。
在内核的主线版本中,spin_lock系列只是简单地调用了raw_spin_lock系列的函数,但内核的代码却是有的地方使用spin_lock,有的地方使用raw_spin_lock。在Real-Time Linux Kernel的模型被提出并实现后,产生了一个Real-Time preemption的patch。该模型允许在临界区中被抢占,而且申请临界区的操作可以导致进程休眠等待,这将导致自旋锁的机制被修改,由原来的整数原子操作变更为信号量操作。使用自旋锁的代码中,真正不允许抢占和休眠的地方,将spin_lock更改为raw_spin_lock。”
因为原来的内核中已经有raw_spin_lock这一名字空间,用于代表体系相关的原子操作的实现,于是linus本人建议:1、
把原来的raw_spin_lock改为arch_spin_lock;2、
把原来的spin_lock改为raw_spin_lock;3、
实现一个新的spin_lock。
“
绝对不允许被抢占和休眠的地方,使用raw_spin_lock,否则使用spin_lock;
如果你的临界区足够小,使用raw_spin_lock。
如原作声明,转载自http://blog.csdn.net/droidphone