(68)自旋锁 , cmpxchg8b 指令
一、臨界區(qū)和自旋鎖的對比
上次課我們學(xué)習(xí)了臨界區(qū),并自己實現(xiàn)了一個。臨界區(qū)是通過線程切換的方式來等待的,自旋鎖則使用循環(huán)代替了線程切換,在多核環(huán)境下,使用自旋鎖可以提高效率。
用一個例子來解釋臨界區(qū)和自旋鎖的區(qū)別。只有一個廁所,有一個人進去了。
臨界區(qū)就是外面的人過來看一眼發(fā)現(xiàn)沒位子,就回家睡覺了,睡醒了再回來看看有沒有位子,重復(fù)這樣的步驟;
自旋鎖就是外面人一看沒位置,他就在原地打轉(zhuǎn),一有位子馬上就進去了。
自旋鎖的執(zhí)行流程真的是在“旋轉(zhuǎn)”,我覺得這個名字起得太貼切了。下面我們來看多核模式下自旋鎖的實現(xiàn)。
二、KiAcquireSpinLock , KiReleaseSpinLock
下面是多核自旋鎖實現(xiàn)。
解釋一下 bts 指令,它是把 [ecx] 的第 0 位的值放到 CF,然后給第0 位置1. jb 指令看 CF=1就跳轉(zhuǎn)。也就是說,如果原來第0位是1,就retn,進入臨界區(qū)了;如果原來是1,那么 jb 跳轉(zhuǎn),開始“自旋”,直到 [ecx] 0位變成1,重新執(zhí)行 KiAcquireSpinLock 。
自旋鎖避免了線程切換,通過 pause 指令給CPU降溫,非常漂亮的做法,但是單核模式下就不合適了。
.text:004699D0 @KiAcquireSpinLock@4 proc near ; CODE XREF: InbvAcquireLock()+2D↑p .text:004699D0 ; KdpPortLock()+5↑j ... .text:004699D0 lock bts dword ptr [ecx], 0 .text:004699D5 jb short loc_4699D8 .text:004699D7 retn .text:004699D8 ; --------------------------------------------------------------------------- .text:004699D8 .text:004699D8 loc_4699D8: ; CODE XREF: KiAcquireSpinLock(x)+5↑j .text:004699D8 ; KiAcquireSpinLock(x)+12↓j .text:004699D8 test dword ptr [ecx], 1 .text:004699DE jz short @KiAcquireSpinLock@4 ; KiAcquireSpinLock(x) .text:004699E0 pause .text:004699E2 jmp short loc_4699D8 .text:004699E2 @KiAcquireSpinLock@4 endp .text:004699E2 .text:004699E2 ; --------------------------------------------------------------------------- .text:004699E4 align 10h .text:004699F0 ; Exported entry 50. KiReleaseSpinLock .text:004699F0 .text:004699F0 ; =============== S U B R O U T I N E ======================================= .text:004699F0 .text:004699F0 .text:004699F0 ; __fastcall KiReleaseSpinLock(x) .text:004699F0 public @KiReleaseSpinLock@4 .text:004699F0 @KiReleaseSpinLock@4 proc near ; CODE XREF: InbvReleaseLock()+E↑p .text:004699F0 ; KdpPortUnlock()+5↑j ... .text:004699F0 mov byte ptr [ecx], 0 .text:004699F3 retn .text:004699F3 @KiReleaseSpinLock@4 endp三、在多核環(huán)境下,如何保證對一個高并發(fā)的內(nèi)核函數(shù)進行HOOK而不會出錯?寫出你的代碼。
這個問題的關(guān)鍵是,hook 后一般是 e8 / e9 后跟4字節(jié),總共5字節(jié),但沒辦法一次性改5個字節(jié),可能改了第一個字節(jié),正要改后4個字節(jié)時,別的線程進來了,就會出錯。
我這介紹三種辦法。
- 短跳中轉(zhuǎn)
- 中斷門
- 找一條一次性修改8字節(jié)的指令
短跳中轉(zhuǎn)是比較常用的,修改前2字節(jié)跳到某個長跳的方式,不多做介紹。
中斷門也是只用改兩個字節(jié),需要先構(gòu)造中斷門,也不介紹。
本文重點介紹第三種,我以前沒用過的方式,這個指令就是 cmpxchg8b .
cmpxchg8b 指令
cmpxchg8b mem64 指令的工作如下:
比較 mem64 和 EDX:EAX
如果相等,那么把 ECX:EBX 存儲到 mem64
如果不相等,那么把 mem64 存儲到 EDX:EAX
我們要一次性改8字節(jié)內(nèi)存,用的是相等的情況,先把要寫入的內(nèi)容放到 ECX:EBX ,然后調(diào)用 cmpxchg8b 指令即可。
總結(jié)
以上是生活随笔為你收集整理的(68)自旋锁 , cmpxchg8b 指令的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (67)多核同步,lock 总线锁 ,自
- 下一篇: (69)番外 —— 编写一个简易的反调试