컨텍스트 전환(Context Switch) – 퀀텀(Quantum)

컴퓨터는 하나의 스레드가 CPU를 사용할 수 있는 일정한 시간을 할당하는데, 이 할당한 시간만큼 스레드가 실행되게 된다. 이 개별 실행 시간을 퀀텀(Quantum)이라고 하며 퀀텀은 클록(Clacks)과 밀접한 관계가 있다. 클럭을 기준으로 CPU를 스케줄하여 사용되게 된다. 실제 퀀텀은 클럭을 기준으로 1/3로 계산되어, 몇 클록만큼 하나의 스레드가 실행할 수 있는지에 대해 정수값으로 이값을 표현한다.

이 값은 앞서 KTHREAD구조체에서 확인 했듯이 각 프로세스별로 퀀텀 초기 값을 가지게 된다.

스레드 별 할당된 퀀텀을 통해 각 스레드는 프로세서를 사용 가능할 때 자신에게 주어진 퀀텀만큼 실행하게 되고, 퀀텀을 다 사용하면, 다음 스레드에게 프로세서를 내어주게 된다. 이렇게 내어주게 될 때, 해당 위치에서 재실행 하기 위해 기존 실행 지점과 처리한 위치를 기억하여 하는 작업을 진행하는데 이것을 컨텍스트 전환(Context Switch)를 하게 된다. 즉 현재 실행중인 정보를 TCB(Task Control Black)에 저장하고 다음 실행될 스레드의 정보를 TCB로부터 불려오게 된다.

[그림] 스레드는 할당 퀀텀만큼 CPU를 사용하고 다른 스레드와 컨텍스트 전환을 한다

그림 이 컨텍스트 전환에 대해 Windbg를 이용하여 조금 더 자세히 알아 보자.

//!thread를 통해서도 확인 할 수 있으며, 여기서는 !stacks 명령을 통해 확인하도록 하겠다. 0~2까지 옵션을 사용할 수 있는데, 스택 정보의 표시 정보로 구분된다. 0이 기본값으로 요약하여 표시
kd> !stacks 2

Proc.Thread .Thread Ticks ThreadState Blocker

Max cache size is : 1048576 bytes (0x400 KB)

Total memory in cache : 0 bytes (0 KB)

Number of regions cached: 0

0 full reads broken into 0 partial reads

counts: 0 cached/0 uncached, 0.00% cached

bytes : 0 cached/0 uncached, 0.00% cached

** Prototype PTEs are implicitly decoded

[81fb99c8 System]

4.000008 81fb9750 00000e9 Blocked nt!KiSwapContext+0x2e

nt!KiSwapThread+0x46

nt!KeWaitForMultipleObjects+0x284

nt!MmZeroPageThread+0x61

nt!Phase1Initialization+0x1144

nt!PspSystemThreadStartup+0x34

nt!KiThreadStartup+0x16

4.000010 81fb6020 0000457 Blocked nt!KiSwapContext+0x2e

nt!KiSwapThread+0x46

nt!KeRemoveQueue+0x20e

nt!ExpWorkerThread+0xd6

nt!PspSystemThreadStartup+0x34

nt!KiThreadStartup+0x16

4.000014 81fb6da8 00049d7 Blocked Stack paged out

4.000018 81fb6b30 000000b Blocked nt!KiSwapContext+0x2e

nt!KiSwapThread+0x46

nt!KeRemoveQueue+0x20e

nt!ExpWorkerThread+0xd6

nt!PspSystemThreadStartup+0x34

nt!KiThreadStartup+0x16

4.00001c 81fb68b8 00049d7 Blocked Stack paged out

4.000020 81fb6640 0000583 Blocked nt!KiSwapContext+0x2e

nt!KiSwapThread+0x46

nt!KeRemoveQueue+0x20e

nt!ExpWorkerThread+0xd6

nt!PspSystemThreadStartup+0x34

nt!KiThreadStartup+0x16

4.000024 81fb63c8 0004f1a Blocked Stack paged out

4.000028 81fb5020 00049fb Blocked Stack paged out

4.00002c 81fb5da8 00049e3 Blocked Stack paged out

4.000030 81fb5b30 00027f3 Blocked Stack paged out

4.000034 81fb58b8 0004968 Blocked Stack paged out

4.000038 81fb5640 00027f6 Blocked Stack paged out

