Debugging/Windows

Thread State Transition

haewon83 2023. 6. 5. 19:47

Windows OS의 Scheduling Unit은 Thread로 이 Thread는 생성되어서 종료될 때까지 여러 State를 가지게 됩니다.

Thread의 State가 어떻게 변화되고, 각각의 State는 어떤 의미를 갖는지 살펴보겠습니다.

 

 

Before After Description Debugger Command
Init   Thread가 처음 만들어진 상태  
Init Terminated Init 상태에서 갑자기 종료되는 경우  
Init Deferred Ready Init 상태의 Thread가 Ready 상태로 가기 전 잠시 머무는 단  
Deferred Ready Ready Ready Queue로 들어간 상태 !ready
Deferred Ready Standby 바로 다음 Scheduling 대상인 상태, Processor 당 하나  
Ready Running Dispatcher에 의해서 Scheduling 된 상태 !running; !thread; ~X(Processor No.)
Ready Deferred Ready 할당될 Processor가 바뀌는 경우(Affinity 때문) Deferred Ready 상태로 전환  
Ready Standby 바로 다음 Scheduling 대상인 상태, Processor 당 하나  
Standby Running Dispatcher에 의해서 Scheduling 된 상태 !running; !thread; ~X(Processor No.)
Standby Deferred Ready 할당될 Processor가 바뀌는 경우(Affinity 때문) Deferred Ready 상태로 전환  
Running Waiting 자발적으로 Quantum 시간을 포기한 상태 !stacks
Running Deferred Ready Quantum 시간을 다 사용한 Thread는 Deferred Ready로  
Running Terminated    
Waiting Running 조건(Event 등)이 맞아서 Signal을 받거나, Timeout이 발생하면 Waiting에서 Running으로 전환 !running; !thread; ~X(Processor No.)
Waiting Deferred Ready Waiting 상태의 Thread가 우선 순위가 높지 않으면 Deferred Ready로  
Waiting Transition Scheduling이 될 때 Context Switching이 필요한데, Thread의 Kernel Stack이 Paged-out되어 있는 경우 바로 Context Switching을 할 수 없기 때문에 Transition 상태로 전환
Kernel Stack이 Page-in 되면, Deferred Ready로 전
 

 

1. Thread State

OS에서 Scheduling 단위는 Thread

어떤 Thread가 어느 Processor에서 실행되는지를 결정하기 위해서 Windows는 KThread, KProcess, KPRCB 구조체를 사용

 

!thread 명령어로 Thread 상태 조회

!thread 
## 아래 예제는 0번 Processor에서 실행 중인 Thread
0: kd> !thread
THREAD fffff8005f162400  Cid 0000.0000  Teb: 0000000000000000 Win32Thread: 0000000000000000 RUNNING on processor 0
Not impersonating
DeviceMap                 ffffde84b7e133c0
Owning Process            fffff8005f15f9c0       Image:         Idle
Attached Process          ffff930173e85040       Image:         System
Wait Start TickCount      0              Ticks: 2526 (0:00:00:39.468)
Context Switch Count      7312           IdealProcessor: 0             
UserTime                  00:00:00.000
KernelTime                00:00:35.562
Win32 Start Address nt!KiIdleLoop (0xfffff8005edbe350)
Stack Init fffff80061065650 Current fffff800610655e0
Base fffff80061066000 Limit fffff8006105f000 Call 0000000000000000
Priority 0 BasePriority 0 PriorityDecrement 0 IoPriority 0 PagePriority 5
Child-SP          RetAddr               : Args to Child                                                           : Call Site
fffff800`61065160 fffff800`5ecb91ab     : fffff800`5eee7b20 00000017`28d1d866 00000000`00000000 00000000`0000000c : nt!PpmIdleGuestExecute+0x1c
fffff800`610651a0 fffff800`5ecb895f     : 00000000`00000000 00000000`00000002 00000000`00000000 00000000`00000008 : nt!PpmIdleExecuteTransition+0x6bb
fffff800`610654c0 fffff800`5edbe37c     : 00000000`00000000 fffff800`5db05180 fffff800`5f162400 ffff9301`78196080 : nt!PoIdle+0x33f
fffff800`61065620 00000000`00000000     : fffff800`61066000 fffff800`6105f000 00000000`00000000 00000000`00000000 : nt!KiIdleLoop+0x2c

