preempt_enable()
本文记录了对preempt_enable()实现的一些思考。
常见的开启/关闭抢占的接口
spin_lock()
- 关抢占 (1)
- 拿锁 (2)
spin_unlock()
- 释放锁 (1)
- 开抢占 (2)
spin_lock_irq()
- 关本地中断 (1)
- 关抢占 (2)
- 拿锁 (3)
spin_unlock_irq()
- 释放锁 (1)
- 开本地中断 (2)
- 开抢占 (3)
拿锁时候都是最后一步拿锁,此时关闭抢占/关闭本地中断,确保拿到锁之后不会被本CPU其它进程抢占或者被本CPU中断打断
放锁时候都是第一步就释放锁,然后再去开抢占/本地中断,因为要让其它CPU上等待锁的进程能尽快拿锁。
spin_unlock_irq()中执行preempt_enable()开启抢占时,有可能进行进程调度。
preempt_enable()的实现
(Linux内核版本 4.19)
#ifdef CONFIG_PREEMPT
#define preempt_enable() \
do { \
barrier(); \
if (unlikely(preempt_count_dec_and_test())) \ //开抢占时判断是否嫩执行进程抢占切换
__preempt_schedule(); \
} while (0)
#define preempt_count_dec_and_test() __preempt_count_dec_and_test()
static __always_inline bool __preempt_count_dec_and_test(void)
{
/*
* Because of load-store architectures cannot do per-cpu atomic
* operations; we cannot use PREEMPT_NEED_RESCHED because it might get
* lost.
*/
return !--*preempt_count_ptr() && tif_need_resched(); //允许抢占切换的条件:1.抢占计数为0 2.当前进程TIF_NEED_RESCHED被置位
}
static __always_inline volatile int *preempt_count_ptr(void)
{
return ¤t_thread_info()->preempt_count; //这里有点小疑惑,preempt_count保存在struct thread_info ??
}
#define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)
#define test_thread_flag(flag) \
test_ti_thread_flag(current_thread_info(), flag)
static inline int test_ti_thread_flag(struct thread_info *ti, int flag)
{
return test_bit(flag, (unsigned long *)&ti->flags);
}
asmlinkage __visible void __sched notrace preempt_schedule(void)
{
/*
* If there is a non-zero preempt_count or interrupts are disabled,
* we do not want to preempt the current task. Just return..
*/
if (likely(!preemptible()))
return;
preempt_schedule_common();
}
#define preemptible() (preempt_count() == 0 && !irqs_disabled()) //条件1:抢占计数为0 条件2:中断开启
preempt_enable()中当以下条件满足,则执行preempt_schedule()尝试切换进程
- 当前进程抢占计数preempt_count为0(说明不在中断处理上下文中)
- 当前进程thread_info中flag的TIF_NEED_RESCHED被置位
preempt_schedule()首先检查是否可以抢占调度,条件如下:
- 当前进程抢占计数preempt_count为0
- CPU本地中断为开启状态
preempt_schedule()为什么要求CPU本地中断为开启状态呢?
我的理解是这样的:假设进程A在CPU2上执行local_irq_disable()关掉CPU2的本地中断后,在未调用local_irq_enable()之前,被CPU2的进程B抢占执行,那么此时CPU2的本地中断什么时候开启,我们是不确定的。因为从设计上我们需要确保【谁关闭了中断,那么就由谁来开启中断】的编程模型不被破坏,所以当进程A重新调度到CPU2上执行local_irq_enable()时,CPU2本地中断才被打开。 当然,进程B在CPU2如果执行local_irq_enable()最终仍然会打开CPU2的本地中断。
对进程A呢?如果进程A在CPU2执行时,还没有打开CPU2的本地中断,就被抢占调度随即被调度到CPU3继续执行,那么进程A在CPU3执行local_irq_enable()则直接打开了CPU3的本地中断。有同学可能会问:直接打开CPU3的中断有什么问题吗? 我的想法是这样的:如果我们允许抢占发生时本地中断为关闭状态,那么CPU3上的进程有可能由于同步原因关闭了CPU3的本地中断,一旦进程A切换到CPU3执行,进程A执行local_irq_enable()时直接打开了CPU3的本地中断,这可能会导致CPU3其它进程的同步逻辑错误。
我的理解是这样的:
编程模型是系统设计原则在编程规范中的体现。设计上采用了【谁关闭本地中断,那么由谁来激活本地中断】的原则,因此软件层面的行为即是遵循此设计思想的具体工程实现
不积跬步无以至千里
巨人的肩膀
宋宝华: 是谁关闭了Linux抢占,而抢占又关闭了谁?
浙公网安备 33010602011771号