代码分析基于4.19.195
我们先看一下代码
#define SOFTIRQ_DISABLE_OFFSET (2 * SOFTIRQ_OFFSET)
static __always_inline void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
{
preempt_count_add(cnt);
barrier();
}
static inline void local_bh_disable(void)
{
__local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}
特别注意SOFTIRQ_DISABLE_OFFSET的定义,按照正常逻辑来说,我们代码一般这么写
local_bh_disable()
****
local_bh_enable()
按照一般的逻辑来说,disable时加一,enable减一就可以了,先加二再减二不是很多余吗?
答案在local_bh_enable()函数中
static inline void local_bh_enable(void)
{
__local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}
void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
{
WARN_ON_ONCE(in_irq());
lockdep_assert_irqs_enabled();
#ifdef CONFIG_TRACE_IRQFLAGS
local_irq_disable();
#endif
/*
* Are softirqs going to be turned on now:
*/
if (softirq_count() == SOFTIRQ_DISABLE_OFFSET)
trace_softirqs_on(ip);
/*
* Keep preemption disabled until we are done with
* softirq processing:
*/
preempt_count_sub(cnt - 1);
if (unlikely(!in_interrupt() && local_softirq_pending())) {
/*
* Run softirq if any pending. And do it in its own stack
* as we may be calling this deep in a task call stack already.
*/
do_softirq();
}
preempt_count_dec();
#ifdef CONFIG_TRACE_IRQFLAGS
local_irq_enable();
#endif
preempt_check_resched();
}
EXPORT_SYMBOL(__local_bh_enable_ip);
首先得明确一点,在local_bh_enable函数中,如果有软中断需要处理,会调用do_softirq函数,这个函数是必须在当前cpu上执行的,do_softirq函数会调用local_softirq_pending函数获取当前cpu上有哪些软中断需要处理,在函数__local_bh_enable_ip中也会通过local_softirq_pending判断是否有软中断需要处理,这就要求执行local_bh_enable函数的进程在此期间一直在同一个cpu上运行,否则可能会获取到错误的软中断相关的信息。
有了这个前提,我们来看代码,__local_bh_enable_ip函数中,会先通过preempt_count_sub(cnt - 1)将抢占计数器的软中断域减1,这样至少保证此时本进程还不能被抢占,这个时候,我们就可以放心的处理软中断,即调用do_softirq()函数,处理完软中断之后,再调用preempt_count_dec(),这时候抢占就打开了,进程即使被调度到其他cpu上也不会带来获取软中断数据错误的问题了。
回到文章标题的问题,加2的原因,是因为在我们使能botom_half时,可能会处理软中断,此时需要保证当前cpu处于静止抢占的状态,等待软中断处理完毕,再将抢占计数器恢复为原来的状态。
写到这,突然有个想法,内核为何不直接在禁用软中断时只加1,而使能时处理完软中断后再减1,即把上面函数的preempt_count_sub(cnt - 1);去掉,这样不就能做到加一减一这样的逻辑了吗?
其实不然,内核加二的原因我认为是这样,通过抢占计数器软中断域的值是奇数还是偶数,我们就能轻易的判断此时内核是否在处理软中断,即是偶数时处于禁用bh的区间,为奇数时是在处理软中断,而加一减一的方法并不能带来这种效果。