1. 自旋锁
自旋锁最初是为了在多处理系统(SMP)设计,实现在多处理器情况下保护临界区。
自旋锁的实现是为了保护一段短小的临界区代码,保证这个临界区的操作是原子的。在Linux内核中,自旋锁通常用于包含内核数据结构的操作(如wait_queue等),用于保证操作内核中的数据结构的原子性。如果内核控制路径发现自旋锁可用,则“锁定”被设置,而代码继续进入临界区。相反,如果内核控制路径发现锁运行在另一个CPU的内核控制路径“锁定”,就原地“旋转”并重复检查这个锁,直到这个锁可用为止。
2. 自旋锁的特性
(1) 被自旋锁保护的临界区代码执行时不能进入休眠。
当内核代码请求到一个自旋锁并且在它的临界区里进行相应的操作,在某个时刻,内核代码可能因为调用了一个函数使该进程休眠或者内核抢占使一个更高优先级的进程获取到CPU资源,使得原来的进程睡眠等原因失去了处理器。此时,正好有一个线程想获取同一个锁,如果这个线程运行在和内核代码不同的处理器上,那么它可能自旋等待一段时间,因为占用自旋锁的进程处于休眠状态,并不能释放锁。当进程从休眠状态唤醒或者重新得到处理器并释放锁,线程就可以获取锁。但是,如果想获取锁的线程正好和内核代码运行在同一个处理器,这是线程一直占有CPU资源进行自旋,等待释放锁,但是内核代码处于休眠,等待CPU资源,因此造成死锁。
(2) 被自旋锁保护的临界区代码执行时是不能被被其他中断中断。
当驱动程序正在运行时,并且已经获取自旋锁,这个锁控制着对设备的访问。此时设备产生了一个中断,导致中断例程被调用,而中断处理例程也要获取该锁。当中断处理例程和驱动程序代码在同一个处理器上运行时,中断处理例程一直占有CPU自旋等待释放锁,而驱动程序代码等待CPU资源去释放锁,会导致死锁。
(3) 被自旋锁保护的临界区代码执行时,内核不能被抢占。
一个在内核态执行的路径有可能被切换出处理器,比如当前进程正在内核态执行某一系统调用时,发生了一个外部中断,当中断处理函数返回时,因为内核的可抢占性,此时将会出现一个调度点。如果处理器的运行队列中出现了一个比当前被中断进程优先级更高的进程,那么被中断的进程即使此时它正运行在内核态,将会被换出处理器。在单处理器上的这种因为内核的可抢占性所导致的两个不同进程并发执行的情形,非常类似于SMP系统上运行在不同处理器上的进程之间的并发。为了保护共享资源不会受到破坏,必须在进入临界区前,内核的可抢占性也要关闭掉。
被自旋锁保护的临界区代码执行时,它不能因为任何原因放弃处理器。
3. 自旋锁相关API
spin_lock_init(lock) //初始化自旋锁,将自旋锁设置为1,表示有一个资源可用。
spin_is_locked(lock) //如果自旋锁被置为1(未锁),返回0,否则返回1。
spin_unlock_wait(lock) //等待直到自旋锁解锁(为1),返回0;否则返回1。
spin_trylock(lock) //尝试锁上自旋锁(置0),如果原来锁的值为1,返回1,否则返回0。
spin_lock(lock) //循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。
spin_unlock(lock) //将自旋锁解锁(置为1)。
spin_lock_irqsave(lock, flags) //循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关中断,将状态寄存器值存入flags。
spin_unlock_irqrestore(lock, flags) //将自旋锁解锁(置为1)。开中断,将状态寄存器值从flags存入状态寄存器。
spin_lock_irq(lock) //循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关中断。
spin_unlock_irq(lock) //将自旋锁解锁(置为1)。开中断。
spin_unlock_bh(lock) //将自旋锁解锁(置为1)。开启底半部的执行。
spin_lock_bh(lock) //循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。阻止软中断的底半部的执行。