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

发布时间:2024年01月14日

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

概述

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

preempt_count变量

preempt_count字段

preempt_count变量使用不同的位来标识系统上下文状态,包括抢占状态、硬中断上下文、软中断上下文、NMI上下文等。preempt_count变量包含以下几个部分:
在这里插入图片描述

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

访问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);
}

系统上下文的设置与判断

内核抢占操作

使能抢占
#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)

硬中断上下文

in_irq()宏可用于判断当前是否正处于硬中断上下文中。

#define hardirq_count()	(preempt_count() & HARDIRQ_MASK)
#define in_irq()		(hardirq_count())
进入中断上下文
#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! */
}

软中断上下文

软中断上下文的判断要稍微特殊一些,最低的1位被用于判断是否处于软中断上下文中,因此软中断上下文的判断应该使用in_serving_softirq()宏;高7位被用于使能/禁用中断上半部。

#define softirq_count()	(preempt_count() & SOFTIRQ_MASK)
#define in_softirq()		(softirq_count())
#define in_serving_softirq()	(softirq_count() & SOFTIRQ_OFFSET)
进入软中断下文
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);
}

NMI上下文

#define in_nmi()		(preempt_count() & NMI_MASK)

中断上下文

中断上下文包含了硬中断上下文、软中断上下文以及NMI上下文。

#define irq_count()	(preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK))
#define in_interrupt()		(irq_count())

任务上下文

in_task()用于判断当前执行环境是否处于进程上下文环境,简单来说就是判断当前是否处于中断环境,然后将结果取反即可。

#define in_task()		(!(preempt_count() &  (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_OFFSET)))

相关参考

文章来源:https://blog.csdn.net/anyegongjuezjd/article/details/135578187
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。