"이 문서는 http://blogs.msdn.com/ntdebugging blog 의 번역이며 원래의 자료가 통보 없이 변경될 수 있습니다. 이 자료는 법률적 보증이 없으며 의견을 주시기 위해 원래의 blog 를 방문하실 수 있습니다. (http://blogs.msdn.com/ntdebugging/archive/2006/12/18/Understanding-Pool-Consumption-and-Event-ID_3A00_--2020-or-2019.aspx)"

Understanding Pool Consumption and Event ID: 2020 or 2019

안녕하세요 저의 이름은 Tate 입니다. 저는 Microsoft Critical Problem Resolution Platforms Team 의 Escalation Engineer 입니다. CPR Team 에서 분석하는 일반적인 에러 중 한가지에 대해서 공유 하고자 합니다. 근본적인 원인은 pool 이 많이 소모된 것이고 해결방법을 빠르게 찾을 수 있습니다.

Pool 이 많이 소모되는 문제는 보통 잘못 분석되고 있으나 90% 의 경우는 별다른 노력 없이 해결책을 찾을 수 있습니다.

첫째, 아래 Event 들의 진정한 의미는 무엇일까요

Event ID 2020
Event Type: Error
Event Source: Srv
Event Category: None
Event ID: 2020
Description:
The server was unable to allocate from the system paged pool because the pool was empty.

Event ID 2019
Event Type: Error
Event Source: Srv
Event Category: None
Event ID: 2019
Description:
The server was unable to allocate from the system NonPaged pool because the pool was empty.

특정 pool type 이 free memory 가 없을 경우 나타나는 친근한 Server Service 의 report 입니다. Server Service (srv.sys) 가 잘못되거나 원인이 아니고 리소스에 문제가 있을 경우 Event Log 에 기록됩니다. 아마도 (보통) system hang 이나 driver 나 application 에서 out of resource 와 같은 징후가 같이 나타나거나 모두 같이 나타납니다.

 

  • Pool 이란 무엇인가? 

첫째 Pool 은 System 에 있는 RAM 의 양을 나타내지는 않으나 Windows boot 시점에 virtual memory 이나 address space 가 예약 되어 있습니다. Pool 은 유한한 address space 입니다. 왜냐하면 32bit(x86) machine 에서는 2^32 인 4Giga 까지 address 가능하며 Windows 는 (기본값)2GB 를 application 이 사용하고 2GB 는 kernel 이 사용합니다. 2GB 의 Kernel을 나타내기 위한 PTEs 가 존재 하며 32Bit(x86) 에서의 Paged pool 은 ~460MB 까지 paged pool 이 사용 가능합니다. 64Bit(x64) 에서는 큰 address space 로 인해서 문제가 많이 사라지기는 하였으나 여전히 제약이 존재 합니다. (번역자주 : Windows Vista 이후의 kernel 은 메모리 할당이 dynamic 으로 변경되어 paged pool 의 한계가 없고 물리 메모리에 의해 할당 가능한 양 만큼 할당 할 수 있습니다.)

* 현재 pool 제약에 대한 보다 많은 정보는 이 문서 뒷부분의 “왜 200MB 까지만 할당할 수 있는가” 를 확인하십시오.

* Pool 에 대해 좀더 많은 정보는 About Memory Management -> Memory pools 참조 하십시오.

* 이 정보들은 Vista 에서 변경되었습니다. Dynamic Kernel Address Space 

 

  • Pool 들은 어디에 사용하는가?

Pool 들은 kernel 에서 직접 사용하기도 하고 Application 의 요청에 의해 system 이 사용하기도 합니다.(예CreateFile ) 또는 system 에 설치되어 있는 Driver 들이 pool allocation 함수를 사용하여 직접 할당하기도 합니다.

문자 그대로 Nonpaged 는 disk 로 page 되지 않는 메모리 할당을 이야기 하는 것으로 항상 메모리 상에 위치 하고 이것은 driver 의 중요한 특성입니다. Paged 는 disk 로 page 될 수 있고 마지막으로 이 모든 메모리 할당은 공통의 함수인 ExAllocatePoolWithTag 를 사용합니다.

 

그렇다면 무엇이 이것을 사용/남용 할까요?(우리의 목적이 맞지요?)

이제 우리는 windows 또는 windows 에 포함된 component 들 driver 또는 application 요청에 의해 kernel 이 할당한 것이 원인이 될 수 있다는 것을 알았습니다. 그렇다면 어떻게 찾을 수 있을까요?

