Media Log

PAGED_CODE 매크로

2011.02.27 19:09 | Programming
PAGED_CODE 는 다음과 같이 생긴 간단한 매크로이다.
#define PAGED_CODE() { \
    if (KeGetCurrentIrql() > APC_LEVEL) { \
        KdPrint(("EX: Pageable code called at IRQL %d\n", KeGetCurrentIrql())); \
        PAGED_ASSERT(FALSE); \
    } \
}
현재 IRQL을 체크해보고 IRQL이 APC_LEVEL 보다 높다면 시스템을 종료시킨다.

디바이스 드라이버는 유저 모드 프로그램들과는 다르게 텍스트 영역이 페이지 파일로 빠져나가지 않도록 설정된다. 즉 코드가 기본적으로 nonpaged 영역에 할당된다.
Making Drivers Pageable

물론 드라이버 프로그래머에게는 이를 제어할 수 있는 방법 또한 주어지며 코드나 데이터를 페이징 가능하도록 설정 할 수도 있다. #pragma alloc_text나 #pragma data_seg 디렉티브를 사용하면 컴파일 타임에 코드나 데이터들에 대한 페이징 여부를 결정할 수 있다.

드라이버 내의 특정 함수들을 페이징 가능하게 만들고 싶다면 다음처럼 쓴다.
#pragma alloc_text(PAGE, functionName)
이 구문은 해당 함수의 선언보다는 아래에 있어야 하고 정의보다는 위에 있어야 한다. 보통 c 모듈에서 헤더들을 include 한 뒤 그 바로 아래에 쓴다.

#pragma alloc_text 는 코드가 올라갈 섹션을 지정하는데, 섹션이란 PE 파일이 가지고 있는 바로 그 섹션을 뜻하며 위와 같이 쓰면 해당 PE파일에 PAGE라는 이름의 섹션이 새로 추가된다.
그리고 I/O 매니저는 드라이버 이미지를 로드할 때 이미지 내의 섹션 이름 중 앞의 4글자가 PAGE 또는 .EDA 가 있는지 찾아보고 있다면 해당 섹션을 paged pool에 넣어버린다.
이제 해당 코드는 시스템에 의해서 필요없을 땐 언제라도 페이지 파일로 빠져나가게 될 것이다.

디바이스 드라이버의 세계에서 중요한 규칙중 하나는 IRQL이 Dispatch 레벨 이상일 때는 페이지 폴트가 일어나서는 안된다는 것이다. IRQL >= DISPATCH_LEVEL 일 때 페이지 폴트가 일어나게 되면(페이지 폴트 뿐만아니라 다른 어떤 소프트웨어 예외라도 일어나게되면) 시스템은 크래시된다.

위 규칙을 알고 나면 #pragma alloc_text를 이용해서 코드를 페이징 가능하게 만들었을 경우에, IRQL이 DISPATCH_LEVEL 이상인 상태에서 해당 함수가 불릴 경우 페이지 폴트가 발생해서 블루스크린이 발생할 수도 있다는 것을 알 수 있다. 따라서 IRQL이 높을 때 불릴 수 있는 함수들은 paged pool에 할당해서는 안된다. 그런데 운이 좋아서 해당 시점에 코드가 램에 잘 존재하고 있어서 페이지 폴트가 일어나지 않는다면 이는 버그를 감추어주게 되고 나중에 더 골치아픈 문제를 가져다준다.

페이징 가능한 함수들에 PAGED_CODE 매크로를 사용하면 이런 운에 의해 감추어지는 버그들을 없애 버리고 곧장 시스템을 크래시 시켜 버림으로서 문제가 있는 부분을 쉽게 찾아낼 수 있다.
그래서 페이징 가능한 모든 코드에는 함수의 시작부에 PAGED_CODE() 매크로를 사용하는 것이다.

위에서 말한 내용들이 너무 복잡하다면 다음 규칙만 외워서 따라해도 좋다.
#pragma alloc_text를 사용했다면 짝이 맞는 함수를 찾아가 시작부분에 PAGED_CODE 매크로를 써준다.
이 둘을 항상 짝으로 같이 다니게 하면 된다.(둘다 있거나 둘다 없거나)

여기까지 읽었으면,
"빌어먹을, 코드이든 데이터이든간에 무조건 nonpaged pool에 할당하는게 편한 것 아닌가?"
하는 생각이 드는게 정상일 것이다. 물론 그렇게 하면 프로그래머는 좀 더 편해지겠지만, 오랫동안 사용되지 않고 있는 코드와 데이터들이 계속 물리 메모리를 점유하고 있기 때문에 시스템의 성능을 떨어뜨릴 수 있다. 특히 넷북같은 꼬물 컴퓨터에서는 더 많은 영향을 끼칠 것이다.
저작자 표시 비영리 동일 조건 변경 허락
신고

'Programming' 카테고리의 다른 글

