全部博文(10)
分类: LINUX
2014-07-08 09:31:48
内核的几种锁同步机制
2014/6/10 冯健
1. atomic原子变量
typedef struct {
int counter;
} atomic_t;
可见,原子变量就是一个赤裸裸的整型,所以原子类型的变量只适用于对整形数据的原子操作。对它的读写都是采用了特殊的指令。
atomic_add(i, v)
atomic_dec(v)
atomic_inc(v)
atomic_sub(i, v)
atomic_set(v,i)
atomic_read(v)等等一系列的原子变量操作函数的实现,严重依赖于特殊指令。
在armv5之前,特殊指令时swp,armv6以后就是ldrex strex两个指令。简单的说,ldrex strex在加载、修改、写入内存变量counter内容时会锁住内存,或者说给内存加独占标记。这样就,可以保证了不存在多个CPU同时访问同一个原子变量。但是仅仅这样不能保证避免被同一个CPU上的中断打断啊?
引入ldrex strex后,只要在ldrex和strex之间对应的原子变量被修改,则strex失败。那么多核环境下,保证了多核间的互斥,同样的,如果单核上,atomic_add执行中产生了中断,影响了对应的v->counter,则atomic_add这次的原子加操作失败了,便重新来过。这样保证了操作的原子性。至于如何判断中断是否影响了对应的v->counter,有的是只要产生总线操作就视为影响,有的则在硬件上处理更为细致。
也就是,如果被中断了,那么这次原子变量操作会失败,可以通过返回值判断!!
而在armv5之前的单核arm架构中,上述原子操作函数是通过raw_local_irq_save来实现的,这样仅仅关闭了本地的中断,因为对原子变量操作的打断只会来自于中断。虽然还可能来自内核抢占,但是,研究之后知道,内核抢占只会发生在中断程序返回内核空间,以及内核线程主动放弃执行的情况下。关闭了本地中断,也就断了内核抢占的来源!!
当然了,现在的情况是,几乎只需要考虑ldrex strex了,arm已经普遍使用到了armv6版本以上。
2. spin_lock自旋锁
自旋锁是一个大而全的锁同步方案!为什么这么说?因为任何想用到锁同步的地方都可以使用它,中断代码中、普通内核代码中都可以,只是开销的问题而已,spin_lock原则是要快而迅速的完成!且临界区内最好不要使用可能引起睡眠的函数,试想,你获取了锁却去睡觉了,若有个人想得到锁,他永远也没办法得到!使用原则可以参见《linux设备驱动归纳总结(四):5.SMP下的竞态和并发》
在单CPU非抢占内核中,spin_lock_irqsave是空操作。
在单CPU抢占内核中,spin_lock_irqsave是preempt_disable关内核抢占、以及关中断操作。
在多CPU非抢占内核中,spin_lock_irqsave是关本地中断、以及通过atomic操作实现CPU间互锁。
在单CPU抢占内核中,spin_lock_irqsave是关本地中断、preempt_disable关内核抢占、以及通过atomic操作实现CPU间互锁。
我看了源码,在多CPU架构的spin_lock的实现中,我看见了atomic操作的身影,不知道spin_lock_irqsave是不是真的是以atomic为基础来实现的?
3. 信号量/互斥量
struct semaphore {
atomic_t count;
int sleepers;
wait_queue_head_t wait;
};
struct semaphore sem;
sema_init(&sem, count);
#define init_MUTEX(sem) sema_init(sem, 1);
可以发现,信号量居然也是使用原子变量作为基本类型,只不过加了一个睡觉进程队列头。信号量显然用于可以睡眠的环境下,那就是说信号量使用的主体应该是进程上下文(系统调用)以及内核线程。中断上下文,显然绝对不可以使用(其实也可以,只要不引起睡眠)!
另外,一般使用的是二元的信号量,也就是互斥量。
4. RCU read-copy-update锁
可以参见深入linux内核架构5.2章节。一种类似于读写锁的机制。
5. 内存屏障
参见 《内存屏障 》