OK6410A 开发板 (八) 72 linux-5.11 OK6410A linux 内核同步机制 自旋锁的实现

  • 解决的问题是什么
所有竞态原因

大概意思
	原子操作写的时候 可以同时发生 ,但不会同时成功,只有一个成功(成功的同时置位占用标识)
	失败的那个,如果再去获取
		先去读占用标识 (肯定是已经被占用,程序上就不再获取了,而是一直读占用标识)

实现

  • API spin_lock 的定义
//https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock.h#L310
static inline void spin_lock(spinlock_t *lock)
{
	raw_spin_lock(&lock->rlock);
}
//https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock.h#L188
#define raw_spin_lock(lock)	_raw_spin_lock(lock)


#ifndef CONFIG_INLINE_SPIN_LOCK
void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
	__raw_spin_lock(lock);
}
EXPORT_SYMBOL(_raw_spin_lock);
#endif
https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock_api_smp.h#L141
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);
}


https://elixir.bootlin.com/linux/v4.0/source/include/linux/lockdep.h#L430
#define LOCK_CONTENDED(_lock, try, lock)			\
do {								\
	if (!try(_lock)) {					\
		lock_contended(&(_lock)->dep_map, _RET_IP_);	\
		lock(_lock);					\
	}							\
	lock_acquired(&(_lock)->dep_map, _RET_IP_);			\
} while (0)


https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock.h#L151

static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
{
	__acquire(lock);
	arch_spin_lock(&lock->raw_lock);
}

https://elixir.bootlin.com/linux/v4.0/source/arch/arm/include/asm/spinlock.h#L58
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
	unsigned long tmp;
	u32 newval;
	arch_spinlock_t lockval;

	prefetchw(&lock->slock);
	__asm__ __volatile__(
"1:	ldrex	%0, [%3]\n"
"	add	%1, %0, %4\n"
"	strex	%2, %1, [%3]\n"
"	teq	%2, #0\n"
"	bne	1b"
	: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
	: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
	: "cc");

	while (lockval.tickets.next != lockval.tickets.owner) {
		wfe();
		lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);
	}

	smp_mb();
}
  • API spin_lock_init 的定义
https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock.h#L304

#define spin_lock_init(_lock)				\
do {							\
	spinlock_check(_lock);				\
	raw_spin_lock_init(&(_lock)->rlock);		\
} while (0)
https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock.h#L103

# define raw_spin_lock_init(lock)				\
	do { *(lock) = __RAW_SPIN_LOCK_UNLOCKED(lock); } while (0)
https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock_types.h#L59

#define __RAW_SPIN_LOCK_UNLOCKED(lockname)	\
	(raw_spinlock_t) __RAW_SPIN_LOCK_INITIALIZER(lockname)

https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock_types.h#L53
#define __RAW_SPIN_LOCK_INITIALIZER(lockname)	\
	{					\
	.raw_lock = __ARCH_SPIN_LOCK_UNLOCKED \
	}
https://elixir.bootlin.com/linux/v4.0/source/arch/arm/include/asm/spinlock_types.h#L25
#define __ARCH_SPIN_LOCK_UNLOCKED	{ { 0 } }
// 注意这里 { { 0 } } 展开为
{
	.tickets = {
		.owner = 0
	}
}

  • spinlock_t 的定义
https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock_types.h#L76

typedef struct spinlock {
	union {
		struct raw_spinlock rlock;
	};
} spinlock_t;
https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock_types.h#L32

typedef struct raw_spinlock {
	arch_spinlock_t raw_lock;
} raw_spinlock_t;
https://elixir.bootlin.com/linux/v4.0/source/arch/arm/include/asm/spinlock_types.h#L23

typedef struct {
	union {
		u32 slock;
		struct __raw_tickets {
			u16 owner;
			u16 next;
		} tickets;
	};
} arch_spinlock_t;
如何索引
spinlock_t *spin_lock_val ;
spin_lock_val->rlock.raw_lock.tickets.owner
spin_lock_val->rlock.raw_lock.tickets.next
spin_lock_val->rlock.raw_lock.slock

arch_spinlock_t *raw_spinlock_val;
arch_spinlock_val->tickets.owner
arch_spinlock_val->tickets.next
arch_spinlock_val->slock

arm 架构 原子指令

LDREX
	用来读取内存中的值,并标记对该段内存的独占访问:
	LDREX Rx, [Ry]

		读取寄存器Ry指向的4字节内存值,将其保存到Rx寄存器中
	同时
		发现指向内存区域已经被标记为独占访问,则不会改变 "独占访问标记位"
		或
		发现指向内存区域没有被标记为独占访问,则 将 指向内存区域 标记为独占访问


STREX
	在更新内存数值时,会检查该段内存是否已经被标记为独占访问,并以此来决定是否更新内存中的值:

	STREX Rd, Rx, [Ry]

		执行这条指令的时候发现已经被标记为独占访问了
			1.将寄存器Rx中的值更新到寄存器Ry指向的内存,并将寄存器Rd设置成0
			2.将独占访问标记位清除

	或

		执行这条指令的时候发现没有设置独占标记
			1.不会更新内存,不会将寄存器Rd的值设置成1
			2.不会改变 "独占访问标记位"

---

arch_spin_trylock 时
	会先 load , 
		如果load 结果为0  , 就 store 1
			store 成功(Rd为0) 		, 就返回成功(0)
			store 失败(Rd为1) 		, 就返回失败(0)
		
		如果load 结果为1  			, 就返回失败(0)

场景分析

	第一种情况AB12
		A.LDREX
		A.STREX
		B.LDREX
		B.STREX
	第二种情况A1B2
		A.LDREX
		B.LDREX
		A.STREX
		B.STREX
	第三种情况A12B
		A.LDREX
		B.LDREX
		B.STREX
		A.STREX

	其他情况
		A.LDREX & B.LDREX (不管A和B在什么时候发生,都会正常执行)

		A.STREX & B.STREX (同时发生,只有一个会将Rd弄成0,另一个为1)
arm   是根据    该内存       	是否 标记独占	, 	strex 来做 不同的事情
riscv 是根据    该内存中的值  	是否 为0      	,	amoswap 来做不同的事情 

AMOSWAP  amoswap.{w/d}.{aqrl} rd, rs2, (rs1) 原子交换指令,
   会判断 rs1 是否为0
      如果为0 , 则 rd = *rs1, *rs1 = rs2 // 即rd 为0 , 同时 *rs1 = rs2
      		做成功了 , rd 为 0
      		做失败了 , rd 为 1
      如果为1 ,, rd = 1 , *rs1 = rs2


arch_spin_trylock 时
	会做 amoswap , 
		如果rs1 结果为0  , 就 尝试 swap  // 1. rd <- *rs1  2. *rs1 <-rs2
			swap 成功(rd为0) 		, 就返回成功(0)
			swap 失败(rd为1) 		, 就返回失败(0)
		
		如果rs1 结果为1  			, 就返回失败(0)

你可能感兴趣的:(ok6410开发板,linux,驱动开发,运维)