spin_lock(自旋锁)的使用以及在单核cpu与多核cpu里面的实现区别

319 篇文章 29 订阅

spinlock_t lock1;

spin_lock(&lock1);

临界区代码

spin_unlock(&lock1);

还有其他一些自旋锁操作: spin_lock_irqsave不仅获得自旋锁,还停用本地CPU的中断,而spin_lock_bh则停用softIRQ(软中断)。用这两个操作获得的自旋锁必须用对应的接口释放,分别是spin_unlock_irqsave和spin_unlock_bh。

spin_lock的初始化
kernel/include/linux/spinlock.h

在这里插入图片描述
kernel/kernel/locking/spinlock_debug.c
在这里插入图片描述
在这里插入图片描述

自旋锁在Linux内核源代码里的定义如下:
kernel/include/linux/spinlock_types.h里面定义了
在这里插入图片描述
在这里插入图片描述
arch_spinlock_t顾名思义是和cpu架构相关的,以arm平台为例,定义在下面文件里面
/kernel/arch/arm/include/asm/spinlock_types.h
在这里插入图片描述上面的ARMEB表示是大端 Big-Edian

spinlock_t与raw_spinlock_t只是arch_spinlock_t的包装,这样子提高了扩展性,架构相关的内嵌在最里面一层。

在RT-linux的patch中修改了spinlock_t,允许自旋锁睡眠。标准Linux kernel在spinlock、irq上下文方面无法抢占,因此高优先级任务被唤醒到得以执行的时间并不能完全确定。同时,Linux kernel本身也不处理优先级反转。

在这里插入图片描述

spin_lock的实现,分为单核和多核cpu的情况

在这里插入图片描述
在这里插入图片描述
在kernel目录下搜索_raw_spin_lock发现如下结果:
在这里插入图片描述

**

单核CPU的情况:

kernel/include/linux/spinlock_api_up.h
在这里插入图片描述
在这里插入图片描述
kernel/include/linux/compiler.h
在这里插入图片描述

**
由此可见:在启用了内核抢占的单处理器内核中, spin_lock(基本上)等价于preempt_disable,而spin_unlock则等价于preempt_enable

