自旋锁是专为防止多处理器并发而引入的一种锁(2.6内核不仅仅为防止多处理器并发,也为防止内核抢占,保护临界资源防止重入问题)。2.6内核实现的系统抢占即时当前进程正文在内核空间,通过调度也可被优先级高的进程中止,排入等待队列。
在多CPU的环境下情况就比较复杂了,因为同时可能有几个程序在运行(是真正的同时),所以必须要定义一个变量当作锁的功能,linux是这样规定的,当这个变量为1时,那么其保护的变量可以被访问,当其值为0时,那么其保护的临界数据不可以被访问,其中,要改变变量锁的值也很有学问,就是不能让几个CPU同时去改,负责就会出现不同步的情况。如spin_lock在多cpu的时候就被
spinlock的作用:spinlock系列函数主要用于保护临界数据(非常重要的数据)不被同时访问(给临界数据加锁),用以达到多任务的同步。如果一个数据当前不可访问,那么就一直等,直到可以访问为止。因此可以说自旋锁是一种忙等锁。
spinlock只能用于内核编程和驱动编程。在2.6以前的内核是不可抢占的,也就是说,当运行于内核状态下时,是不容许切换到其他进程的。而在2.6以后的内核中,编译内核的时候多了一个选项,可以配置内核是否可以被抢占,这也就是为什么在2.6的内核中多了一个preempt.h的原因。
spinlock函数根据机器的配置分为两套,单CPU和多CPU,先来看看单CPU的情况。
在单CPU的情况下,spin_lock和spin_unlock函数都被定义成空操作(do { } while(0)),这是因为我们上面说的,内核不可以被抢占的原因。所以,在单CPU的情况下,只要你能够保证你要保护的临界数据不会在中断中用到的话,那么你的数据已经是受保护的了,不需要做任何操作。
linux最初是依据分时操作系统设计实现的,不能完全适用于实时操作系统。然而在内核HACK的努力下在2.6内核中完美实现内核抢占,系统的抢占性是保证实现实时调度算法的一个重要因素,所以这两个函数就不再这么简单了,因为内核也有可能被其他程序中断,所以要保护数据,还要让调度程序暂时不调度此段程序,也就是说,暂时禁止抢占式任务调度功能,所以在上面两个函数中分别多了一个。
通过判断我们要互斥的数据会被这四个因素中的哪几个来存取,就可以决定具体使用哪种形式的spinlock。如果只要和其他CPU互斥,就要用spin_lock/spin_unlock,如果要和irq及其他CPU互斥,就要用spin_lock_irq/spin_unlock_irq,如果既要和irq及其他CPU互斥,又要保存EFLAG的状态,就要用spin_lock_irqsave/spin_unlock_irqrestore,如果要和bh及其他CPU互斥,就要用spin_lock_bh/spin_unlock_bh,如果不需要和其他CPU互斥,只要和irq互斥,则用local_irq_disable/local_irq_enable,如果不需要和其他CPU互斥,只要和bh互斥,则用local_bh_disable/local_bh_enable,等等。值得指出的是,对同一个数据的互斥,在不同的内核执行路径中,所用的形式有可能不同。