一、概括
(1)自旋锁适用于SMP系统,UP系统用spinlock是作死。
(2)保护模式下禁止内核抢占的方法:1、执行终端服务例程时2、执行软中断和tasklet时3、设置本地CPU计数器preempt_count
(3)自旋锁的忙等待的实际意义是:尝试获取自旋锁的另一个进程不断尝试获取被占用的自旋锁,中间只pause一下!
(4)在抢占式内核的spin_lock宏中,第一次关抢占,目的是防止死锁(防止一个已经获取自旋锁而未释放的进程被抢占!!)。而后又开抢占,目的是让已经释放自旋锁的进程可以被调度出去,让其他进程可以进入临界区。当然,开启内核抢占后,调度器调度的进程是不是在盲等的进程不可而知!
二、具有内核抢占的spin_lock宏
让我们来详细讨论用于请求自旋锁的spin_lock宏。下面的描述都是针对支持SMP系统的抢占式内核的。该宏获取自旋锁的地址sip作为它的参数,并执行下面的操作:
调用preempt_disable()以禁用内核抢占
调用函数__raw_spin_trylock(),它对自旋锁的slock字段执行原子性的测试和设置操作。该操作首先执行等价于下列汇编片段的一些指令:
movb$0,%a1
xchgb%al,slp->slock
汇编语言指令xchg原子性地交换8位寄存器%al和slp->slock指示的内存单元的内容。随后,如果存放在自旋锁中的旧值是正数,函数就返回1,否则返回0。
如果自旋锁中的旧值是正数,宏结束;内核控制路径已经获得自旋锁。
否则,内核控制路径无法获得自旋锁,因此宏必须执行循环一直到在其它CPU上运行的内核控制路径释放自旋锁。调用preempt_enable()递减在第1步递增了的抢占计数器。如果在执行spin_lock宏之前内核抢占被启用,那么其它进程此时可以取代等待自旋锁的进程。
如果break_lock字段等于0,则把它设置为1。通过检测该字段,拥有锁并在其它CPU上运行的进程可以知道是否有其它进程在等待这个锁。如果进程把持某个自旋锁的时间太长,它可以提前释放锁以便等待相同自旋锁的进程能够继续向前运行。
执行等待循环:
while(spin_is_locked(sip)&& slp->break_lock)
cpu_relax();
宏cpu_relax()简化为一条pause汇编语言指令。在Pentium4模型中引入了这条指令以优化自旋锁循环的执行。通过引入一个很短的延迟,加快了紧跟在锁后面的代码的执行并减少了能源消耗。pause与早先的80x86微处理器模型是向后兼容的,因为它对应rep;nop指令,也就是对应空操作。
跳转回第1步,再次试图获取自旋锁
三、非抢占式内核中的spin_lock宏
如果在内核编译时没有选择内核抢占选项,spin_lock宏就与前面描述的spin_lock宏有很大的区别。在这种情况下,宏生成一个汇编语言程序片段,它本质上等价于下面紧凑的忙等待:
1:lock;decbslp->lock
jns3f
2:pause
cmpb$0,slp->slock
jle2b
jmp1b
3;
汇编语言指令decb递减自旋锁的值,该指令是原子的,因为它带有lock字节前缀。随后检测符号标志,如果它被清0,说明自旋锁被设置为1,因此从标记3处继续正常执行。否则,在标签2处执行紧凑循环直到自旋锁出现正值。然后从标签1处开始重新执行,因为不检查其它的处理器是否抢占了锁就继续执行是不安全的。
spin_unlock宏
spin_unlock宏释放以前获得的自旋锁,它本质上执行下列汇编语言指令;
movb$1,slp->slock
并在随后调用preempt_enable(),注意,因为现在的80x86微处理器总是原子地执行内存中的只写访问,所以不使用lock字节。