Linux Kernel之spin_lock之ARM64实现

函数arch_spin_lock()实现:
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
	unsigned int tmp;
	arch_spinlock_t lockval, newval;

	asm volatile(
	/* Atomically increment the next ticket. */
	ARM64_LSE_ATOMIC_INSN(
	/* LL/SC */
"	prfm	pstl1strm, %3\n"
"1:	ldaxr	%w0, %3\n"
"	add	%w1, %w0, %w5\n"
"	stxr	%w2, %w1, %3\n"
"	cbnz	%w2, 1b\n",
	/* LSE atomics */
"	mov	%w2, %w5\n"
"	ldadda	%w2, %w0, %3\n"
	__nops(3)
	)


	/* Did we get the lock? */
"	eor	%w1, %w0, %w0, ror #16\n"
"	cbz	%w1, 3f\n"
	/*
	 * No: spin on the owner. Send a local event to avoid missing an
	 * unlock before the exclusive load.
	 */
"	sevl\n"
"2:	wfe\n"
"	ldaxrh	%w2, %4\n"
"	eor	%w1, %w2, %w0, lsr #16\n"
"	cbnz	%w1, 2b\n"
	/* We got the lock. Critical section starts here. */
"3:"
	: "=&r" (lockval), "=&r" (newval), "=&r" (tmp), "+Q" (*lock)
	: "Q" (lock->owner), "I" (1 << TICKET_SHIFT)
	: "memory");
}
在理解上面操作之前,先看看ARMv8手册相关说明:
A PE can use the Wait for Event (WFE) mechanism to enter a low-power state, depending on the value of an Event
Register for that PE. To enter the low-power state, the PE executes a Wait For Event instruction, WFE, and if the Event
Register is clear, the PE can enter the low-power state.
If the PE does enter the low-power state, it remains in that low-power state until it receives a WFE wake-up event.
The architecture does not define the exact nature of the low-power state, except that the execution of a WFE
instruction must not cause a loss of memory coherency.
WFE mechanism behavior depends on the interaction of all of the following, that are described in the subsections
that follow:
• The Event Register for the PE. See subsection The Event Register on page D1-1529.
• The Wait For Event instruction, WFE. See subsection The Wait For Event instruction on page D1-1529.
• WFE wake-up events. See subsection WFE wake-up events in AArch64 state on page D1-1530
• The Send Event instructions, SEV and SEVL that can cause WFE wake-up events. See subsection The Send
Event instructions on page D1-1530.
也就是说,PE可以进入低功耗模式,采用执行WFE指令。WFE指令实现基于一个Event register,即一个事件寄存器,如果事件寄存器被清除
则PE进入低功耗,进而执行WFE。这意味着当Event register被设置时,PE被唤醒。当然,这种是处理器内部逻辑来唤醒的。
还需要注意的是,这里的WFE指令到底是让处理器干啥,是未定义的,只要不让RAM丢失数据即可。也就是维护好cache 和memory一致性
就好。
WFE依赖下面几点:
1. 一个Event Register寄存器。
2. 一个等待Event的指令,即WFE。
3. WFE所等待的唤醒事件,定义哪些事件可以唤醒。
4. 发送事件指令,即WEV和SEVL。这两个指令可以触发唤醒事件,进而让处于WFE状态的PE重新工作起来。
详细的内容读者可以参考上面给出的文档链接。
 
所以,我们可以看到函数arch_spin_lock()实现。其中有三个标号,代表了三个跳转。标号3代表获取了锁,函数执行到此后,简单设置后返回。
标号2到标号3是一个WFE状态检查,即处理唤醒事件,如果么有唤醒事件,则继续处于WFE状态。注意标号2之前的SEVL指令,其用于发送唤醒事件。
可以想象,SEVL是个处理器间的唤醒发送操作。我们这段spin_lock代码,真正发送事件的应该是在unlock中。
 
