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地址的独占访问;否则不影响)
- 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 单核