## Processor 변경하여 Thread 조회
0: kd> ~1

1: kd> !thread
THREAD ffff800107bb0200  Cid 0000.0000  Teb: 0000000000000000 Win32Thread: 0000000000000000 RUNNING on processor 1
Not impersonating
DeviceMap                 ffffde84b7e133c0
Owning Process            fffff8005f15f9c0       Image:         Idle
Attached Process          ffff930173e85040       Image:         System
Wait Start TickCount      0              Ticks: 2526 (0:00:00:39.468)
Context Switch Count      6799           IdealProcessor: 1             
UserTime                  00:00:00.000
KernelTime                00:00:30.750
Win32 Start Address nt!KiIdleLoop (0xfffff8005edbe350)
Stack Init ffffcc015e62f650 Current ffffcc015e62f5e0
Base ffffcc015e630000 Limit ffffcc015e629000 Call 0000000000000000
Priority 0 BasePriority 0 PriorityDecrement 0 IoPriority 0 PagePriority 0
Child-SP          RetAddr               : Args to Child                                                           : Call Site
ffffcc01`5e62f160 fffff800`5ecb91ab     : fffff800`5eee7b20 00000017`23541222 00000000`00000000 00000000`00000050 : nt!PpmIdleGuestExecute+0x1c
ffffcc01`5e62f1a0 fffff800`5ecb895f     : 00000000`00000000 00000000`00000002 ffff9301`74c30010 00000000`00000001 : nt!PpmIdleExecuteTransition+0x6bb
ffffcc01`5e62f4c0 fffff800`5edbe37c     : 00000000`00000000 ffff8001`07ba0180 ffff8001`07bb0200 ffff9301`78196080 : nt!PoIdle+0x33f
ffffcc01`5e62f620 00000000`00000000     : ffffcc01`5e630000 ffffcc01`5e629000 00000000`00000000 00000000`00000000 : nt!KiIdleLoop+0x2c

 

Thread의 State 정보는 KThread 구조체의 State 필드 값을 이용

KThread.state 
1: kd> dt nt!_KTHREAD fffff8005f162400 state
   +0x184 State : 0x2 ''

1: kd> dt nt!_KTHREAD ffff800107bb0200 state
   +0x184 State : 0x2 ''

 

State 정의

## Thread의 State 값은 Dispatcher Database에 보관되며 Dispatcher는 이 값을 이용하여 어느 Thread가 다음에 실행되어야 하는지를 결정

Value State
0 Init
1 Ready
2 Running
3 Standby
4 Terminate
5 Waiting
6 Transition
7 Deferred Ready

 

개별 Processor 별로 실행 중인 Thread확인할 수도 있지만, !running command이용하면 번에 전체 Processor실행 중인 Thread 정보 출력

!running -it 
1: kd> !running -it

System Processors:  (00000000000000ff)
  Idle Processors:  (00000000000000ff)

All processors idle.

       Prcbs             Current         (pri) Next            (pri) Idle
  0    fffff8005db05180  fffff8005f162400 ( 0)                       fffff8005f162400  ................

 # Child-SP          RetAddr               Call Site
00 fffff800`61065160 fffff800`5ecb91ab     nt!PpmIdleGuestExecute+0x1c
01 fffff800`610651a0 fffff800`5ecb895f     nt!PpmIdleExecuteTransition+0x6bb
02 fffff800`610654c0 fffff800`5edbe37c     nt!PoIdle+0x33f
03 fffff800`61065620 00000000`00000000     nt!KiIdleLoop+0x2c

  1    ffff800107ba0180  ffff800107bb0200 ( 0)                       ffff800107bb0200  ................

 # Child-SP          RetAddr               Call Site
