linux 内核的spinlock

spinlock设计成了三层,第一层是接口,第二层raw实现层,第三层是CPU平台层。在第二层raw实现层提供了两个分支,分别是单CPU和多CPU(核)。第三层是不同CPU的锁操作实现。raw层除了调用锁操作来保护临界资源外,还提供不同调度保护,如关闭中断和抢占。

为不同保护要求提供了4个版本的锁操作。

基本操作: spin_lock,spin_unlock

中断保护: spin_lock_irq,spin_lock_irqsave和spin_unlock_irq,spin_unlock_irqsave

软中断保护: spin_lock_hb,spin_unlock_hb

对于单CPU的内核,spinlock的基本锁操作并不做任何时,或者说是一个空函数。

对于多核多处理器(SMP)环境的内核,spinlock实现为一种票据轮号的算法来进行排队竞争锁。这种算法并不使用队列数据结构。而是大家不停地对同共享变量进行查询比较。这种算法使一切操作的指令数最少,并且只要使用原子的逻辑运算指令就可以实现FIFO。算法将一个字长内整数分为队列的head和tail。使得修改这个队列的操作可以保证在原子指令进行。不同等待队列,队列并不维护除head和tail的中间元素,锁的释放者并需要迭代队列的元素,只需要将head增加步长(INC单位值),由其它CPU自己去比较发现是否轮到自己。每个上锁的CPU都必须对队列的tail增加步长,成功后在私有栈中保留tail的值,作为自己的排位号。当自己保留的tail值与当前队列的head一致时,表示自己的轮号到了。这个获取锁的CPU在释放锁时,只要原子地修改队列的head增加步长,无需要唤醒和调度。atomic_xchg相当于copy-update,对于update的一方必须同时提交copy(副本),以确保提交的update对应的被修改copy不是过时的,在多方同时对同一份copy进行修改时,只有一方可以被接受,接受修改后,旧copy就可以视为过时,对于的update也就不能被接受。需要锁的CPU必须保证只修改tail而head不变,而释放锁的CPU必须保证只修改head而tail不变,确保修改的原子性。队列的head和tail压缩为一个字长内的整数,可以利用一条原子指令运算来实现。

用于SMP的spinlock,内核提供了两套实现,内联和非内联的。因为spinlock是内核同步临界资源的最基本同步原语,如果使用内联的话,内核的体量会很大。

虽然说内联和非内联,但是实现上也各自执行了不一样的行动。

在内联版本,自旋spin的行为采用arch_spin_lock。arch_spin_lock实现为FIFO队列等待方式。而非内联版本,spin的行为并不使用arch_spin_lock,而是另外实现了一个使用arch_spin_trylock的try-wait-loop的spin循环。在这里的wait并不使用sleep,而是在一个嵌套循环内查询spinlock是否被释放。因为没有使用arch_spin_lock进行自旋,所以并没有使用FIFO队列进行等待。因此上锁的顺序并非按FIFO。

下面是基本锁函数__raw_spin_lock的实现区别比较:

__raw_spin_lock内联版本:

__raw_spin_lock非内联版本:

在spin过程对中断保护程度也不同。

内联版本中,在CPU进行spin之前就开始关闭中断和抢占,直到spinlock释放后才打开中断和抢占。

而非内联版本在spin过程中,只在每次尝试上锁的一瞬是进行中断保护的,其余空等时间是可以中断和抢占的。

下面是中断保护锁函数__raw_spin_lock_irq的实现区别比较:

下面是中断保护锁函数__raw_spin_lock_irqsave的实现区别比较:

软中断版本的区别就比较大。

内联版本一如前面的实现,在进入spin之前进行关闭软中断,但不关闭CPU中断。

非内联版本在spin过程中,使用的是CPU中断保护和抢占保护,在每次尝试上锁的一瞬。在上锁后才进行关闭软中断。

下面是软中断保护锁函数__raw_spin_lock_bh的实现区别比较:

linux-3.19 arch-x86

原文地址:https://www.cnblogs.com/bbqzsl/p/6728886.html