本文基于Linux 5.10.186版本内核源码进行分析。
Linux系统在运行时,总会处于某一种特定的上下文中,例如进程上下文、中断上下文等,为了判断系统当前运行的上下文状态,内核提供了preempt_count变量来记录当前运行的上下文信息。
preempt_count变量使用不同的位来标识系统上下文状态,包括抢占状态、硬中断上下文、软中断上下文、NMI上下文等。preempt_count变量包含以下几个部分:
Linux内核定义了一系列的宏,用于判断系统当下所处的上下文,这些宏都与基于preempt变量实现,如下:
#define hardirq_count() (preempt_count() & HARDIRQ_MASK)
#define in_irq() (hardirq_count())
#define softirq_count() (preempt_count() & SOFTIRQ_MASK)
#define in_softirq() (softirq_count())
#define in_serving_softirq() (softirq_count() & SOFTIRQ_OFFSET)
#define in_nmi() (preempt_count() & NMI_MASK)
#define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK))
#define in_interrupt() (irq_count())
#define in_task() (!(preempt_count() & (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_OFFSET)))
preempt_count变量的意义在各个体系结构上是类似的,但是实现方式可能存在差异。
在x86体系中,preempt_count定义为一个名为__preempt_count的Per-CPU变量。
DECLARE_PER_CPU(int, __preempt_count);
static __always_inline int preempt_count(void)
{
return raw_cpu_read_4(__preempt_count) & ~PREEMPT_NEED_RESCHED;
}
在ARMv8体系下,preempt_count为定义在thread_info结构中的一个变量,如下:
struct thread_info {
...
union {
u64 preempt_count; /* 0 => preemptible, <0 => bug */
struct {
#ifdef CONFIG_CPU_BIG_ENDIAN
u32 need_resched;
u32 count;
#else
u32 count;
u32 need_resched;
#endif
} preempt;
};
...
};
因此,ARMv8体系需要通过thread_info结构来访问preempt_count信息。
static inline int preempt_count(void)
{
return READ_ONCE(current_thread_info()->preempt.count);
}
设置系统上下文其实就是对preempt_count变量的相应字段进行配置,针对不同上下文,Linux内核提供的常用接口定义如下:
#define preempt_enable() \
do { \
barrier(); \
if (unlikely(preempt_count_dec_and_test())) \
__preempt_schedule(); \
} while (0)
#define preempt_disable() \
do { \
preempt_count_inc(); \
barrier(); \
} while (0)
static __always_inline void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
{
preempt_count_add(cnt);
barrier();
}
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET); /* 退出软中断上下文 */
static void __local_bh_enable(unsigned int cnt)
{
...
preempt_count_sub(cnt);
}
__local_bh_enable(SOFTIRQ_OFFSET); /* 进入软中断上下文 */
static inline void local_bh_disable(void)
{
__local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}
static inline void local_bh_enable(void)
{
__local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}
#define __irq_enter() \
do { \
account_irq_enter_time(current); \
preempt_count_add(HARDIRQ_OFFSET); \
trace_hardirq_enter(); \
} while (0)
void irq_enter(void)
{
rcu_irq_enter();
if (is_idle_task(current) && !in_interrupt()) {
local_bh_disable();
tick_irq_enter();
_local_bh_enable();
}
__irq_enter();
}
void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
lockdep_assert_irqs_disabled();
#endif
account_irq_exit_time(current);
preempt_count_sub(HARDIRQ_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
tick_irq_exit();
rcu_irq_exit();
trace_hardirq_exit(); /* must be last! */
}