关于spin_lock的问题
作者:Physh 发布于:2014-12-12 14:10 分类:Linux内核分析
spin_lock的分析文章Google一下有很多,这里只是分享一些关于spin_lock思考过的问题。
一、UP 下spin_lock的实现
UP的情况下,spin_lock本身并没有实现锁机制,相对应的spin_lock()只是禁用了内核抢占而已。如下代码:
//@include/linux/spinlock_api_up.h #define _raw_spin_lock(lock) __LOCK(lock) /* * In the UP-nondebug case there's no real locking going on, so the * only thing we have to do is to keep the preempt counts and irq * flags straight, to suppress compiler warnings of unused lock * variables, and to add the proper checker annotations: */ #define __LOCK(lock) \ do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)
#define preempt_disable() \ do { \ preempt_count_inc(); \ barrier(); \ } while (0)
由以上代码可知,UP下spin_lock仅仅是禁用了内核抢占,而此时IRQ是可以正常触发的。那就先考虑tick中断——即,UP下,持有spin_lock的进程会被调度器切走么?
1)UP下,spin_lock() 是不会失败的, 但spin_lock() 保护的临界代码还是需要一定时间执行的,这期间内核抢占被禁止,那当前进程有可能被切走么?
先假设CFS(先假设调度器用的是CFS)在禁止内核抢占的情况下不会将当前进程切走,那么,仅仅禁用内核抢占可以保证临界区代码不会被进程上下文重入;如果,CFS并不理会抢占计数,会强制剥夺当前进程的CPU使用权,那么就存在spin_lock()保护的临界区代码被进程上下文重入的问题。
2)再考虑,spin_lock() 临界区被中断上下文重入的问题。
以上两点,都不会造成死锁或者CPU Halt, 因为UP下spin_lock()没有进行锁等待,所以存在的就只是代码重入问题,也即,即便用spin_lock()进行保护,这段代码还是有被重入的可能。
那问题已经提出来了,就看看代码如何实现?先看CFS的周期调度代码,如下代码确认当前进程是否需要重新调度:
static void check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr) { .... ideal_runtime = sched_slice(cfs_rq, curr); /* * 计算该进程的预期运行时间: * *当cfs_rq中的运行进程不大于sched_nr_latency(8)时,各个进程的ideal_runtime是一个常量 *之所以是常量,是因为period是常量(sysctl_sched_latency=6000000)。 *当cfs_rq中的运行进程大于sched_nr_latency时,period = nr_running * sysctl_sched_min_granularity; *其中,sysctl_sched_min_granularity是750000. *ideal_runtime *= period * weight/load; */ if (delta_exec > ideal_runtime) { //delta_exec 实际运行的时间已经超过其预计运行时间, //调用resched_task将该进程设置为TIF_NEED_RESCHED,即需要重新调度的; resched_task(rq_of(cfs_rq)->curr); //clear buddy cache?? //个人的理解是buddy的缓冲信息可能会影响CFS,使其优先选择有buddy缓冲 //的进程,更高效的利用缓冲。 clear_buddies(cfs_rq, curr); return; } //如果运行时间小于sysctl_sched_min_granularity(最小执行时间) //则直接返回,让当前继续执行。 if (delta_exec < sysctl_sched_min_granularity) return; se = __pick_first_entity(cfs_rq); delta = curr->vruntime - se->vruntime; //如果最左边的se并不处于饥渴等待状态。 //可能当前进程的没得到足够执行时间,或者当前进程的优先级比最左边se更高。 //这种情况下,直接返回,让当前进程接着跑。 if (delta < 0) return; //如果最左边的se的等待时间已经大于curr的ideal_runtime,表明处于CPU饥渴 //状态,则将当前task设置成TIF_NEED_RESCHED,触发调度; if (delta > ideal_runtime) resched_task(rq_of(cfs_rq)->curr); }
由此可见,这里也只是设置了RESCHED FLAG,真正的调度工作还是交给了中断返回时的do_work_pending(),而我们知道内核抢占被禁用的情况下,当前进程是不会被切走的,即不会重新调度,所以第一个问题就澄清了,即进程上下文重入的问题是不会发生的。
接着,中断上下文是否会发生重入?考虑这样的情况:进程A陷入内核,获取spin_lock,正在执行临界区代码,被IRQ打断,且ISR中要获取同一个spin_lock,在UP下,ISR不会由于锁等待而halt住,中断返回后,进程A继续执行,就像什么都没发生过一样,但是这样就没有起到防止重入的作用,临界变量可能已经改变,而进程A却不知道。而这种情况,仔细想想除了禁用本地中断之外似乎没有什么办法可以避免了。
所以,在UP情况下,如果临界区有可能被ISR访问的话,那么就应该是用 spin_lock_irq() 而不是仅仅用 spin_lock() 了事。
二、SMP下 spin_lock 的实现
spin_lock 在SMP下实现肯定要比UP下复杂得多,看代码:
//@include/linux/spinlock_api_smp.h static inline void __raw_spin_lock(raw_spinlock_t *lock) { preempt_disable(); spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); }
preempt_disable()不再解释,spin_acquire() 是sparse检查需要,用来检查死锁的,LOCK_CONTENDED是一个宏定义,先调用do_raw_spin_trylock()尝试获得锁,不等待,如果失败在调用do_raw_spin_lock() 忙等待unlock. do_row_spin_trylock()的代码不分析了,和do_raw_spin_lock()一样,只是不循环等待,直接看do_raw_spin_lock()代码:
static inline void arch_spin_lock(arch_spinlock_t *lock) { unsigned long tmp; __asm__ __volatile__( "1: ldrex %0, [%1]\n" " teq %0, #0\n" WFE("ne") " strexeq %0, %2, [%1]\n" " teqeq %0, #0\n" " bne 1b" : "=&r" (tmp) : "r" (&lock->lock), "r" (1) : "cc"); smp_mb(); }这段内嵌汇编首先检查lock->lock,如果等于0,就表明现在是unlock状态,就把lock->lock置位1,表示lock状态。这段汇编里边,关键的三个指令是:ldrex, strexeq, WFE, 前两个指令实现独占访问储存器,保证"读取-修改-写入”在芯片级是原子的,而WFE是wait for event, 和WFI类似,只是他可以被SEV指令唤醒,在spin_unlock()的时候,会发出SEV。
具体可以参见蜗蜗的文章:http://www.wowotech.net/armv8a_arch/wfe_wfi.html
了解SMP 平台下的spin_lock之后,还是那两个问题,进程上下文重入和中断上下文重入。第一个问题已经澄清,那第二个问题呢?重现之前的情景:进程A陷入内核,获取spin_lock,正在执行临界区代码,被本地IRQ打断,且ISR中要获取同一个spin_lock, 在SMP下,ISR就要调用WFE进入low-power-mode,这样持有锁的内核路径也得不到运行,所以无法释放锁资源,ISR也就只能一直WFE,本地CPU就这样挂掉了。当然,当IRQ在其他CPU上的时候,这种情况是不会发生的,另一个CPU为WFE直到本地CPU释放锁资源。
所以,这样又回到之前的解决办法,如果ISR有可能重入临界区,那么就应该使用 spin_lock_irq() 而非 spin_lock()。如果不这样使用,在UP下,临界数据将错乱,而在SMP下,CPU将死锁。
(完)
标签: spin_lock

评论:
功能
最新评论
- wangjing
写得太好了 - wangjing
写得太好了! - DRAM
圖面都沒辦法顯示出來好像掛點了。 - Simbr
bus至少是不是还有个subsystem? - troy
@testtest:只要ldrex-modify-strex... - gh
Linux 内核在 sparse 内存模型基础上实现了vme...
文章分类
随机文章
文章存档
- 2025年4月(5)
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2014-12-15 16:00
感谢分享!