spin_lock的变体

2020-03-24 10:54:17 浏览数 (1)

spin_lock变体的引入

考虑如下图所示的情况:

当处理器上当前进程A需要对共享变量a操作,所以在操作前通过spin_lock获取锁进入临界区,如上图标号1。当进程A进入临界区后,进程A所在的处理器发生了一个外部硬件中断,此时系统必须停下进程A的执行转向执行中断,如上图标号2。假设中断处理程序也需要操作共享变量a,所以在操作之前也许要调用spin_lock获取锁来操作变量a。当中断处理程序试图去获取变量a的时候,因为之前被中断的进程A已经获取了锁,于是将导致中断处理程序进入自旋状态。在中断处理程序中出现自旋是非常致命的,因为中断处理程序必须尽可能短的返回。同时被中断进程A因中断处理程序不能返回而无法恢复执行,也就不可能释放锁,所以将导致中断处理程序一直自旋下去,出现死锁。所以就引入了spin_lock的变体出现。

spin_lock_irq

spin_lock_irq对比之前的spin_lock的不同是: 在进入临界区的时候增加关闭本地处理器响应中断的能力。

代码语言:javascript复制
static inline void spin_lock_irq(spinlock_t *lock)
{
	raw_spin_lock_irq(&lock->rlock);
}

#define raw_spin_lock_irq(lock)		_raw_spin_lock_irq(lock)

void __lockfunc _raw_spin_lock_irq(raw_spinlock_t *lock)
{
	__raw_spin_lock_irq(lock);
}

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

根据上面代码可以明显的看见spin_lock_irq对比spin_lock就是多了local_irq_disable,关于别的内容不再解释,可以详看spin_lock一节

local_irq_disable

local_irq_disable是用来关闭本地处理器的中断。

代码语言:javascript复制
#define local_irq_disable()	do { raw_local_irq_disable(); } while (0)
#define raw_local_irq_disable()		arch_local_irq_disable()

因为arch_local_irq_disable的实现是和具体的硬件平台有关,所以以ARM平台为主分析其实现。

local_irq_disable在ARM32上实现

代码语言:javascript复制
/*
 * Disable IRQs
 */
static inline void arch_local_irq_disable(void)
{
	unsigned long temp;
	asm volatile(
		"	mrs	%0, cpsr	@ arch_local_irq_disablen"
		"	orr	%0, %0, #128n"
		"	msr	cpsr_c, %0"
		: "=r" (temp)
		:
		: "memory", "cc");
}

如果还有朋友不熟悉嵌入汇编的话,可以阅读前面<GCC内嵌汇编>小节。

在分析代码之前,需要解释下CPSR寄存器以及格式。

cpsr寄存器

程序状态寄存器(current program status register) (当前程序状态寄存器),在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。

而操作cpst寄存器是不能使用mov,ldr等通用的指令,只能使用特权指令: MSR, MRS

MRS R1,CPSR ; 将CPSR状态寄存器读取,保存到R1中 MSR SPSR, R2 ; 将R2的值保存到SPSR状态寄存器中

这里不把每一位都介绍到位,只介绍需要的而已。

bit[6] : 快速中断屏蔽位,置为1表示禁止快速中断,置为0表示打开快速中断

bit[7] : 外部中断屏蔽位,置为1表示禁止外部中断,置为0表示打开外部中断

local_irq_disable代码分析

汇编

c语言

解释

mrs %0, cpsr

temp=cpsr

将cpsr的值赋值为temp临时变量

orr %0, %0, #128

bit[7]=1

将Bit7置1,将结果保存到temp中

msr cpsr_c, %0

cpsr=temp

将temp中的值赋值为cpsr

通过上述的操作,就可以将cpsr的bit设置为1,从而关闭了本地cpu中断。

local_irq_disable在ARM64上实现

代码语言:javascript复制
static inline void arch_local_irq_disable(void)
{
	asm volatile(
		"<span style="font-family: Arial, Helvetica, sans-serif;">msr	daifset, #2  </span>// arch_local_irq_disable"
		:
		:
		: "memory");
}

可以看到上述的代码非常少,关键之处还是在daifset指令,查看armV8参考手册。

上面的意思是在ARM64中可以使用DAIFSet, DAIFClr可以直接的操作PSTATE区域。

MSR DAIFSet, #Imm4 ; 用于设置DAIF域为1

MSR DAIFClr, #Imm4 ; 用与清除DAIF域为0

而其中的DAIF代码的是:

代码语言:javascript复制
type ProcState is (
....
bits (1) D, // Debug mask bit                       [AArch64 only]
bits (1) A, // Asynchronous abort mask bit
bits (1) I, // IRQ mask bit
bits (1) F, // FIQ mask bit
.....
)

而根据代码:

代码语言:javascript复制
msr	daifset, #2	

可以知道,#Imm4=2的时候的含义是:

红色区域的意思就是设置DAIF中的I位为1, 设置为1就代码关闭了IRQ中断。

至于arch_local_irq_enable函数原理相通,在这里不做过多解释。

0 人点赞