解决的问题是什么
所有竞态原因
大概意思
原子操作写的时候 可以同时发生 ,但不会同时成功,只有一个成功(成功的同时置位占用标识)
失败的那个,如果再去获取
先去读占用标识 (肯定是已经被占用,程序上就不再获取了,而是一直读占用标识)
实现
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, %4n"
" strex %2, %1, [%3]n"
" teq %2, #0n"
" 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)
上一篇:OK6410A 开发板 (八) 73 linux-5.11 OK6410A linux 内核同步机制 互斥锁的实现
下一篇:OK6410A 开发板 (八) 71 linux-5.11 OK6410A linux 内核同步机制 禁软中断的实现
推荐阅读最新更新时间:2024-10-13 18:00
设计资源 培训 开发板 精华推荐
- 做个PCB铜钱吧
- 用于EMIF02-01OABRY或EMIF02-02OABRY的MDI BRR板
- 使用 RP40-1103.3SFR DC/DC 转换器并根据 EN55022 B 类(110Vin 双输出)进行 EMC 滤波的典型应用
- NCP133AMXADJTCGEVB:NCP133 XDFN-4 评估板 ADJ
- LT3482,快速电流监视器瞬态响应 ADP 偏置拓扑
- 使用 ADA4077-2ARZ-RL 双电源高精度放大器用于低功耗线性化 RTD 电路的典型应用电路
- MIC3203YM EV,具有 MIC3203/MIC3203-1 的评估板是迟滞降压、恒流、高亮度 LED 驱动器
- CN0248
- ESP-01S MOS开关管(代替继电器)
- 具有基准的 LT1117IST-3.3 低压差正稳压器的典型应用