在某个线程里添加如下语句:
preempt_disable();
while(1);
然后过会就会打印如下log:
[ 28.548865] BUG: soft lockup - CPU#1 stuck for 22s! [us kthread:1106]
[ 28.555378] Modules linked in:
[ 28.558566]
[ 28.560173] Pid: 1106, comm: us kthread
[ 28.564951] CPU: 1 Not tainted (3.0.35-00007-g60f7071-dirty #153)
[ 28.571528] PC is at ks103_ultrasonic_thread+0x18/0x1c
[ 28.576750] LR is at kthread+0x80/0x88
[ 28.580566] pc : [] lr : [] psr: 60000013
[ 28.580571] sp : d441bfc8 ip : c08ba7d0 fp : 00000000
[ 28.592249] r10: 00000000 r9 : 00000000 r8 : 00000000
[ 28.597599] r7 : 00000013 r6 : c035706c r5 : 00000000 r4 : d402fe94
[ 28.604202] r3 : d441a000 r2 : 00000001 r1 : d4044560 r0 : 00000000
[ 28.610854] Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
[ 28.618287] Control: 10c53c7d Table: 1000404a DAC: 00000015
[ 28.624095]
[ 28.624098] PC: 0xc0357004:
[ 28.628502] 7004 ebffffaa e58d0004 eaffffd7 e59f1034 e59f0040 eb0c4e8a e3a00003 ebffffa3
[ 28.637449] 7024 e59f2034 e59f101c e1a03000 e59f002c e3530000 e58d3004 01a02000 e59f0020
[ 28.646433] 7044 eb0c4e7f eaffffc8 c06a73cc c07e9fdc c07e9ffc c07ea018 c07ea038 c07e9fc4
[ 28.655417] 7064 c07e9fd4 c07ea058 e1a0200d e3c23d7f e3c3303f e5932004 e2822001 e5832004
[ 28.664414] 7084 eafffffe e92d4008 e3a00000 e59f1008 ebfe1658 e3a00000 e8bd8008 c0972128
[ 28.673356] 70a4 e92d4008 e3a00000 e59f100c ebfe2136 e3500000 13e0000f e8bd8008 c0972128
[ 28.682303] 70c4 e3a03001 e3443004 e1510003 e92d4010 e1a04002 0a000007 e3a03002 e3443004
[ 28.691325] 70e4 e1510003 0a000012 e59f00b4 eb0c4e54 e3e00015 e8bd8010 e59f00a8 eb0c6694

**

多核CPU的情况:

**
kernel/include/linux/spinlock_api_smp.h
在这里插入图片描述
在这里插入图片描述
kernel/include/linux/lockdep.h
在这里插入图片描述
LOCK_CONTENDED()里的第一个参数作为第三个函数的参数
所以去看do_raw_spin_lock()的实现
kernel/include/linux/spinlock.h
在这里插入图片描述

kernel/arch/arm/include/asm/spinlock.h
在这里插入图片描述
在这里插入图片描述
对上面的代码解析如下:
核心是ldrex指令和strex指令
ex表示exclusive,专用的。
为了解决多核情况下的锁竞争问题,arm引入了exclusive操作,并添加了相应的指令
exclusive的操作的核心,就是会将锁,用一个状态机进行维护,该状态机有2种状态,open状态和exclusive状态。要想成功的对锁进行上锁,状态必须要从exclusive状态切换到open状态,其他状态,都是失败的。
为了支持exclusive操作,在A64,新增了LDXR和STXR指令。
在A32和T32下,也加入LDREX和STREX指令来支持

%0是lockval %1是newval %2是tmp %3是lock->slock 4%是1<<TICKET_SHIFT
1:为了提高性能

2.涉及ldrex指令,LDREX , [] 是base register,保存memory的address,LDREX指令从base register中获取memory address,并且将memory的内容加载到(destination register)中。这些操作和ldr的操作是一样的,那么如何体现exclusive呢?其实,在执行这条指令的时候,还放出两条“狗”来负责观察特定地址的访问(就是保存在[]中的地址了),这两条狗一条叫做local monitor,一条叫做global monitor。

本句中表示lockval = lock->slock (如果lock->slock没有被其他处理器独占,则标记当前执行处理器对lock->slock地址的独占访问;否则不影响)

  1. newval = lockval + (1 << TICKET_SHIFT)

4.涉及strex指令,STREX , , []
和LDREX指令类似,是base register,保存memory的address,STREX指令从base register中获取memory address,并且将 (source register)中的内容加载到该memory中。这里的保存了memeory 更新成功或者失败的结果,0表示memory更新成功,1表示失败。STREX指令是否能成功执行是和local monitor和global monitor的状态相关的。对于Non-shareable memory(该memory不是多个CPU之间共享的,只会被一个CPU访问),只需要放出该CPU的local monitor这条狗就OK了,

本句表示 strex tmp, newval, [&lock->slock] (如果当前执行处理器没有独占lock->slock地址的访问,不进行存储,返回1给temp;如果当前处理器已经独占lock->slock内存访问,则对内存进行写,返回0给temp,清除独占标记) lock->tickets.next = lock->tickets.next + 1

5.检查是否写入成功 lockval.tickets.next

6.初始化时lock->tickets.owner、lock->tickets.next都为0,假设第一次执行arch_spin_lock,lockval = *lock,lock->tickets.next++,lockval.tickets.next 等于 lockval.tickets.owner,获取到自旋锁;自旋锁未释放,第二次执行的时候,lock->tickets.owner = 0, lock->tickets.next = 1,拷贝到lockval后,lockval.tickets.next != lockval.tickets.owner,会执行wfe等待被自旋锁释放被唤醒,自旋锁释放时会执行 lock->tickets.owner++,lockval.tickets.owner重新赋值

7.暂时中断挂起执行。如果当前spin lock的状态是locked,那么调用wfe进入等待状态。

8.其他的CPU唤醒了本cpu的执行,说明owner发生了变化,该新的own赋给lockval,然后继续判断spin lock的状态,也就是回到step 6

9.memory barrier的操作

(209条消息) spin_lock(自旋锁)的使用以及在单核cpu与多核cpu里面的实现区别_wusheng1989的博客-CSDN博客_spinlock 单核

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、付费专栏及课程。

余额充值