【note】自旋锁
多线程和多处理器的关系:
1. 单核cpu: DPC/Dispatch级别的中断导致线程切换
2. 多核cpu:
1)某个cpu的中断导致线程的切换
2)多个cpu可以同时使得多个线程运行
在单核cpu上,只需要关闭中断就可以保证原子操作。。。。。原子操作完成后打开中断即可。
但是在多核cpu上,关闭中断不能保证其他的cpu不进行资源的操作,但是多个cpu共享一条总线,所以只要锁住总线便能保证原子操作。自旋锁的原理便是如此。
自旋锁和其他的同步对象event的区别:
线程使用其他的同步对象进行同步操作时,如果遇到等待,那么内核便会切换任务,让cpu运行其他的线程。。。。。。但是这个切换包括(原有线程上下文的保存,调度算法的执行,cache失效,cr3的修改。。。)如果同步操作仅仅有两条指令,那么这个代价是非常昂贵的,还不如让等待线程空转自旋。不断测试自旋锁的状态。。。
Q1:如何保证对自旋锁的状态操作是原子的?
A1:这个只能通过硬件来实现。因为多个cpu共享同一条总线,所以锁住总线便能保证对自旋锁的操作时原子的。
Q2:获得自旋锁时,为什么要提升中断请求级别(IRQL)到DISPATCH_LEVEL?
提升到Dispatch级别时为了能够禁止软中断,保证持有该锁的线程能够尽快使用完,然后释放。因为其他的cpu在空转。尽少的减少cpu资源的浪费。
----------------------------------------转载:看雪-------------------------------------------------------------------
用IDA分析双核系统的内核文件ntkrnlpa.exe,关于自旋锁操作的两个基本函数是KiAcquireSpinLock和KiReleaseSpinLock,其它几个类似。
.text:004689C0 KiAcquireSpinLock proc near ; CODE XREF:
sub_416FEE+2D p
.text:004689C0 ; sub_4206C0+5 j ...
.text:004689C0 lock bts dword ptr [ecx], 0
.text:004689C5 jb short loc_4689C8
.text:004689C7 retn
.text:004689C8 ; ---------------------------------------------------------------------------
.text:004689C8
.text:004689C8 loc_4689C8: ; CODE XREF: KiAcquireSpinLock+5 j
.text:004689C8 ; KiAcquireSpinLock+12 j
.text:004689C8 test dword ptr [ecx], 1
.text:004689CE jz short KiAcquireSpinLock
.text:004689D0 pause
.text:004689D2 jmp short loc_4689C8
.text:004689D2 KiAcquireSpinLock endp
代码比较简单,还原成源码是这样子的(偷懒用了F5):
void __fastcall KiAcquireSpinLock(int _ECX)
{
while ( 1 )
{
__asm { lock bts dword ptr [ecx], 0 }
if ( !_CF )
break;
while ( *(_DWORD *)_ECX & 1 )
__asm { pause }//应是rep nop,IDA将其翻译成pause
}
}
fastcall方式调用,参数KSPIN_LOCK在ECX中,可以看到是一个死循环,先测试其是否置位,若否,则CF将置0,并将ECX置位,即获取锁的操作成功;若是,即锁已被占有,则一直对其进行测试并进入空转状态,这和前面分析的完全一致,只是代码似乎更精炼了一点,毕竟是实用的玩意嘛。
再来看看释放时:
.text:004689E0 public KiReleaseSpinLock
.text:004689E0 KiReleaseSpinLock proc near ; CODE XREF: sub_41702E+E p
.text:004689E0 ; sub_4206D0+5 j ...
.text:004689E0 mov byte ptr [ecx], 0
.text:004689E3 retn
.text:004689E3 KiReleaseSpinLock endp
这个再清楚不过了,直接设置为0就代表了将其释放,此时那些如虎狼般疯狂空转的其它处理器将马上获知这一信息,于是,下一个获取、释放的过程开始了。这就是最基本的自旋锁,其它一些自旋锁形式是对这种基本形式的扩充。比如排队自旋锁,是为了解决多处理器竞争时的无序状态等等,不多说了。
现在对自旋锁可谓真的是明明白白了,之前我犯的错误就是以为用了自旋锁就能保证多核同步,其实不是的,用自旋锁来保证多核同步的前提是大家都要用这个锁。若当前处理器已占有自旋锁,只有别的处理器也来请求这个锁时,才会进入空转,不进行别的操作,这时你的操作将不会受到干扰。但是假如某个需要互斥的操作只有你这个线程才做而别人根本不去做(以iceboy的安全实现Inline Hook为例,请求锁、修改代码、释放锁的过程只有这个线程才会做,别的处理器上的
线程如果要执行这里还是照样执行,人家又不用修改),所以人家不请求锁时还是该干嘛干嘛啊,自己在那儿自旋只是一厢情愿。所以MJ说“别人都不跟你旋,你自己旋个头啊”,经典经典…….