4.00003c 81fb53c8 000001b Blocked nt!KiSwapContext+0x2e

nt!KiSwapThread+0x46

nt!KeRemoveQueue+0x20e

nt!ExpWorkerThread+0xd6

nt!PspSystemThreadStartup+0x34

nt!KiThreadStartup+0x16

4.000040 81fb4020 00002a7 Blocked nt!KiSwapContext+0x2e

nt!KiSwapThread+0x46

nt!KeRemoveQueue+0x20e

nt!ExpWorkerThread+0xd6

nt!PspSystemThreadStartup+0x34

//모든 스레드에 nt!KiSwapContext, nt!KiSwapThread 가 있는 것을 확인할 수 있다. 그럼 컨텍스트 전환 동작을 uf 명령을 이용하여 어떻게 스레드가 전환 되는지 어셈블링해 보자

kd> uf nt!KiSwapThread

nt!KiSwapThread:

804de0bf 8bff mov edi,edi

804de0c1 56 push esi

804de0c2 57 push edi

804de0c3 3ea120f0dfff mov eax,dword ptr ds:[FFDFF020h]

804de0c9 8bf0 mov esi,eax

804de0cb 8b4608 mov eax,dword ptr [esi+8]

804de0ce 85c0 test eax,eax

804de0d0 8b7e04 mov edi,dword ptr [esi+4]

804de0d3 0f85d6b80000 jne nt!KiSwapThread+0x16 (804e99af)

nt!KiSwapThread+0x1c:

804de0d9 53 push ebx

804de0da 0fbe5e10 movsx ebx,byte ptr [esi+10h]

804de0de 33d2 xor edx,edx

804de0e0 8bcb mov ecx,ebx

804de0e2 e86bffffff call nt!KiFindReadyThread (804de052) ß 준비중인 스레드 검색

804de0e7 85c0 test eax,eax

804de0e9 0f8496990000 je nt!KiSwapThread+0x2e (804e7a85)

nt!KiSwapThread+0x3e:

804de0ef 5b pop ebx

nt!KiSwapThread+0x3f:

804de0f0 8bc8 mov ecx,eax

804de0f2 e831f7ffff call nt!KiSwapContext (804dd828) ß 컨택스트 전환을 한다.

804de0f7 84c0 test al,al

804de0f9 8a4f58 mov cl,byte ptr [edi+58h]

804de0fc 8b7f54 mov edi,dword ptr [edi+54h]

804de0ff 8b359c964d80 mov esi,dword ptr [nt!_imp_KfLowerIrql (804d969c)]

804de105 0f85d0090100 jne nt!KiSwapThread+0x56 (804eeadb)

…중략

//준비중인 스레드를 확인하여 컨텍스트 전환을 진행해주는 것을 확인할 수 있었다. 계속해서 컨텍스트 전환 동작을 어셈블리 하여 보자.

kd> uf nt!KiSwapContext

nt!KiSwapContext:

804dd828 83ec10 sub esp,10h

804dd82b 895c240c mov dword ptr [esp+0Ch],ebx ß EBX 레지스터 저장

804dd82f 89742408 mov dword ptr [esp+8],esi ß ESI 레지스터 저장

804dd833 897c2404 mov dword ptr [esp+4],edi ß EDI 레지스터 저장

804dd837 892c24 mov dword ptr [esp],ebp ß EBP 레지스터 저장

804dd83a 8b1d1cf0dfff mov ebx,dword ptr ds:[0FFDFF01Ch] ß PCR 주소 저장

804dd840 8bf1 mov esi,ecx

804dd842 8bbb24010000 mov edi,dword ptr [ebx+124h] ß 이전 스레드 주소 저장

804dd848 89b324010000 mov dword ptr [ebx+124h],esi ß 이후 스레드 주소 저장

804dd84e 8a4f58 mov cl,byte ptr [edi+58h]

804dd851 e8ce000000 call nt!SwapContext (804dd924)

804dd856 8b2c24 mov ebp,dword ptr [esp] ß EBP 레지스터 복원

804dd859 8b7c2404 mov edi,dword ptr [esp+4] ß EDI 레지스터 복원

804dd85d 8b742408 mov esi,dword ptr [esp+8] ß ESI 레지스터 복원

804dd861 8b5c240c mov ebx,dword ptr [esp+0Ch] ß EBX 레지스터 복원