00 ffffcc01`5e62f160 fffff800`5ecb91ab     nt!PpmIdleGuestExecute+0x1c
01 ffffcc01`5e62f1a0 fffff800`5ecb895f     nt!PpmIdleExecuteTransition+0x6bb
02 ffffcc01`5e62f4c0 fffff800`5edbe37c     nt!PoIdle+0x33f
03 ffffcc01`5e62f620 00000000`00000000     nt!KiIdleLoop+0x2c

  2    ffff800107c66180  ffff800107c76200 ( 0)                       ffff800107c76200  ................

 # Child-SP          RetAddr               Call Site
00 ffffcc01`5e63f160 fffff800`5ecb91ab     nt!PpmIdleGuestExecute+0x1c
01 ffffcc01`5e63f1a0 fffff800`5ecb895f     nt!PpmIdleExecuteTransition+0x6bb
02 ffffcc01`5e63f4c0 fffff800`5edbe37c     nt!PoIdle+0x33f
03 ffffcc01`5e63f620 00000000`00000000     nt!KiIdleLoop+0x2c

  3    ffff800107d0e180  ffff800107d1e200 ( 0)                       ffff800107d1e200  ................

 # Child-SP          RetAddr               Call Site
00 ffffcc01`5e64f298 fffff80f`88522a40     nt!DbgBreakPointWithStatus
01 ffffcc01`5e64f2a0 fffff80f`88522911     kdnic!TXTransmitQueuedSends+0x120
02 ffffcc01`5e64f2e0 fffff800`5ecf09e9     kdnic!TXSendCompleteDpc+0x141
03 ffffcc01`5e64f320 fffff800`5ecf1937     nt!KiProcessExpiredTimerList+0x159
04 ffffcc01`5e64f410 fffff800`5edbe3aa     nt!KiRetireDpcList+0x4a7
05 ffffcc01`5e64f620 00000000`00000000     nt!KiIdleLoop+0x5a

  4    ffff800107e00180  ffff800107e10200 ( 0)                       ffff800107e10200  ................

 # Child-SP          RetAddr               Call Site
00 ffffcc01`5e65f160 fffff800`5ecb91ab     nt!PpmIdleGuestExecute+0x1c
01 ffffcc01`5e65f1a0 fffff800`5ecb895f     nt!PpmIdleExecuteTransition+0x6bb
02 ffffcc01`5e65f4c0 fffff800`5edbe37c     nt!PoIdle+0x33f
03 ffffcc01`5e65f620 00000000`00000000     nt!KiIdleLoop+0x2c

  5    ffff800107e65180  ffff800107e75200 ( 0)                       ffff800107e75200  ................

 # Child-SP          RetAddr               Call Site
00 ffffcc01`5e66f160 fffff800`5ecb91ab     nt!PpmIdleGuestExecute+0x1c
01 ffffcc01`5e66f1a0 fffff800`5ecb895f     nt!PpmIdleExecuteTransition+0x6bb
02 ffffcc01`5e66f4c0 fffff800`5edbe37c     nt!PoIdle+0x33f
03 ffffcc01`5e66f620 00000000`00000000     nt!KiIdleLoop+0x2c

  6    ffff800107f0d180  ffff800107f1d200 ( 0)                       ffff800107f1d200  ................

 # Child-SP          RetAddr               Call Site
00 ffffcc01`5e67f160 fffff800`5ecb91ab     nt!PpmIdleGuestExecute+0x1c
01 ffffcc01`5e67f1a0 fffff800`5ecb895f     nt!PpmIdleExecuteTransition+0x6bb
02 ffffcc01`5e67f4c0 fffff800`5edbe37c     nt!PoIdle+0x33f
03 ffffcc01`5e67f620 00000000`00000000     nt!KiIdleLoop+0x2c

  7    ffff800108000180  ffff800108010200 ( 0)                       ffff800108010200  ................

 # Child-SP          RetAddr               Call Site
