中断与锁
中断是Linux响应异步事件的一种方式,区别于陷阱、异常指令和出错等在指令执行完后产生的异常,它往往发生在处理器的外部,通常由外设触发,可以在指令执行完成的瞬间产生,也可能在指令执行的过程中产生,因而不可预测。从外设的角度看,中断是设备请求CPU响应的方式;而从调度的视角来看,中断是驱动系统心跳和调度的手段。而为了实现同步互斥机制,内核还基于处理器提供的原子指令实现了锁的机制,在进入临界段时需要上锁,而在退出时需要解锁,这样就保证了其他处理器不会也进入临界区。通过关本地中断,保重了当前运行的处理器不会再去执行其他内核线程,而是沿着当前上下文继续执行下去;而在临界段之前上锁,则防止其他处理器也执行即将要被执行的代码。那么在什么时候需要上锁,什么时候要关中断,什么时候二者都需要呢?下文结合Linux内核已经提供的几组相关函数进行了分析。
三组相关函数
Spin_lock 与spin_unlock
Spin_lock() 与 spin_unlock(),顾名思义,是内核提供的分别上锁和解锁的函数,值得一提的是这组函数在单处理器系统中,都退化成空操作,因为单处理器系统上只有一个处理器,所以任何当前正在执行的临界段,不可能被其他处理器再次进入。
Spin_lock_irq与spin_unlock_irq
与上面的spin_lock和spin_unlock这组函数的不同,spin_lock_irq()在执行的过程中,会先关本地中断,然后上锁;spin_unlock_irq()相反,会先解锁,然后打开本地中断。注意这里两个函数是互逆对称的操作过程。读者可以思考一下,spin_lock_irq()先上锁后关中断是否可以?答案是不可以,否则对应的spin_unlock_irq()执行的顺序就是开中断先然后解锁,在解锁和开中断的瞬间,如果有同一中断再被当前CPU响应,它就会再次调用spin_lock_irq(),里面先执行spin_lock(),可是这时该锁已经被它上次申请,并且没有来得及释放,这样它就会译制在这里死等。虽然它还可以响应其他类型的中端请求,但是该CPU无法再正确处理当前的中断类型。用户将会看到对应的设备好像停止了工作。
Spin_lock_irqsave 与spin_unlock_irqrestore
Spin_lock_irqsave()与spin_unlock_irqsrestore()除了分别包含spin_lock_irq()和spin_unlock_irq()所执行的所有操作之外,还会分别保存中断使能与否的状态、恢复中断使能的状态。因为有的中断可以以嵌套的方式执行,当前的中端可能正是嵌套进入的,这种情况下在unlockspin 锁后,内核不能贸然使能本地中断,而应恢复之前的中断使能状态,否则相当于在嵌套中断返回之前提前执行了本地中断使能的工作。这组函数具体的伪码如下:
Spin_lock_irqrestore()
{
…………
Save_local_irq_state(&flag);
Local_irq_disable()
Spin_lock();
…………
}
Spin_unlock_irqrestore()
{
Spin_unlock()
Restore_local_irq_state(flag);
Enable_local_irq()
}
使用场景对比
通过上面描述的对比可以看到,三组函数既有共同的地方,那就是上锁和解锁,也有不同的地方,那就是关于中断是否关闭、是否保存。所实现功能的不同,也就决定了三组函数应用场景的不同,下表给出了对比和说明:
总结
内核提供了看似差不多实则有不小差别的中断、自旋锁相关的函数,我们需要根据不同的应用场景和需求谨慎选择,否则很容易出现意想不到的问题。