内核抢占、进程和中断互斥

内核抢占

内核抢占是指当进程在内核模式下运行的时候可以被其他进程抢占,编译内核时打开配置宏CONFIG_PREEMPT则允许内核抢占。

抢占是指当进程在内核模式下运行的时候可以被其他进程抢占,如果优先级更高的进程处于就绪状态,强行剥夺当前进程的处理器使用权。

禁止内核抢占

有时候进程可能在执行一些关键操作,不能被抢占;所以内核设计了抢占计数器。如果抢占计数器为0,表示可以被抢占;如果抢占计数器器不为0,表示不能被抢占

抢占计数器

每个进程的task_struct结构体中的thread_info结构体有一个抢占计数器:preempt_count,用来表示当前进程是否可以被抢占。arm64架构下的thread_info定义如下:

/*
 * low level task data that entry.S needs immediate access to.
 */
struct thread_info {
	unsigned long		flags;		/* low level flags */
	mm_segment_t		addr_limit;	/* address limit */
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
	u64			ttbr0;		/* saved TTBR0_EL1 */
#endif
	int			preempt_count;	/* 0 => preemptable, <0 => bug */
};

preempt_count是一个32位的整数,内核对它的位进行了划分,不同位域对应不同的场景,如下图:

字段位数用途
不可屏蔽中断标志(NMI,Non Maskable Interrupt)1位(20)记录不可屏蔽中断
硬中断计数4 位(16-19)记录硬中断计数
禁用软中断计数8 位(8-15)记录禁用软中断的计数
抢占禁用计数8 位(0-7)表示内核中主动禁用抢占的层级(如 preempt_disable()
)。

相关定义代码如下:

/*
 * We put the hardirq and softirq counter into the preemption
 * counter. The bitmask has the following meaning:
 *
 * - bits 0-7 are the preemption count (max preemption depth: 256)
 * - bits 8-15 are the softirq count (max # of softirqs: 256)
 *
 * The hardirq count could in theory be the same as the number of
 * interrupts in the system, but we run all interrupt handlers with
 * interrupts disabled, so we cannot have nesting interrupts. Though
 * there are a few palaeontologic drivers which reenable interrupts in
 * the handler, so we need more than one bit here.
 *
 *         PREEMPT_MASK:	0x000000ff
 *         SOFTIRQ_MASK:	0x0000ff00
 *         HARDIRQ_MASK:	0x000f0000
 *             NMI_MASK:	0x00100000
 * PREEMPT_NEED_RESCHED:	0x80000000
 */
#define PREEMPT_BITS	8
#define SOFTIRQ_BITS	8
#define HARDIRQ_BITS	4
#define NMI_BITS	1

各种场景分别利用各自的位禁止或开启抢占

(1)普通场景(PREEMPT_MASK):对应函数preempt_disable()和 preeempt_enable()。

(2)软中断场景(SOFTIRQ_MASK):对应函数local_bh_disable()和local_bh_enable()。

(3)硬中断场景(HARDIRQ_MASK):对应函数__irq_enter()和__irq_exit()。

(4)不可屏蔽中断场景(NMI_MASK):对应函数nmi_enter()和nni_exit()。

反过来,我们可以通过抢占计数器的值判断当前处于什么场景

#define hardirq_count()	(preempt_count() & HARDIRQ_MASK)
#define softirq_count()	(preempt_count() & SOFTIRQ_MASK)
#define irq_count()	(preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
				 | NMI_MASK))

/*
 * Are we doing bottom half or hardware interrupt processing?
 *
 * in_irq()       - We're in (hard) IRQ context
 * in_softirq()   - We have BH disabled, or are processing softirqs
 * in_interrupt() - We're in NMI,IRQ,SoftIRQ context or have BH disabled
 * in_serving_softirq() - We're in softirq context
 * in_nmi()       - We're in NMI context
 * in_task()	  - We're in task context
 *
 * Note: due to the BH disabled confusion: in_softirq(),in_interrupt() really
 *       should not be used in new code.
 */
#define in_irq()		(hardirq_count())
#define in_softirq()		(softirq_count())
#define in_interrupt()		(irq_count())
#define in_serving_softirq()	(softirq_count() & SOFTIRQ_OFFSET)
#define in_nmi()		(preempt_count() & NMI_MASK)
#define in_task()		(!(preempt_count() & \
				   (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_OFFSET)))
- in_irq()表示硬中断场景,也就是正在执行硬中断。
- in_softirq()表示软中断场景,包括禁止软中断和正在执行软中断。
- in_interrupt()表示正在执行不可屏蔽中断、硬中断或软中断,或者禁止软中断。
- in_serving_softirq()表示正在执行软中断。
- in_nmi()表示不可屏蔽中断场景。
- in_task()表示普通场景,也就是进程上下文。