804dd865 83c410 add esp,10h

804dd868 c3 ret

//EBX 레지스터에 PRC 주소를 저장하게 된다.

kd> dd 0FFDFF01Ch

ffdff01c ffdff000 ffdff120 0000001c 00000000

ffdff02c 00000000 ffff20d8 8054edb8 8003f400

//!PCR 확장명령을 통해 동일한 주소값임을 확인 할 수 있다.

kd> !pcr 0

KPCR for Processor 0 at ffdff000:

Major 1 Minor 1

NtTib.ExceptionList: 805527d0

NtTib.StackBase: 80552ff0

NtTib.StackLimit: 80550200

NtTib.SubSystemTib: 00000000

NtTib.Version: 00000000

NtTib.UserPointer: 00000000

NtTib.SelfTib: 00000000

SelfPcr: ffdff000

Prcb: ffdff120

Irql: 0000001c

IRR: 00000000

[내용] Windbg를 이용한 퀀텀값 확인

이 컨텍스트 전환시 저장하는 레지스터는 운영체제 버전과 32/64비트인지에 따라 달라지게 된다.

그럼 이러한 퀀텀값들이 어떻게 적용되고 운영되는지에 대해 계속 확인해 보자.

먼저 퀀텀은 CPU 클럭(Clock)을 기준으로 CPU 사용 시간을 계산하게 된다. 이 클럭은 앞서 윈도우 구조에서 Windbg를 통해 확인하는 방법을 진행한바 있다.

또한 Sysinternals에서 제공하는 Clockres를 통해 1회 Clock interval을 확인할 수 있다.

[그림] Clockres.exe를 이용해 클럭 인터발을 확인 할 수 있다

Ollydbg로 확인해본 결과, Clockres는 ZWQueryTimerResolution API을 이용하여 현 시스템에서 이용하는 클럭 간격을 구한다.

[그림] Ollydbg를 이용한 Clockres.exe 분석

기본 퀀텀값은 서버 모델(윈도우 2000, 2003, 2008)이냐 클라이언트 모델(윈도우 XP, 윈도우 비스타)이냐에 따라 다르게 된다. 클라이언트의 경우 6(3*2)으로 설정되어 있으며, 서버의 경우 36(3*12)로 초기 퀀텀값이 설정 되어 있다. (이 값은 제품별로 다를수 있다.) 퀀텀값은 타이머 인터럽트가 발생할 때 마다 3씩 하락하게 되어 0이 되면 다른 스레드에게 프로세스를 양보하게 된다.

그리고 퀀텀값이 전환시, 컨텍스트 전환(Context switch)를 진행하게 되는데, 이 컨텍스트 전환은 자주 일어날수록 시스템 성능에 영향을 미치게 된다.

따라서 잦은 컨텍스트 전환이 이루어지지 않도록 서버에 맞는 퀀텀값을 지정할 필요가 있다.

이값은 내컴퓨터의 마우스 오른쪽 메뉴의 속성에 드러가 고급->성능에서 변경이 변경이 가능하며,

[그림] 퀀텀 크기를 지정할 수 있다

레지스트리에 직접 접근하여서도 변경할 수 있다. 위 그림처럼 성능 옵션에서는 단 2가지만 선택이 가능하지만 레지스트리를 이용할 경우 보다 다양하게 퀀텀값을 정의할 수 있으며 레지스트리 경로는 아래와 같다.

레지스트리에서는 제공하느 다양한 옵션은 6Bit를 이용해 표현하는데 각 필드는 2비트씩 구분하여 바로 3가지 옵션을 선택이 가능하다.

6-5bit 4-3bit 2-1bit 결과
10h(32) 숏 퀀텀(Short Quantum)
01h(16) 롱 퀀텀(Long Quantum)
00(0) 기본값(클라이언트=숏, 서버=롱)
10h(8) 고정 퀀텀(Fixed Quantum)
01h(4) 가변 퀀텀(Variable Quantum)
00(0) 기본값(클라이언트=가변, 서버=고정)
10(2) 높은 포어그라운드(Foreground) 부스터(퀀텀x3, 기본순위+2)
01(1) 중간 포어그라운드(Foreground) 부스터(퀀텀x2, 기본순위+1)
00(0) 포어그라운드(Foreground) 부스터 사용않함

[내용] 레지스터로 지정 가능한 퀀텀 옵션

