浅析Linux进程管理:preempt_count抢占计数器

本文基于Linux 5.10.186版本内核源码进行分析。

文章目录

    • preempt_count变量
      • preempt_count字段
      • 使用preempt变量判断系统上下文
      • preempt_count变量的定义
        • x86体系下preempt_count变量
        • ARMv8体系下preempt_count变量
    • 系统上下文的设置
      • 内核抢占操作
        • 使能抢占
        • 禁止抢占
      • 软中断上下文
        • 进入软中断下文
        • 退出软中断上下文
        • 禁用/使能中断下半部
      • 硬中断上下文
        • 进入中断上下文
        • 退出中断上下文
    • 相关参考

preempt_count变量

Linux系统在运行时,总会处于某一种特定的上下文中,例如进程上下文、中断上下文等,为了判断系统当前运行的上下文状态,内核提供了preempt_count变量来记录当前运行的上下文信息。

preempt_count字段

preempt_count变量使用不同的位来标识系统上下文状态,包括抢占状态、硬中断上下文、软中断上下文、NMI上下文等。preempt_count变量包含以下几个部分:
浅析Linux进程管理:preempt_count抢占计数器_第1张图片

  • preempt:用于抢占计数,linux内核支持进程的抢占调度,但是在很多情况下我们需要禁止抢占,每禁止一次,这个数值就加 1,在使能抢占的时候将减 1,系统支持的最大嵌套次数为256次;
  • softirq:记录软中断的嵌套次数,由于软中断在单个cpu上不会嵌套执行,因此仅第8位就可以用来判断当前是否处于软中断上下文中,而其它的9~15位用于表示是否禁用了中断下半部;
  • hardirq:记录硬中断嵌套次数,在最新版本内核中由于不再支持中断嵌套,实际只用到了1位,为1则表示处于硬中断上下文中;
  • NMI:用于指示 NMI 中断,实际只使用到了1位, 因此仅涉及两个状态:发生并处理NMI中断置1,退出中断清除。

使用preempt变量判断系统上下文

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)))
  • in_irq():判断当前是否位于硬中断上下文;
  • in_softirq():判断当前是否位于BH临界区,或正在处理软中断;
  • in_interrupt():判断当前是否位于NMI、硬中断上下文、软中断上下文,或位于BH临界区中;
  • in_serving_softirq():判断当前是否位于软中断上下文;
  • in_nmi():判断当前是否位于NMI上下文;
  • in_task():判断当前是否位于任务上下文。

preempt_count变量的定义

preempt_count变量的意义在各个体系结构上是类似的,但是实现方式可能存在差异。

x86体系下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变量

在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内核提供的常用接口定义如下:
浅析Linux进程管理:preempt_count抢占计数器_第2张图片

内核抢占操作

使能抢占
#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! */
}

相关参考

  • linux 中断子系统 - linux 内核中的上下文判断
  • Linux中的preempt_count

你可能感兴趣的:(#,进程管理,linux,服务器,运维)