일반적으로 사용하는 4가지 기본 방법이 있습니다.(난이도가 낮은순)

1.) Handle 개수 찾기?

Handle 개수? 그렇습니다. Application 이 OS 에 무언가를 요청하였을 때 생성 또는 참조를 하기 위해 생성되고 process 의 총 handle 개수에 포함됩니다.

Machine 이 hang 에 빠지지 않았을 경우 가장 간단한 방법은 Task Manager 를 통해 확인하는 것입니다. Ctrl+Shift+Esc 를 누른 후 Processes tab 에서 View, Select Columns, handle count 를 선택한 후 handle column 으로 정열을 하여 큰 값을 가진 것이 있는지 확인할 수 있습니다. (이 정보는 perfmon.exe, process explorer, handle.exe 에 포함되어 있습니다.)

많다는 것이란? 일반적으로 5000 개가 넘게 사용할 수 있습니다. 5000 을 넘는 다는 것이 무조건 나쁜 것은 아닙니다.  많이 사용한다는 것에는 대가가 따른 다는 것이고 많은 handle 을 사용한다는 것은 NonPaged 또는 Paged Pool 에 저장된 Object 의 수가 Memory 의 가용량 보다 늘어날 수 있다는 것입니다.

예를 들어 100,000 개의 핸들을 사용하는 mybadapp.exe 가 있다고 할 때 우리는 무엇을 해야 할까요?

만약에 이것이 service 라면 우리는 이것을 멈출 수 있습니다.( handle 을 해제할 것 입니다.) 또는 Application 이라면 종료할 수 있을 것이고 얼마나 많은 kernel memory (Paged 또는 NonPaged 우리가 부족한 것?) 가 줄어드는 지 확인할 수 있습니다. 만약 우리가 400MB 의 paged pool (성능 탭에서 kernel memory paged) 을 사용하고 있었고 100,000 개의 handle 을

사용하는  mybadapp.exe 를 종료하여 100MB 가 되었다면 우리가 잘못한 것이고 어떤 Type 의 handle 이 많이 소모 되었는지 확인할 수 있습니다.( sysinternals 의 process exploere 또는 Windows debugger 를 사용)

Tip : legacy application 을 지원하기 위해 변경을 가할 필요가 없습니다. Perfmonce monitor 통보를 설정하여 handle count 가 몇천개에 다다르면 통지를 주게 할 수 있습니다. (perfmance object : process, Counter : handle count) 그러나 이 것은 문제있는 application 이 system 을 hang/crash 하는 것을 찾을 수 있는 방법입니다.

2.) Pooltag (poolmon.exe 사용)

handle count 가 증가하지 않는가? 문제 없습니다.

Windows 2003 이후의 machine 들은 pool 소모를 확인할 수 있는 기능인 pooltag 가 기본으로 enable 되어 있습니다. 이전 OS 의 경우 gflags.exe 를 사용해서 Enable Pool Tagging 을 활성화 시켜야 했습니다(불행히도 재부팅을 해야 적용됩니다.) Pooltag 는  kernel api 를 사용하여 pool 을 할당할 때 3번째 파라미터로 제공한 기술적인 3-4개의 문자열입니다. 최대 4개까지의 문자열을 ‘ ’ 안에 넣습니다. (ExAllocatePoolWithTag)

Poolmon.exe 를 사용하여 어떤 pooltag 가 많이 사용되었는지 확인할 수 있습니다. cmd prompt 를 사용하여 실행 시킨 후 B 버튼을 누르면 Byte 별 내림 차순으로 보여주며 P 버튼을 누르면 Pool type (Paged, NonPaged, Both) 순으로 보여주고 우리는 실시간으로 변화량을 볼 수 있습니다. Tag 이름과 사용량을 확인하여 문제점을 파악할 수 있습니다. Poolmon.exe 받기 또는 poolmon.exe 의 사용법

OS 에서 사용하는 많은 Tag 들은 이미 pooltag.txt 라는 파일에 문서화 되어 있고 Windows Component 에 대한 정보를 확인할 수 있습니다. 만약 MmSt 라는 Tag 가 가장 높은 사용률을 기록하고 있다면 pooltag.txt 에서 Memory manager 가 사용한다는 것을 알 수 있고 Tag 를 search engine 을 사용하여 검색하면 아마도 KB304101 을 찾아 해결할 수 있을 것입니다.

Pooltag.txt 는 windbg 를 설치한 폴더의 triage 라는 폴더 안에 있습니다.

이런 list 에 존재하지 않는다면, 전혀 문제되지 않습니다.