위 표에서 애기하는 숏과 롱, 가변과 고정의 퀀텀 값은 아래와 같다.

구분
가변 6 12 18 12 14 36
고정 18 18 18 36 36 36

[내용] 각 지정 값별 퀀텀 크기

그리고, 마지막 2-1bit의 값은 프로그램을 선택하였을 때, 포어그라운드 부스터 기능이 추가되는데, 이 기능이 활성화 되면, 새로운 포어그라운드 프로세스의 퀀텀은 3배가 되어 실행되게 된다. 따라서 새로운 포어그라운드 프로세스가 더 많은 CPU를 사용할 수 있게 된다.

이 포어그라운드 관련값은 커널 변수 PsPrioitySeparation에 저장되며, 전체적인 값은 PspForegroundQuantum에 저장되어 사용된다.

윈도우 클라이언트 버전(XP, 7)는 기본적으로 이값을 2를 가지고 있으며, 이값은 26h(38)과 같은 값을 의미한다. 그리고 서버 버전(2003, 2008)은 기본 퀀텀값을 0를 가지고 있으며, 이값은 18h(24)과 같은 값을 의미한다.

성능옵션의 프로그램 기본값

32 + 4 + 2 = 26h(38) = 숏, 가변, 높은 포어그라운드 부스터(High foreground boots)

성능옵션의 백그라운드 서비스 기본값

16 + 8 + 0 = 18h(24) = 롱, 고정, 포어그라운드 부스터 사용않함.

그외 선택가능한 퀀텀옵션

32 + 8 + 2 = 2Ah(42) = 숏, 고정, 높은 포어그라운드 부스터

32 + 8 + 1 = 29h(41) = 숏, 고정, 중간 포어그라운드 부스터

32 + 8 + 0 = 28h(40) = 숏, 고정, 포어그라운드 부스터 사용않함

32 + 4 + 2 = 26h(38) = 숏, 가변, 높은 포어그라운드 부스터

32 + 4 + 1 = 25h(37) = 숏, 가변, 중간 포어그라운드 부스터

32 + 4 + 0 = 24h(36) = 숏, 가변, 포어그라운드 부스터 사용않함.

16 + 8 + 2 = 1Ah(26) = 롱, 고정, 높은 포어그라운드 부스터

16 + 8 + 1 = 19h(25) = 롱, 고정, 중간 포어그라운드 부스터

16 + 8 + 0 = 18h(24) = 롱, 고정, 포어그라운드 부스터 사용않함

16 + 4 + 2 = 16h(22) = 롱, 가변, 높은 포어그라운드 부스터

16 + 4 + 1 = 15h(21) = 롱, 가변, 중간 포어그라운드 부스터

16 + 4 + 0 = 14h(20) = 롱, 가변, 포어그라운드 부스터 사용않함

[그림] Windows 7에서 백그라운드 서비스로 변경하였을때의 기본 퀀텀값

그럼 현재 시스템 퀀텀값을 Windbg를 통해 확인해 보도록하자.

//포어그라운드 우선쉰위로 지정된 값을 확인
kd> dd PsPrioritySeperation l1
ß포어그라운드 우선순위 2만큼 상승 (기본순위아님)

80563464 00000002

//퀀텀값 확인

kd> db PspForegroundQuantum l3
ß 숏,가변형임을 알수 있다.

80563440 06 0c 12

//현재 프로세스 리스트를 확인하자.

kd> !process 0 0

**** NT ACTIVE PROCESS DUMP ****

PROCESS 81fb99c8 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000

DirBase: 00039000 ObjectTable: e1000d10 HandleCount: 444.

Image: System

//해당 프로세스의 퀀텀값을 확인해보자.

kd> dt nt!_KPROCESS 81fb99c8

ntdll!_KPROCESS

+0x000 Header : _DISPATCHER_HEADER

+0x010 ProfileListHead : _LIST_ENTRY [ 0x81fb99d8 – 0x81fb99d8 ]

+0x018 DirectoryTableBase : [2] 0x39000

+0x020 LdtDescriptor : _KGDTENTRY

+0x028 Int21Descriptor : _KIDTENTRY

+0x030 IopmOffset : 0x20ac

+0x032 Iopl : 0 ”

+0x033 Unused : 0 ”

+0x034 ActiveProcessors : 0

