Thread State Transition
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 호출