内核自旋锁,申请自旋锁的函数中包含了禁止内核抢占(**preempt_disable**

spin_lock() -> raw_spin_lock() -> _raw_spin_lock() -> __raw_spin_lock()

static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
	preempt_disable();
	spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
	LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

释放自旋锁的函数中包含了开启内核抢占(**preempt_enable**

spin_unlock() -> raw_spin_unlock() -> _raw_sspin_unlock() -> __raw_spin_unlock()

static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
	spin_release(&lock->dep_map, 1, _RET_IP_);
	do_raw_spin_unlock(lock);
	preempt_enable();
}

进程和软中断互斥

如果进程和软中断可能访问同一个对象,那么进程和软中断需要互斥,进程需要禁止软中断。

如果进程只需要和本处理器的软中断互斥,那么进程只需要要禁止本处理器的软中断;如果进程要和所有处理器的软中断互斥,那么进程需要禁止本处理器的软中断,还要使用自旋锁和其他处理器的软中断互斥。

抢占计数器preempt_count中,bit 8 ~ bit 15表示软中断计数。

禁止软中断local_bh_disable()的时候把当前进程的软中断计数加2

注意:这个接口只能禁止本处理器的软中断,不能禁止其他处理器的软中断。

开启软中断local_bh_enable()的时候把当前进程的软中断计数减2。如果软中断计数、硬中断计数和不可屏蔽中断计数都是0,并且有软中断需要处理,那么执行软中断。

static __always_inline void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
{
	preempt_count_add(cnt);
	barrier();
}

// include/linux/preempt.h
/*
#define SOFTIRQ_DISABLE_OFFSET	(2 * SOFTIRQ_OFFSET)
#define SOFTIRQ_OFFSET	(1UL << SOFTIRQ_SHIFT)
#define SOFTIRQ_SHIFT	(PREEMPT_SHIFT + PREEMPT_BITS)
#define PREEMPT_SHIFT	0
#define PREEMPT_BITS	8
*/
static inline void local_bh_disable(void)
{
	__local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}

static inline void local_bh_enable_ip(unsigned long ip)
{
	__local_bh_enable_ip(ip, SOFTIRQ_DISABLE_OFFSET);
}

static inline void local_bh_enable(void)
{
	__local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}

进程和硬中断互斥

如果进程和硬中断可能访问同一个对象,那么进程和硬中断需要要互斥,进程需要禁止硬中断。

如果进程只需要和本处理器的硬中断互斥,那么进程只需要要禁止本处理器的硬中断;如果进程要和所有处理器的硬中断互斥,那么进程需要禁止本处理器的硬中断,还要使用自旋锁和其他处理器的硬中断互斥(有禁用所有处理器中断的方法,但一般不使用)。

禁止硬中断的接口如下。

(1)local_irq_disable()

(2)local_irq_save(flags):首先把硬中断状态保存在参数flags中,然后禁止硬中断。

这两个接口只能禁止本处理器的硬中断,不能禁止其他处理器的硬中断。禁止硬中断后,处理器不会响应中断请求。

开启硬中断的接口如下。

(1)local_irq_enable()

(2)local_irq_restore(flags):恢复本处理器的硬中断状态。

local_irq_disable()local_irq_enable()不能嵌套使用,local_irq_save(flags)local_irq_restore(flags)可以嵌套使用。


硬中断如何使用抢占计数器preempt_count

单纯的禁用硬中断<font style="color:rgb(64, 64, 64);">local_irq_disable()</font>不会修改<font style="color:rgb(64, 64, 64);">preempt_count</font>,它主要通过设置ARM64的DAIF寄存器中的来屏蔽中断。

当中断实际发生时,进入中断处理程序会调用<font style="color:rgb(64, 64, 64);">irq_enter()</font>,这会修改<font style="color:rgb(64, 64, 64);">preempt_count</font>的HARDIRQ域。

/*
 * Enter an interrupt context.
 */
void irq_enter(void)
{
	rcu_irq_enter();
	if (is_idle_task(current) && !in_interrupt()) {
		/*
		 * Prevent raise_softirq from needlessly waking up ksoftirqd
		 * here, as softirq will be serviced on return from interrupt.
		 */
		local_bh_disable();
		tick_irq_enter();
		_local_bh_enable();
	}

	__irq_enter();
}
/*
 * It is safe to do non-atomic ops on ->hardirq_context,
 * because NMI handlers may not preempt and the ops are
 * always balanced, so the interrupted value of ->hardirq_context
 * will always be restored.
 */
#define __irq_enter()					\
	do {						\
		account_irq_enter_time(current);	\
		preempt_count_add(HARDIRQ_OFFSET);	\
		trace_hardirq_enter();			\
	} while (0)

参考资料

  1. Professional Linux Kernel Architecture,Wolfgang Mauerer
  2. Linux内核深度解析,余华兵
  3. Linux设备驱动开发详解,宋宝华
  4. linux kernel 4.12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值