+0x038 KernelTime : 0x173

+0x03c UserTime : 0

+0x040 ReadyListHead : _LIST_ENTRY [ 0x81fb9a08 – 0x81fb9a08 ]

+0x048 SwapListEntry : _SINGLE_LIST_ENTRY

+0x04c VdmTrapcHandler : (null)

+0x050 ThreadListHead : _LIST_ENTRY [ 0x81fb9900 – 0x81e12990 ]

+0x058 ProcessLock : 0

+0x05c Affinity : 1

+0x060 StackCount : 0x28

+0x062 BasePriority : 8 ”

+0x063 ThreadQuantum : 6ßThreadReset과 같은 의미로 퀀텀으로 6을 받았다.

+0x064 AutoAlignment : 0 ”

+0x065 State : 0 ”

+0x066 ThreadSeed : 0 ”

+0x067 DisableBoost : 0 ”

+0x068 PowerState : 0 ”

+0x069 DisableQuantum : 0 ”

+0x06a IdealNode : 0 ”

+0x06b Flags : _KEXECUTE_OPTIONS

+0x06b ExecuteOptions : 0 ”

[내용] Windbg를 이용한 퀀텀값 확인

이를 내컴퓨터->속성의 고급탭을 열어 백그라우드 서비스로 변경하도록 하자.

[그림] 퀀텀값 변경

이값은 시스템을 재시작하지 않아도 바로 적용된다. 그럼 Windbg를 통해 변경된 값을 확인해보자.

// 포어그라운드시 우선순위로 상승되어 지는 값을 확인해 보자. 예제에는 0으로 상승값이 없다.
kd> dd PsPrioritySeperation l1

80563464 00000000

//그럼 이제 퀀텀값을 확인해보자. 예제의 값을 위 표와 대조해보면 롱,고정형임을 알수 있다.

kd> db PspForegroundQuantum l3

80563440 24 24 24 $$$

kd> dt nt!_KPROCESS 81fb99c8

ntdll!_KPROCESS

+0x000 Header : _DISPATCHER_HEADER

+0x010 ProfileListHead : _LIST_ENTRY [ 0x81fb99d8 – 0x81fb99d8 ]

+0x018 DirectoryTableBase : [2] 0x39000

+0x020 LdtDescriptor : _KGDTENTRY

+0x028 Int21Descriptor : _KIDTENTRY

+0x030 IopmOffset : 0x20ac

+0x032 Iopl : 0 ”

+0x033 Unused : 0 ”

+0x034 ActiveProcessors : 0

+0x038 KernelTime : 0x18d

+0x03c UserTime : 0

+0x040 ReadyListHead : _LIST_ENTRY [ 0x81fb9a08 – 0x81fb9a08 ]

+0x048 SwapListEntry : _SINGLE_LIST_ENTRY

+0x04c VdmTrapcHandler : (null)

+0x050 ThreadListHead : _LIST_ENTRY [ 0x81fb9900 – 0x81e12990 ]

+0x058 ProcessLock : 0

+0x05c Affinity : 1

+0x060 StackCount : 0x28

+0x062 BasePriority : 8 ”

+0x063 ThreadQuantum : 36 ‘$’ßThreadReset과 같은 의미로 퀀텀값으로 36을 받았다.

+0x064 AutoAlignment : 0 ”

+0x065 State : 0 ”

+0x066 ThreadSeed : 0 ”

+0x067 DisableBoost : 0 ”

+0x068 PowerState : 0 ”

+0x069 DisableQuantum : 0 ”

+0x06a IdealNode : 0 ”

+0x06b Flags : _KEXECUTE_OPTIONS

+0x06b ExecuteOptions : 0 ”

[내용] Windbg를 이용해 퀀텀값이 변경되었음을 확인할 수 있다

이렇듯 퀀텀에 대한 최적화를 위해서는 컨텍스트 전환이 얼마나 많이 일어나지를 성능 모니터링을 통해 성능분석을 통해 진행할 수 있다. Perfmon.msc를 실행한 이후 System\Context Switches/sec를 모니터링하여, 과도한 컨텍스트 전환이 발생하는지 모니터링 후 최적의 값을 설정해줄 필요가 있다.

[그림] 성능 모니터를 이용하여 컨텍스트 전환 빈도를 분석할 수 있다

Facebook Comments

Leave A Reply

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다