接前一篇文章:Linux内核与驱动面试经典"小"问题集锦(2)
问题4
问:既然spin_lock可以在进程上下文和中断上下文中使用,那么一旦进入中断,被自旋住,那么CPU岂不是被死锁住了?
备注:这个问题是笔者当年参加比特大陆面试的时候被问到的。当时他们先是问了自旋锁和信号量,我答上来了。正在心中窃喜之际,面试官突然追问了这个问题。由于此前遇到过的此类面试题都是只问到自旋锁与信号量的区别就可以了,并没有如此深入,因此当时就懵住了。说明对于自旋锁的掌握还是不够透彻。
答:
自旋锁主要针对SMP或单CPU但内核可抢占的情况;对于单CPU且内核不支持抢占的系统,自旋锁退化为空操作 。在单CPU且内核可抢占的系统中,自旋锁持有期间,内核的抢占将被禁止 。由于内核可抢占的单CPU系统的行为实际上类似于SMP系统,因此,在这样的单CPU系统中使用自旋锁仍十分必要。另外,在多核SMP的系统中,任何一个核拿到了自旋锁,该核上的抢占调度也暂时禁止了,但是没有禁止另外一个核的抢占调度。
在多核编程的时候,如果进程和中断访问同一片临界资源,则一般需要在进程上下文中调用spin_lock_irqsave()/spin_unlock_irqrestore(),在中断上下文中调用spin_lock()/spin_unlock()。这样,在CPU0上,无论是进程上下文还是中断上下文获得了自旋锁,此后,如果CPU1上不论是进程上下文还是中断上下文,想要获得同一自旋锁,都必须忙等待,这避免了一切核间并发的可能性。
额外:
由此引申出一个进阶问题。
问:在使用自旋锁的时候有哪些注意事项?
答:
驱动工程师应谨慎使用自旋锁,在使用过程中要特别注意如下几个问题:
(1)自旋锁实际上是忙等待锁,当锁不可用时,CPU一直循环执行"测试并设置"该锁,直到其可用而取得该锁。CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。因此,只有在占用锁的时间极短的情况下,使用自旋锁才是合理的。当临界区很大,或者有共享设备的时候,需要较长时间占用锁,使用自旋锁会降低系统的性能。
(2)自旋锁可能导致系统死锁 。引发这个问题最常见的情况是递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的CPU想第二次获得该锁,则此CPU将死锁。也就是说,自旋锁不可递归。
(3)在自旋锁锁定期间不能调用可能引起进程调度的函数。如果进程获得自旋锁之后再阻塞(如调用copy_from_user、kmalloc、msleep等函数),则可能导致内核的崩溃,引发内核panic。
(4)在单核情况下编程的时候,也应该认为自己是多核的。比如,在单CPU的情况下,若中断和进程访问同一临界区,进程里调用spin_lock_irqsave()是安全的,在中断中不调用spin_lock()也没有问题。因为spin_lock_irqsave()可以保证这个CPU的中断服务程序不可能执行。但是,如果CPU是多核的,那么spin_lock_irqsave()不能屏蔽另外一个核的中断,则另外那个核就可能造成并发问题。因此不管怎样,在中断服务程序中也应该调用spin_lock()。
参考资料:
《Linux设备驱动开发详解 ------ 基于最新的Linux 4.0内核》 宋宝华 编著,机械工业出版社