static inline int arch_spin_trylock(arch_spinlock_t *lock)
{
	unsigned int tmp;
	arch_spinlock_t lockval;


	asm volatile(ARM64_LSE_ATOMIC_INSN(
	/* LL/SC */
	"	prfm	pstl1strm, %2\n"
	"1:	ldaxr	%w0, %2\n"
	"	eor	%w1, %w0, %w0, ror #16\n"
	"	cbnz	%w1, 2f\n"
	"	add	%w0, %w0, %3\n"
	"	stxr	%w1, %w0, %2\n"
	"	cbnz	%w1, 1b\n"
	"2:",
	/* LSE atomics */
	"	ldr	%w0, %2\n"
	"	eor	%w1, %w0, %w0, ror #16\n"
	"	cbnz	%w1, 1f\n"
	"	add	%w1, %w0, %3\n"
	"	casa	%w0, %w1, %2\n"
	"	and	%w1, %w1, #0xffff\n"
	"	eor	%w1, %w1, %w0, lsr #16\n"
	"1:")
	: "=&r" (lockval), "=&r" (tmp), "+Q" (*lock)
	: "I" (1 << TICKET_SHIFT)
	: "memory");


	return !tmp;
}
 
 
static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
	unsigned long tmp;


	asm volatile(ARM64_LSE_ATOMIC_INSN(
	/* LL/SC */
	"	ldrh	%w1, %0\n"
	"	add	%w1, %w1, #1\n"
	"	stlrh	%w1, %0",
	/* LSE atomics */
	"	mov	%w1, #1\n"
	"	staddlh	%w1, %0\n"
	__nops(1))
	: "=Q" (lock->owner), "=&r" (tmp)
	:
	: "memory");
}
 
 
/*
 * Write lock implementation.
 *
 * Write locks set bit 31. Unlocking, is done by writing 0 since the lock is
 * exclusively held.
 *
 * The memory barriers are implicit with the load-acquire and store-release
 * instructions.
 */


static inline void arch_write_lock(arch_rwlock_t *rw)
{
	unsigned int tmp;


	asm volatile(ARM64_LSE_ATOMIC_INSN(
	/* LL/SC */
	"	sevl\n"
	"1:	wfe\n"
	"2:	ldaxr	%w0, %1\n"
	"	cbnz	%w0, 1b\n"
	"	stxr	%w0, %w2, %1\n"
	"	cbnz	%w0, 2b\n"
	__nops(1),
	/* LSE atomics */
	"1:	mov	%w0, wzr\n"
	"2:	casa	%w0, %w2, %1\n"
	"	cbz	%w0, 3f\n"
	"	ldxr	%w0, %1\n"
	"	cbz	%w0, 2b\n"
	"	wfe\n"
	"	b	1b\n"
	"3:")
	: "=&r" (tmp), "+Q" (rw->lock)
	: "r" (0x80000000)
	: "memory");
}
 
static inline int arch_write_trylock(arch_rwlock_t *rw)
{
	unsigned int tmp;


	asm volatile(ARM64_LSE_ATOMIC_INSN(
	/* LL/SC */
	"1:	ldaxr	%w0, %1\n"
	"	cbnz	%w0, 2f\n"
	"	stxr	%w0, %w2, %1\n"
	"	cbnz	%w0, 1b\n"
	"2:",
	/* LSE atomics */
	"	mov	%w0, wzr\n"
	"	casa	%w0, %w2, %1\n"
	__nops(2))
	: "=&r" (tmp), "+Q" (rw->lock)
	: "r" (0x80000000)
	: "memory");


	return !tmp;
}
 
static inline void arch_write_unlock(arch_rwlock_t *rw)
{
	asm volatile(ARM64_LSE_ATOMIC_INSN(
	"	stlr	wzr, %0",
	"	swpl	wzr, wzr, %0")
	: "=Q" (rw->lock) :: "memory");
}
 
 
/*
 * Read lock implementation.
 *
 * It exclusively loads the lock value, increments it and stores the new value
 * back if positive and the CPU still exclusively owns the location. If the
 * value is negative, the lock is already held.
 *
 * During unlocking there may be multiple active read locks but no write lock.
 *
 * The memory barriers are implicit with the load-acquire and store-release
 * instructions.
 *
 * Note that in UNDEFINED cases, such as unlocking a lock twice, the LL/SC
 * and LSE implementations may exhibit different behaviour (although this
 * will have no effect on lockdep).
 */