다른 기술을 사용하여 tag 의 사용자를 찾을 수 있습니다.

32bit version 의 windows 에서 poolmon /c 를 사용하면 컴퓨터 안에(%SystemRoot%System32\Drivers\*.sys) 들어 있는 드라이버에 있는 Tag 를 사용하여 local tag 파일을 만들어 냅니다. 기본 이름은 localtag.txt 입니다.

Windows 2000 이나 Windows NT 4.0 의 경우 file 을 찾기 위해 아래 KB 에서 설명하는 FindStr 이나 검책창을 사용할 수 있습니다.

From: http://www.microsoft.com/whdc/driver/tips/PoolMem.mspx

 

3.) Driver Verifier 사용

Driver verifier 를 사용하는 것은 문제 분석을 위한 고급 시도입니다. Driver Verifier 는 Driver 개발자가 Driver 를 배포 하기 전에 안정성을 점검하기 위해 특정 드라이버에 사용하는 것입니다.

그러나 어떤 driver 의 pooltag 가 문제를 일으키는지 pool tracking 을 통해 확인할 수 있습니다.

Pool tracking 은 설치된 드라이버가 완벽하지 않아 Bouescreen 이 발생하거나 잘못된 동작으로 인해 문제가 되는 상태를 막기 위한 방법입니다.

정리하자면 Driver verifier 매우 강력한 tool 이다. 그러나 사용해야 하고 쉬운 방법으로 모든 문제를 해결해 주지는 않습니다.

4.) Debug (live 또는 이후 분석)

이전에 이야기 한 것과 같이 pool allocate 는 ExAllocatePoolWithTag 를 사용합니다. Kernel debugger 를 사용하고 있다면 이곳에 break point 를 설정할 수 있습니다. 그러나 이것은 유용하지 않고 우리는 이렇게 이야기할 수 있습니다. Down time 이 늘어나는가? 창조적인 live debug 방법이 필요하고 나중에 이야기할 고급 기술이 필요합니다.

일반적으로 debugging 은 이후 분석으로 Hang 이 발생한 서버에서 수집한 memory.dmp 파일을 가지고 분석을 하게 됩니다. System 에 hang 이 발생하고 keyboard 또는 Ctrl+Alt+Del 에는 반응할 경우 경우 Ctrl+Scroll Lock 방법 KB244139 을 사용할 수 있습니다.

Memory.dmp 를 windbg.exe 나 kd.exe 로 load 하였을 경우 아래의 명령으로 system 의 상태를 파악할 수 있습니다.

Debugger output Example 1.1 (the !vm command)

2: kd> !vm

*** Virtual Memory Usage ***
Physical Memory: 262012 ( 1048048 Kb)
Page File: \??\C:\pagefile.sys
Current: 1054720Kb Free Space: 706752Kb
Minimum: 1054720Kb Maximum: 1054720Kb
Page File: \??\E:\pagefile.sys
Current: 2490368Kb Free Space: 2137172Kb
Minimum: 2490368Kb Maximum: 2560000Kb
Available Pages: 63440 ( 253760 Kb)
ResAvail Pages: 194301 ( 777204 Kb)
Modified Pages: 761 ( 3044 Kb)
NonPaged Pool Usage: 52461 ( 209844 Kb)<<주의! 최대값에 근접해 있습니다.
NonPaged Pool Max: 54278 ( 217112 Kb)
********** Excessive NonPaged Pool Usage *****

 

NonPaged pool 사용률이 최대 값에 가깞다는 것은 NonPaged Pool 이 부족하다는 것을 알려줍니다.

!poolused 명령을 사용하여 poolmon 과 같은 결과를 볼 수 있습니다.

Debugger output Example 1.2 (!poolused 2)

2 값은 NonPaged 순으로 정렬하라는 파라미터입니다.

2: kd> !poolused 2
Sorting by NonPaged Pool Consumed

Pool Used:
NonPaged Paged
Tag Allocs Used Allocs Used
Thre 120145 76892800 0 0
File 187113 29946176 0 0
AfdE 89683 25828704 0 0
TCPT 41888 18765824 0 0
AfdC 90964 17465088 0 0

 

Thre 라는 Tag 가 가장 높게 기록된 것을 확인할 수 있고 pooltag.txt 파일에서 아래 정보를 확인할 수 있습니다.

Thre - nt!ps - Thread objects

! 앞에 있는 nt 는 NT 이거나 kernel 의 Thread object tag 라는 것입니다.