00 ffffcc01`5e68f160 fffff800`5ecb91ab     nt!PpmIdleGuestExecute+0x1c
01 ffffcc01`5e68f1a0 fffff800`5ecb895f     nt!PpmIdleExecuteTransition+0x6bb
02 ffffcc01`5e68f4c0 fffff800`5edbe37c     nt!PoIdle+0x33f
03 ffffcc01`5e68f620 00000000`00000000     nt!KiIdleLoop+0x2c

 

!running command는 KPRCB 구조체에서 CurrentThread와 NextThread 필드를 이용하여 출력

KPRCB 
1: kd> dt nt!_KPRCB fffff8005db05180 CurrentThread
   +0x008 CurrentThread : 0xfffff800`5f162400 _KTHREAD

1: kd> dt nt!_KPRCB fffff8005db05180 NextThread
   +0x010 NextThread : (null) 

 

2. Thread Priority

Thread는 얼마나 CPU를 획득해서 사용할 수 있는지를 Priority Level로 차이를 줄 수 있음

아래는 Windows Thread가 가질 수 있는 Priority Level(32개, 0~31)

더 높은 Priority Level이면, 더 많은 Quantum을 할당받을 가능성이 높아짐

또한, Priority Level 16~31은 Real-Time Level로 Real-Time Level은 Priority Level의 변경없이 설정된 Priority Level로 Code가 끝날 때까지 유지되는 반면

Variable Level인 1~15는 Thread Starvation을 방지하기 위해서 Priority Level을 조정해가면서 Quantum 을 할당

 

 

Thread는 Base Priority와 Current Priority 값을 가지며, Base Priority는 Thread 생성 시점에 설정되고 Current Priority는 실행 시간 동안 설정

Current Priority는 Priority 값을 Application이 변경하거나, OS가 Thread를 Boost 하기 위해서 Priority 값을 상승하는 경우가 있음

 

!thread로 Thread 조회 시 Base Priority와 Current Priority 확인

Current Priority 값은 KThread 구조체의 Priority 필드를 통해서 확인 가능하며, Base Priority는 Thread가 속한 Process의 KProcess 구조체에 있는 BasePriority 필드에서 상속

Thread Priority 
3: kd> !thread
THREAD ffff930178b3a080  Cid 18d4.1a78  Teb: 000000841acd7000 Win32Thread: 0000000000000000 RUNNING on processor 3
IRP List:
    ffff930178173920: (0006,03a0) Flags: 00020043  Mdl: ffff930177bff550
Not impersonating
DeviceMap                 ffffde84b93a5d40
Owning Process            ffff930178999080       Image:         ServerManager.exe
Attached Process          N/A            Image:         N/A
Wait Start TickCount      4116           Ticks: 0
Context Switch Count      98             IdealProcessor: 3             
UserTime                  00:00:00.031
KernelTime                00:00:00.000
Win32 Start Address 0x00007ff8d1cac150
Stack Init ffffcc016139f650 Current ffffcc016139e7d0
Base ffffcc01613a0000 Limit ffffcc0161399000 Call 0000000000000000
Priority 12 BasePriority 8 PriorityDecrement 2 IoPriority 2 PagePriority 5 ### <--
Child-SP          RetAddr               : Args to Child                                                           : Call Site
ffffcc01`6139eb80 fffff80f`8716411e     : ffffcc01`6139eff0 ffff9301`78173920 00000000`00008000 00000000`00000000 : Ntfs!NtfsNonCachedIo+0x458
ffffcc01`6139ee10 fffff80f`87162440     : ffffcc01`6139f000 ffff9301`78173920 ffffcc01`6139f000 ffff9301`78cf4b28 : Ntfs!NtfsCommonRead+0x1abe
ffffcc01`6139efc0 fffff800`5ecf1469     : ffff9301`789c1ba0 ffff9301`78173920 ffff9301`78173920 ffff9301`748b28d0 : Ntfs!NtfsFsdRead+0x210
ffffcc01`6139f080 fffff80f`862f6219     : 00000000`00000000 ffffcc01`6139f150 ffff9301`78173920 ffffcc01`6139f160 : nt!IofCallDriver+0x59
ffffcc01`6139f0c0 fffff80f`862f4a36     : ffffcc01`6139f150 ffff9301`77bff460 ffffd100`044af440 00000000`00000000 : FLTMGR!FltpLegacyProcessingAfterPreCallbacksCompleted+0x289
ffffcc01`6139f130 fffff800`5ecf1469     : ffff9301`78173920 fffff800`5ed23bdb ffff9301`78b854a0 00000000`00000000 : FLTMGR!FltpDispatch+0xb6
ffffcc01`6139f190 fffff800`5ed22ca0     : ffff9301`748b28d0 ffff9301`78173920 ffff9301`77bff490 ffff9301`77bff550 : nt!IofCallDriver+0x59
ffffcc01`6139f1d0 fffff800`5ec4b2d9     : ffff9301`77bff440 ffff9301`77bff460 ffff9301`77bff4a0 ffff9301`77bff490 : nt!IoPageReadEx+0x188
ffffcc01`6139f220 fffff800`5ec82c4d     : 00000000`00000003 ffffcc01`6139f2b0 ffffcc01`6139f418 ffffde6f`1ffe3440 : nt!MiIssueHardFaultIo+0xc1
ffffcc01`6139f270 fffff800`5ec99c41     : ffff8000`00000000 00000000`c0033333 00007ff8`d1038f70 00000000`00000000 : nt!MiIssueHardFault+0x3ed
ffffcc01`6139f320 fffff800`5edc84c9     : ffff9301`78b3a080 00000000`00000000 00000000`00000000 ffff9301`78c0e660 : nt!MmAccessFault+0x2c1
ffffcc01`6139f4c0 00007ff8`d1cf614d     : 00000000`00000000 00007ff8`00000000 00000000`00000000 00000000`00000000 : nt!KiPageFault+0x349 (TrapFrame @ ffffcc01`6139f4c0)
00000084`1bcfe370 00000000`00000000     : 00007ff8`00000000 00000000`00000000 00000000`00000000 00000000`001f5f13 : 0x00007ff8`d1cf614d
 
3: kd> dt nt!_KTHREAD ffff930178b3a080 Priority
   +0x0c3 Priority : 12 ''

3: kd> dt nt!_KPROCESS ffff930178999080 BasePriority
   +0x1bc BasePriority : 8 ''

 

3. Dispatcher Database

Ready 상태의 Thread 목록은 Priority 순으로 정렬되어 위에서 언급한 Dispatcher Database에 보관

Dispatcher Database는 Thread가 선점될 때와 퀀텀 시간이 종료될 때 사용

Dispatcher Database는 Processor 별로 존재하며, 위치는 KPRCB 구조체의 DispatcherReadyListHead 필드 값을 통해 확인

아래 예제를 보면, 총 32개의 Array로 구성된 것을 알 수 있음

DispatcherReadyListHead 
3: kd> dt nt!_KPRCB ffff800107d0e180 DispatcherReadyListHead
   +0x5980 DispatcherReadyListHead : [32] _LIST_ENTRY [ 0xffff8001`07d13b00 - 0xffff8001`07d13b00 ]

 

Dispatcher Database는 !ready command확인 가능

DispatcherReadyListHead 
kd> !ready
Processor 0: Ready Threads at priority 26
    THREAD fffffa8001fa27b0  Cid 0b1c.0b20  Teb: 000000007efdb000 Win32Thread: fffff900c0715d50 READY
Processor 0: Ready Threads at priority 23
    THREAD fffffa8000cde040  Cid 0004.0078  Teb: 0000000000000000 Win32Thread: 0000000000000000 READY
Processor 0: Ready Threads at priority 16
    THREAD fffffa8000cdd720  Cid 0004.0074  Teb: 0000000000000000 Win32Thread: 0000000000000000 READY

<snip>

 

4. Thread Quantum

Quantum은 OS가 Ready 상태인 동일 Priority의 다른 Thread가 없는지를 확인하기 전까지 Thread가 실행되는 시간의 양

Thread의 Quantum은 Foreground로 실행되는 Thread는 2 Tick, Background로 실행되는 Thread에게는 12 Tick이 부여

Windows 10 x64 기준으로 Quantum은 15ms

 

5. Waiting Threads

Processor의 개수는 제약적이고 Thread 개수는 매우 많기 때문에 많은 수의 Thread가 Ready 또는 Waiting 상태

하지만 너무 많은 수의 Ready 상태의 Thread가 있거나(Thread Starvation), Waiting 상태로 너무 오랫 동안 기다리고 있는 Thread가 있다면 System 성능 문제를 의심할 수 있음

 

Thread가 Waiting 하고 있는 시간은 !thread command에서 확인 가능

Wait Start TickCount와 Ticks 값 참고

Wait Start TickCount 값은 KThread 구조체의 WaitTime 값

System 내의 전체 Thread의 WaitTime을 확인하려면 !for_each_thread command를 이용

Wait Start TickCount 
THREAD ffff930176e25080  Cid 0248.024c  Teb: 0000003a682ad000 Win32Thread: ffff930176e68340 WAIT: (UserRequest) UserMode Non-Alertable
    ffff930176dfd4e0  NotificationEvent
Not impersonating
DeviceMap                 ffffde84b7e133c0
Owning Process            ffff930176e24080       Image:         wininit.exe
Attached Process          N/A            Image:         N/A
Wait Start TickCount      3017           Ticks: 3321 (0:00:00:51.890) ### <--
Context Switch Count      250            IdealProcessor: 5             
UserTime                  00:00:00.000
KernelTime                00:00:00.015
Win32 Start Address 0x00007ff6e8813470
Stack Init ffffcc015ee27650 Current ffffcc015ee270a0
Base ffffcc015ee28000 Limit ffffcc015ee21000 Call 0000000000000000
Priority 15 BasePriority 15 PriorityDecrement 0 IoPriority 2 PagePriority 5
Child-SP          RetAddr               : Args to Child                                                           : Call Site
ffffcc01`5ee270e0 fffff800`5ecac627     : ffff9301`76e25080 00000000`00000000 ffff8001`07e75200 ffff9301`73fca000 : nt!KiSwapContext+0x76
ffffcc01`5ee27220 fffff800`5ecac199     : 00000000`00000001 00000000`0000008c ffff9301`76e25180 00000000`00000200 : nt!KiSwapThread+0x297
ffffcc01`5ee272e0 fffff800`5ecaaf20     : ffff9301`763fa400 0000003a`00000000 00000000`00000000 ffffcc01`5ee273f1 : nt!KiCommitThreadWait+0x549
ffffcc01`5ee27380 fffff800`5f257d8c     : ffff9301`76dfd4e0 ffffffff`00000006 00000000`00000000 00007ff8`00000000 : nt!KeWaitForSingleObject+0x520
ffffcc01`5ee27450 fffff800`5edcbc05     : ffff9301`76e25080 00000000`00000000 00000000`00000000 ffff9301`76dfd4e0 : nt!NtWaitForSingleObject+0xfc
ffffcc01`5ee274c0 00007ff8`f387f7e4     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x25 (TrapFrame @ ffffcc01`5ee274c0)
0000003a`680ef978 00000000`00000000     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007ff8`f387f7e4

3: kd> dt nt!_KTHREAD ffff930176e25080 WaitTime
   +0x1b4 WaitTime : 0xbc9

3: kd> ? 0xbc9
Evaluate expression: 3017 = 00000000`00000bc9

!for_each_thread ".echo @#Thread ;  dt nt!_KTHREAD WaitTime @#Thread "

 

6. Idle Thread

Idle Thread는 실제로는 이름처럼 Idle이 아니라, 다음과 같은 역할을 수행

  • Interrupt 활성화
  • DPC queue 확인
  • Ready queue 확인 ## Processor가 언제 Thread가 Ready가 되었는지 확인할 때 사용
  • Power State가 변경되었는지 확인하기 위해 Power Management 호출