static inline void arch_read_lock(arch_rwlock_t *rw)
{
	unsigned int tmp, tmp2;


	asm volatile(
	"	sevl\n"
	ARM64_LSE_ATOMIC_INSN(
	/* LL/SC */
	"1:	wfe\n"
	"2:	ldaxr	%w0, %2\n"
	"	add	%w0, %w0, #1\n"
	"	tbnz	%w0, #31, 1b\n"
	"	stxr	%w1, %w0, %2\n"
	"	cbnz	%w1, 2b\n"
	__nops(1),
	/* LSE atomics */
	"1:	wfe\n"
	"2:	ldxr	%w0, %2\n"
	"	adds	%w1, %w0, #1\n"
	"	tbnz	%w1, #31, 1b\n"
	"	casa	%w0, %w1, %2\n"
	"	sbc	%w0, %w1, %w0\n"
	"	cbnz	%w0, 2b")
	: "=&r" (tmp), "=&r" (tmp2), "+Q" (rw->lock)
	:
	: "cc", "memory");
}
 
static inline void arch_read_unlock(arch_rwlock_t *rw)
{
	unsigned int tmp, tmp2;


	asm volatile(ARM64_LSE_ATOMIC_INSN(
	/* LL/SC */
	"1:	ldxr	%w0, %2\n"
	"	sub	%w0, %w0, #1\n"
	"	stlxr	%w1, %w0, %2\n"
	"	cbnz	%w1, 1b",
	/* LSE atomics */
	"	movn	%w0, #0\n"
	"	staddl	%w0, %2\n"
	__nops(2))
	: "=&r" (tmp), "=&r" (tmp2), "+Q" (rw->lock)
	:
	: "memory");
}


static inline int arch_read_trylock(arch_rwlock_t *rw)
{
	unsigned int tmp, tmp2;


	asm volatile(ARM64_LSE_ATOMIC_INSN(
	/* LL/SC */
	"	mov	%w1, #1\n"
	"1:	ldaxr	%w0, %2\n"
	"	add	%w0, %w0, #1\n"
	"	tbnz	%w0, #31, 2f\n"
	"	stxr	%w1, %w0, %2\n"
	"	cbnz	%w1, 1b\n"
	"2:",
	/* LSE atomics */
	"	ldr	%w0, %2\n"
	"	adds	%w1, %w0, #1\n"
	"	tbnz	%w1, #31, 1f\n"
	"	casa	%w0, %w1, %2\n"
	"	sbc	%w1, %w1, %w0\n"
	__nops(1)
	"1:")
	: "=&r" (tmp), "=&r" (tmp2), "+Q" (rw->lock)
	:
	: "cc", "memory");


	return !tmp2;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
spin_lock() 和 spin_lock_irqsave() 都是 Linux 内核中用于保护共享资源的自旋锁函数,它们的区别在于是否对中断进行处理。 spin_lock() 函数会获取自旋锁,并禁用本地 CPU 的中断。这意味着,如果在获取自旋锁的过程中发生了中断,中断处理程序将无法运行,直到自旋锁被释放。因此,spin_lock() 主要用于在中断被禁用的情况下保护共享资源,以防止其他 CPU 并发访问。 而 spin_lock_irqsave() 函数则会获取自旋锁,并保存本地 CPU 的中断状态。这意味着,在获取自旋锁的过程中,中断可以被触发,但是中断处理程序不能访问被保护的共享资源,因为它们也会尝试获取自旋锁。因此,spin_lock_irqsave() 主要用于在中断被启用的情况下保护共享资源。 在使用 spin_lock_irqsave() 函数时,需要在获取自旋锁的同时保存中断状态,并在释放自旋锁的同时恢复中断状态,以避免出现中断状态被篡改的情况。常见的用法是: ```c spinlock_t my_lock; unsigned long flags; spin_lock_irqsave(&my_lock, flags); // 获取自旋锁并保存中断状态 // 在这里进行对共享资源的访问 spin_unlock_irqrestore(&my_lock, flags); // 释放自旋锁并恢复中断状态 ``` 总之,spin_lock() 适用于在禁用中断的情况下保护共享资源,而 spin_lock_irqsave() 适用于在启用中断的情况下保护共享资源,它们的使用方式略有不同,需要根据具体情况选择合适的函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值