Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Gdy wątek wymaga wyłącznego dostępu do kodu lub innego zasobu, żąda blokady. Jeśli to możliwe, system Windows odpowiada, dając tę blokadę wątkowi. W tym momencie nic innego w systemie nie może uzyskać dostępu do zablokowanego kodu. Dzieje się to cały czas i jest normalną częścią każdej dobrze napisanej aplikacji wielowątkowej. Mimo że określony segment kodu może mieć tylko jedną blokadę na nim jednocześnie, wiele segmentów kodu może mieć własną blokadę.
Zakleszczenie pojawia się, gdy co najmniej dwa wątki zażądały blokad na co najmniej dwóch zasobach w niezgodnej sekwencji. Załóżmy na przykład, że wątek Jeden uzyskał blokadę zasobu A, a następnie żąda dostępu do zasobu B. Tymczasem wątek Drugi nabył blokadę zasobu B, a następnie żąda dostępu do zasobu A. Żaden z wątków nie może kontynuować, dopóki blokada drugiego wątku nie zostanie wycofana, a zatem żaden z wątków nie może kontynuować.
Zakleszczenia w trybie użytkownika pojawiają się, gdy wiele wątków, zwykle z jednej aplikacji, zablokowało sobie dostęp do tego samego zasobu. Jednak wiele wątków wielu aplikacji może również zablokować sobie dostęp do zasobu globalnego/udostępnionego, takiego jak zdarzenie globalne lub semafor.
Zakleszczenia w trybie jądra pojawiają się, gdy wiele wątków (z tego samego procesu lub różnych procesów) zablokowało dostęp do tego samego zasobu jądra.
Procedura używana do debugowania zakleszczenia zależy od tego, czy zakleszczenie występuje w trybie użytkownika, czy w trybie jądra.
Debugowanie deadlocku User-Mode
Gdy zakleszczenie występuje w trybie użytkownika, wykonaj następującą procedurę, aby ją debugować:
Wydaj rozszerzenie !ntsdexts.locks . W trybie użytkownika można po prostu wpisać !locks w wierszu polecenia debugera; przyjmuje się prefiks ntsdexts .
To rozszerzenie wyświetla wszystkie sekcje krytyczne skojarzone z bieżącym procesem, wraz z identyfikatorem właściciela wątku oraz liczbą blokad dla każdej sekcji krytycznej. Jeśli sekcja krytyczna ma liczbę blokad zera, nie jest zablokowana. Użyj polecenia ~ (stan wątku), aby wyświetlić informacje o wątkach, które są właścicielem innych sekcji krytycznych.
Użyj polecenia kb (Display Stack Backtrace) dla każdego z tych wątków w celu określenia, czy oczekują na inne krytyczne sekcje.
Korzystając z danych wyjściowych tych poleceń kb, można znaleźć zakleszczenie: dwa wątki, które oczekują na blokadę posiadaną przez drugi wątek. W rzadkich przypadkach zakleszczenie może być spowodowane przez więcej niż dwa wątki trzymające blokady w cyklicznym wzorcu, ale większość zakleszczeń obejmuje tylko dwa wątki.
Oto ilustracja tej procedury. Zaczynasz od rozszerzenia !ntdexts.locks :
0:006> !locks
CritSec ftpsvc2!g_csServiceEntryLock+0 at 6833dd68
LockCount 0
RecursionCount 1
OwningThread a7
EntryCount 0
ContentionCount 0
*** Locked
CritSec isatq!AtqActiveContextList+a8 at 68629100
LockCount 2
RecursionCount 1
OwningThread a3
EntryCount 2
ContentionCount 2
*** Locked
CritSec +24e750 at 24e750
LockCount 6
RecursionCount 1
OwningThread a9
EntryCount 6
ContentionCount 6
*** Locked
Pierwsza wyświetlona sekcja krytyczna nie ma blokad i dlatego można je zignorować.
Druga sekcja krytyczna, która została wyświetlona, ma licznik blokad równy 2 i dlatego może być możliwą przyczyną zakleszczenia. Wątek będący właścicielem ma identyfikator wątku 0xA3.
Ten wątek można znaleźć, wyświetlając listę wszystkich wątków za pomocą polecenia ~ (stan wątku) i wyszukując wątek o tym identyfikatorze:
0:006> ~
0 Id: 1364.1330 Suspend: 1 Teb: 7ffdf000 Unfrozen
1 Id: 1364.17e0 Suspend: 1 Teb: 7ffde000 Unfrozen
2 Id: 1364.135c Suspend: 1 Teb: 7ffdd000 Unfrozen
3 Id: 1364.1790 Suspend: 1 Teb: 7ffdc000 Unfrozen
4 Id: 1364.a3 Suspend: 1 Teb: 7ffdb000 Unfrozen
5 Id: 1364.1278 Suspend: 1 Teb: 7ffda000 Unfrozen
. 6 Id: 1364.a9 Suspend: 1 Teb: 7ffd9000 Unfrozen
7 Id: 1364.111c Suspend: 1 Teb: 7ffd8000 Unfrozen
8 Id: 1364.1588 Suspend: 1 Teb: 7ffd7000 Unfrozen
Na tym wyświetlaczu pierwszym elementem jest wewnętrzny numer wątku debugera. Drugi element ( Id pole) zawiera dwie liczby szesnastkowe rozdzielone przecinkami dziesiętnym. Liczba przed przecinkiem dziesiętnym jest identyfikatorem procesu; liczba po przecinku dziesiętnym to identyfikator wątku. W tym przykładzie zobaczysz, że identyfikator wątku 0xA3 odpowiada numerowi wątku 4.
Następnie użyj polecenia kb (Display Stack Backtrace), aby wyświetlić stos odpowiadający numerowi wątku 4:
0:006> ~4 kb
4 id: 97.a3 Suspend: 0 Teb 7ffd9000 Unfrozen
ChildEBP RetAddr Args to Child
014cfe64 77f6cc7b 00000460 00000000 00000000 ntdll!NtWaitForSingleObject+0xb
014cfed8 77f67456 0024e750 6833adb8 0024e750 ntdll!RtlpWaitForCriticalSection+0xaa
014cfee0 6833adb8 0024e750 80000000 01f21cb8 ntdll!RtlEnterCriticalSection+0x46
014cfef4 6833ad8f 01f21cb8 000a41f0 014cff20 ftpsvc2!DereferenceUserDataAndKill+0x24
014cff04 6833324a 01f21cb8 00000000 00000079 ftpsvc2!ProcessUserAsyncIoCompletion+0x2a
014cff20 68627260 01f21e0c 00000000 00000079 ftpsvc2!ProcessAtqCompletion+0x32
014cff40 686249a5 000a41f0 00000001 686290e8 isatq!I_TimeOutContext+0x87
014cff5c 68621ea7 00000000 00000001 0000001e isatq!AtqProcessTimeoutOfRequests_33+0x4f
014cff70 68621e66 68629148 000ad1b8 686230c0 isatq!I_AtqTimeOutWorker+0x30
014cff7c 686230c0 00000000 00000001 000c000a isatq!I_AtqTimeoutCompletion+0x38
014cffb8 77f04f2c 00000000 00000001 000c000a isatq!SchedulerThread_297+0x2f
00000001 000003e6 00000000 00000001 000c000a kernel32!BaseThreadStart+0x51
Zwróć uwagę, że ten wątek ma wywołanie funkcji WaitForCriticalSection, co oznacza, że nie tylko ma blokadę, ale także czeka na kod, który jest zablokowany przez coś innego. Możemy dowiedzieć się, na której sekcji krytycznej czekamy, patrząc na pierwszy parametr wywołania waitForCriticalSection. Jest to pierwszy adres w obszarze argumenty do podprocesu: "24e750". Dlatego ten wątek czeka na sekcję krytyczną pod adresem 0x24E750. Była to trzecia sekcja krytyczna wymieniona przez rozszerzenie !locks, które użyłeś wcześniej.
Innymi słowy, wątek 4, który jest właścicielem drugiej sekcji krytycznej, czeka na trzecią sekcję krytyczną. Teraz zwróć uwagę na trzecią sekcję krytyczną, która jest również zablokowana. Wątek będący właścicielem ma identyfikator wątku 0xA9. ** Wracając do danych wyjściowych polecenia ~, które widzieliście wcześniej, zauważ, że wątek o tym identyfikatorze jest wątkiem numer 6. Wyświetl ślad wywołań stosu dla tego wątku.
0:006> ~6 kb
ChildEBP RetAddr Args to Child
0155fe38 77f6cc7b 00000414 00000000 00000000 ntdll!NtWaitForSingleObject+0xb
0155feac 77f67456 68629100 6862142e 68629100 ntdll!RtlpWaitForCriticalSection+0xaa
0155feb4 6862142e 68629100 0009f238 686222e1 ntdll!RtlEnterCriticalSection+0x46
0155fec0 686222e1 0009f25c 00000001 0009f238 isatq!ATQ_CONTEXT_LISTHEAD__RemoveFromList
0155fed0 68621412 0009f238 686213d1 0009f238 isatq!ATQ_CONTEXT__CleanupAndRelease+0x30
0155fed8 686213d1 0009f238 00000001 01f26bcc isatq!AtqpReuseOrFreeContext+0x3f
0155fee8 683331f7 0009f238 00000001 01f26bf0 isatq!AtqFreeContext+0x36
0155fefc 6833984b ffffffff 00000000 00000000 ftpsvc2!ASYNC_IO_CONNECTION__SetNewSocket
0155ff18 6833adcd 77f05154 01f26a58 00000000 ftpsvc2!USER_DATA__Cleanup+0x47
0155ff28 6833ad8f 01f26a58 000a3410 0155ff54 ftpsvc2!DereferenceUserDataAndKill+0x39
0155ff38 6833324a 01f26a58 00000000 00000040 ftpsvc2!ProcessUserAsyncIoCompletion+0x2a
0155ff54 686211eb 01f26bac 00000000 00000040 ftpsvc2!ProcessAtqCompletion+0x32
0155ff88 68622676 000a3464 00000000 000a3414 isatq!AtqpProcessContext+0xa7
0155ffb8 77f04f2c abcdef01 ffffffff 000ad1b0 isatq!AtqPoolThread+0x32
0155ffec 00000000 68622644 abcdef01 00000000 kernel32!BaseThreadStart+0x51
Ten wątek również oczekuje na zwolnienie sekcji krytycznej. W takim przypadku oczekuje na sekcję krytyczną w 0x68629100. Była to druga sekcja krytyczna na liście wygenerowana wcześniej przez rozszerzenie !locks .
To jest impas. Wątek 4, który posiada drugą sekcję krytyczną, czeka na trzecią sekcję krytyczną. Wątek 6, który posiada trzecią sekcję krytyczną, oczekuje na drugą sekcję krytyczną.
Po potwierdzeniu charakteru tego zakleszczenia można użyć zwykłych technik debugowania do analizy wątków 4 i 6.
Debugowanie zakleszczenia Kernel-Mode
Istnieje kilka rozszerzeń debugera, które są przydatne do debugowania zakleszczeń w trybie jądra:
Rozszerzenie !kdexts.locks wyświetla informacje o wszystkich blokadach przechowywanych w zasobach jądra i wątkach trzymających te blokady. (W trybie jądra można po prostu wpisać !locks w wierszu debugera; przyjmuje się prefiks kdexts ).
Rozszerzenie !qlocks wyświetla stan wszystkich blokad spinowych w kolejce.
Rozszerzenie !wdfkd.wdfspinlock wyświetla informacje o obiekcie blokady spin (spin-lock) z Kernel-Mode Driver Framework (KMDF).
Rozszerzenie !deadlock jest używane w połączeniu z Weryfikatorem Sterowników w celu wykrywania niespójnego użycia blokad w kodzie, które mogą powodować zakleszczenia.
Kiedy występuje zakleszczenie w trybie jądrowym, użyj rozszerzenia !kdexts.locks, aby wyświetlić listę wszystkich blokad obecnie uzyskanych przez wątki.
Zazwyczaj można wskazać zakleszczenie, wyszukując jeden nieużytkowany wątek, który przechowuje wyłączną blokadę zasobu wymaganego przez wątek wykonujący. Większość blokad jest współużytkowanych.