_countof 매크로  (0) 2011.03.15
FIELD_OFFSET 매크로  (1) 2011.03.01
PAGED_CODE 매크로  (5) 2011.02.27
디렉터리의 읽기 전용 속성  (4) 2011.02.20
알쏭달쏭한 typedef  (9) 2011.01.04
하위 디렉터리의 파일이 변경 되었는지 감지하는 법  (8) 2010.12.20
  1. 오곡 at 2013.07.09 17:40 신고 [edit/del]

    잘배우고 갑니다~!

    Reply
  2. 없다캐라 at 2014.02.11 17:50 신고 [edit/del]

    저두 잘 배우고 갑니다. prama alloc 관련 검색을 해서 알게되어 왔는데 다른 글보다 이해하기 쉬웠습니다.

    Reply
  3. Favicon of http://minosori.tistory.com BlogIcon minosori at 2014.12.05 14:53 신고 [edit/del]

    안녕하세요,
    좋은 글 잘 읽고 있습니다. 감사합니당~
    그런데, 이해가 안돼서 질문드려요;;
    PAGED_CODE() 매크로를 써서 IRQL이 DISPATCH_LEVEL 이상이면 해당 코드가 페이지되어있든 안되어있든, 그리고 페이징 가능한 코드로 만들어져 있든 말든 무조건 크래시를 발생시키잖아요?(이것도 블루스크린으로 뜨나요?)
    그럼 메모리 절약을 위해서 해당 함수를 페이징 가능한 코드로 만들었다고 했을 때, 크래시가 일어나지 않고 다시 메모리로 로드 되도록 하려면 어떻게 해야하나요?
    현재, Driver Unload때만 섹션을 PAGE로 할당하고 나머지는 디폴트로(모두 Non Paged Area에 들어가게 되죠?) 만들고 돌려보니, NON_PAGED_AREA에서 PAGING됐다고 블루스크린이 뜹니다...ㅜ_ㅡ
    설마, 헤더파일과 소스파일을 여러개로 나눠서 작업하는거랑 관련이 있나요? 디바이스 드라이버는 전혀 생각지도 못한 부분에서 문제가 터지는 경우가 많아서 혹시나 싶어서 여쭤봅니다...;

    Reply
    • Favicon of http://www.benjaminlog.com BlogIcon 김재호 at 2014.12.05 15:37 신고 [edit/del]

      PAGED_CODE() 매크로를 써서 IRQL이 DISPATCH_LEVEL 이상이면 해당 코드가 페이지되어있든 안되어있든, 그리고 페이징 가능한 코드로 만들어져 있든 말든 무조건 크래시를 발생시키잖아요?(이것도 블루스크린으로 뜨나요?)

      -> 네 블루스크린이 발생합니다.

      그럼 메모리 절약을 위해서 해당 함수를 페이징 가능한 코드로 만들었다고 했을 때, 크래시가 일어나지 않고 다시 메모리로 로드 되도록 하려면 어떻게 해야하나요?

      질문을 잘 이해하지 못하겠지만, 원글에 써있는대로 함수를 페이징 가능한 코드로 만들었다면 해당 함수가 IRQL 이 디스패치 레벨보다 낮을 때만 불려야 합니다.
      pagefault in non paged area 라는 에러는 드라이버를 작성하면서 가장 흔히 볼수있는 오류이며, 위 규칙만 잘 지켰다고 해결되는 것은 아닐 껍니다. 동적으로 할당하는 메모리들을 페이지드 풀에 할당했는지도 다 꼼꼼하게 살펴봐야하는데, dump파일을 windbg 로 잘 분석해보시면 어디가 문제인지 찾으실 수 있을 겁니다.

    • Favicon of http://minosori.tistory.com BlogIcon minosori at 2014.12.05 16:54 신고 [edit/del]

      동적 메모리 할당할 때는 분명 NonPagedPool로 할당했는데... 아직 원인을 알 수 없네요. 메모리 할당부분을 주석처리 했더니 크래시가 나지 않는걸 보니 분명 메모리 할당부분에 문제가 있는것 같긴 한데, 아직 초보라 분석하려면 오래 걸릴것 같네요;;
      아무튼 답변 감사합니다. :)

submit
osronlinepooltag reporter를 사용하면 디바이스 드라이버들이 사용하는 커널 힙 메모리의 태그들과 그 용량을 GUI를 통해 확인할 수 있다.
어떤 풀 태그를 사용해서 논 페이지드 영역이나 페이지드 영역에 얼마만큼의 메모리를 할당하고 또 해제했는지를 실시간으로 보여준다.
디바이스 드라이버에서의 메모리 누수 등을 확인하고 싶을 때 주로 사용되며, 특정 태그들만 골라내서 보여줄 수 있는 기능이 있는데 와일드 카드도 지원이 되어서 편리하다.

디바이스 드라이버를 개발하며 디버깅하다보면 남의 풀 태그들을 보고서 이건 누구의 태그일까 궁금할 때가 많이 있는데, 윈도우즈에 기본으로 탑재되어 있는 드라이버들의 태그는 알아낼 수 있는 방법이 있다.

WDKDebugging Tools for Windows를 설치하면 <debugger>\triage\pooltag.txt 라는 텍스트 파일이 있는데, 이 곳에 태그의 주인이 누구인지 적혀있다.

예를 들어 WDK를 설치했다면,
C:\WinDDK\7600.16385.1\Debuggers\triage\pooltag.txt 와 같은 경로가 될 것이다.

C:\>pooltag -g C:\WinDDK\7600.16385.1\Debuggers\triage\pooltag.txt

pooltag를 실행할 때 이렇게 뒤에 인자를 넣어주면 풀태그 애플리케이션이 텍스트 파일을 파싱해서 UI에 함께 보여준다.

참고로 논 페이지드 풀은 32비트 운영체제에서 물리메모리의 75%나 2GB중 작은 값만큼 최대로 할당할수 있으며, 64비트 운영체제에서는 물리 메모리의 75%나 128GB 중 작은 값 만큼 할당할 수 있다.
저작자 표시 비영리 동일 조건 변경 허락
신고

submit