앞에서 이야기 한 것과 같이 많은 Thread object 를 가지고 있다면 아마도 system 에 있는 Application 중 많은 handle 값과 Thread 값을 가진 것이 있음을 쉽게 찾을 수 있습니다.

Debugger 에서 쉽게 !process 0 0 명령을 사용해서 TableSize ( Handle Count ) 값이 90,000 이 넘는 것을 확인할 수 있습니다.

Debugger output Example 1.3 (the !process command continued)

!process 이후 두개의 0 은 모든 process 의 정보를 확인한다는 의미입니다.

 

PROCESS 884e6520 SessionId: 0 Cid: 01a0 Peb: 7ffdf000 ParentCid: 0124
DirBase: 110f6000 ObjectTable: 88584448 TableSize: 90472
Image: mybadapp.exe

쉽게 thread 정보를 확인할 있습니다.

Debugger output Example 1.4 (the !process command continued)

0: kd> !PROCESS 884e6520 4
PROCESS 884e6520 SessionId: 0 Cid: 01a0 Peb: 7ffdf000 ParentCid: 0124
DirBase: 110f6000 ObjectTable: 88584448 TableSize: 90472.
Image: mybadapp.exe

THREAD 884d8560 Cid 1a0.19c Teb: 7ffde000 Win32Thread: a208f648 WAIT
THREAD 88447560 Cid 1a0.1b0 Teb: 7ffdd000 Win32Thread: 00000000 WAIT
THREAD 88396560 Cid 1a0.1b4 Teb: 7ffdc000 Win32Thread: 00000000 WAIT
THREAD 88361560 Cid 1a0.1bc Teb: 7ffda000 Win32Thread: 00000000 WAIT
THREAD 88335560 Cid 1a0.1c0 Teb: 7ffd9000 Win32Thread: 00000000 WAIT
THREAD 88340560 Cid 1a0.1c4 Teb: 7ffd8000 Win32Thread: 00000000 WAIT

And the list goes on…

 

!thread 88340560 과 같은 명령을 손쉽게 Thread 정보를 확인할 수 있습니다.

Applicatoin 이 여러분의 것이라면 code level 의 동작을 확인하여 어떤 Thread 가 만들어져 있으며 어떤 함수가 실해오디고 있고 어떤 상태인지 확인할 수 있��� 것입니다.

 

공통질문

  • Limit 이 460MB 근처라고 하면서 200 MB 까지만 할당할 수 있는가?

이유는 system 이 Boot 시점에 물리 메모리의 크기와 /3GB 와 같은 Option 을 확인하여 최대 Size X 를 결정합니다. Max size 를 확인할 수 있는 방법은 두 가지가 있습니다.

1.) Process Explorer 의 Task Manager 사용 – View.. system information.. kernel memory section

Dbghelp.dll 과 symbol 이 올바르게 설정되어 있어야 합니다.

Dbghelp.dll path:

c:\<path to debugging tools for windows>\dbghelp.dll

Symbols path:

SRV*C:\websymbols*http://msdl.microsoft.com/download/symbols

2.) Debugger 사용 ( live 또는 Dump 파일에서 !vm 사용)

* NonPaged pool size 는 /3GB  외에는 수정할 수 없습니다.

NonPaged pool size 는 /3GB 를 사용하면 128MB 이고 없으면 256MB 입니다.

Paged pool size 는 수동으로 PagedPoolSize 레지스트리 값을 변경하여 최대 Size 를 변경할 수 있습니다. 예제는 KB304101 에서 확인할 수 있습니다.

  • Perfmon 에서 process object pool paged byte 무엇인가?

Application 이 ExAllocatePoolWithQuotaTag 를 사용하여 할당한 것입니다. 일반적으로 ExAllocatePoolWithTag 를 사용하고 이 count 는 효과적일 것입니다. 그러나 perfmon 의 정보를 놓치지말아야 합니다.. 잘 활용하면 쉽게 문제를 해결할 수 있습니다.

  • 추가 자료:

“Who's Using the Pool?” from Driver Fundamentals > Tips: What Every Driver Writer Needs to Know

http://www.microsoft.com/whdc/driver/tips/PoolMem.mspx

Poolmon Remarks: http://technet2.microsoft.com/WindowsServer/en/library/85b0ba3b-936e-49f0-b1f2-8c8cb4637b0f1033.mspx

저의 글을 즐기고 도움이 되었기를 바랍니다. 그리고 다음 번에 같은 event 또는 Pool 소모 문제에 도움이 되었기를 바랍니다.