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函数原理相通,在这里不做过多解释。