Share via


페이지 가능 코드 또는 데이터 잠금

직렬 및 병렬 드라이버와 같은 특정 커널 모드 드라이버는 관리하는 디바이스가 열려 있지 않으면 메모리 상주할 필요가 없습니다. 그러나 활성 연결 또는 포트가 있는 한 해당 포트를 관리하는 드라이버 코드의 일부는 디바이스를 서비스하기 위해 상주해야 합니다. 포트 또는 연결을 사용하지 않는 경우 드라이버 코드가 필요하지 않습니다. 반면, 시스템 코드, 애플리케이션 코드 또는 시스템 페이징 파일이 포함된 디스크용 드라이버는 항상 메모리 상주해야 합니다. 드라이버가 디바이스와 시스템 간에 데이터를 지속적으로 전송하기 때문입니다.

산발적으로 사용되는 디바이스(예: 모뎀)의 드라이버는 관리하는 디바이스가 활성 상태가 아닐 때 시스템 공간을 확보할 수 있습니다. 단일 섹션에 활성 디바이스를 서비스하기 위해 상주해야 하는 코드를 배치하고 디바이스를 사용하는 동안 드라이버가 메모리에 코드를 잠그면 이 섹션을 페이저블로 지정할 수 있습니다. 드라이버의 디바이스가 열리면 운영 체제는 페이징 가능한 섹션을 메모리로 가져오고 드라이버는 더 이상 필요하지 않을 때까지 해당 섹션을 잠깁니다.

시스템 CD 오디오 드라이버 코드는 이 기술을 사용합니다. 드라이버에 대한 코드는 CD 디바이스 제조업체에 따라 페이지 가능한 섹션으로 그룹화됩니다. 특정 브랜드는 지정된 시스템에 존재하지 않을 수 있습니다. 또한 시스템에 CD-ROM이 있더라도 자주 액세스할 수 없으므로 코드를 CD 유형별로 페이징 가능한 섹션으로 그룹화하면 특정 컴퓨터에 존재하지 않는 디바이스에 대한 코드가 로드되지 않습니다. 그러나 디바이스에 액세스하면 시스템은 적절한 CD 디바이스에 대한 코드를 로드합니다. 그런 다음, 드라이버는 아래에 설명된 대로 MmLockPagableCodeSection 루틴을 호출하여 디바이스를 사용하는 동안 코드를 메모리에 잠급니다.

페이저블 코드를 명명된 섹션으로 격리하려면 다음 컴파일러 지시문으로 표시합니다.

#pragma alloc_text(PAGE*Xxx, *RoutineName)

페이지 가능 코드 섹션의 이름은 4개의 문자 "PAGE"로 시작해야 하며, 섹션을 고유하게 식별하려면 최대 4자( Xxx로 표시됨)가 뒤따를 수 있습니다. 섹션 이름의 처음 네 글자(즉, "PAGE")는 대문자로 표시되어야 합니다. RoutineName은 페이지 가능 섹션에 포함할 진입점을 식별합니다.

드라이버 파일에서 페이지 가능한 코드 섹션의 가장 짧은 유효한 이름은 PAGE입니다. 예를 들어 다음 코드 예제의 pragma 지시문은 RdrCreateConnection PAGE라는 페이징 가능한 코드 섹션의 진입점으로 식별됩니다.

#ifdef  ALLOC_PRAGMA 
#pragma alloc_text(PAGE, RdrCreateConnection) 
#endif 

페이징 가능한 드라이버 코드를 상주하고 메모리에 잠그기 위해 드라이버는 MmLockPagableCodeSection을 호출하여 페이징 가능한 코드 섹션에 있는 주소(일반적으로 드라이버 루틴의 진입점)를 전달합니다. MmLockPagableCodeSection 은 호출에서 참조되는 루틴이 포함된 섹션의 전체 내용에서 잠급니다. 즉, 이 호출은 동일한 PAGEXxx 식별자와 연결된 모든 루틴을 상주하고 메모리에 잠깁니다.

MmLockPagableCodeSection 은 섹션의 잠금을 해제할 때( MmUnlockPagableImageSection 루틴을 호출하여) 또는 드라이버가 코드의 추가 위치에서 섹션을 잠가야 하는 경우 사용할 핸들을 반환합니다.

또한 드라이버는 거의 사용되지 않는 데이터를 페이징 가능으로 처리하여 지원하는 디바이스가 활성화될 때까지 페이징할 수도 있습니다. 예를 들어 시스템 믹서 드라이버는 페이저블 데이터를 사용합니다. 믹서 디바이스에는 연결된 비동기 I/O가 없으므로 이 드라이버는 데이터를 페이저블하게 만들 수 있습니다.

페이지가 지정 가능한 데이터 섹션의 이름은 "PAGE"라는 네 글자로 시작해야 하며 섹션을 고유하게 식별하려면 최대 4자까지 지정할 수 있습니다. 섹션 이름의 처음 네 글자(즉, "PAGE")는 대문자로 표시되어야 합니다.

코드 및 데이터 섹션에 동일한 이름을 할당하지 마세요. 소스 코드를 더 읽기 쉽게 만들기 위해 드라이버 개발자는 이 이름이 짧고 다양한 alloc_text pragma 지시문에 표시될 수 있으므로 일반적으로 PAGE라는 이름을 페이징 가능한 코드 섹션에 할당합니다. 그런 다음 드라이버에 필요할 수 있는 페이지 지정 가능한 데이터 섹션(예: data_seg PAGEDATA, bss_seg PAGEBSS 등)에 더 긴 이름이 할당됩니다.

예를 들어 다음 코드 예제의 처음 두 pragma 지시문은 PAGEDATA 및 PAGEBSS의 두 개의 페이징 가능한 데이터 섹션을 정의합니다. PAGEDATA는 data_seg pragma 지시문을 사용하여 선언되며 초기화된 데이터를 포함합니다. PAGEBSS는 bss_seg pragma 지시문을 사용하여 선언되며 초기화되지 않은 데이터를 포함합니다.

#pragma data_seg("PAGEDATA")
#pragma bss_seg("PAGEBSS")

INT Variable1 = 1;
INT Variable2;

CHAR Array1[64*1024] = { 0 };
CHAR Array2[64*1024];

#pragma data_seg()
#pragma bss_seg()

이 코드 예제 Variable1 에서 및 Array1 는 명시적으로 초기화되므로 PAGEDATA 섹션에 배치됩니다. Variable2Array2 는 암시적으로 0으로 초기화되며 PAGEBSS 섹션에 배치됩니다.

전역 변수를 0으로 암시적으로 초기화하면 디스크 내 실행 파일의 크기가 줄어들고 명시적 초기화보다 0으로 선호됩니다. 특정 데이터 섹션에 변수를 배치하기 위해 필요한 경우를 제외하고 명시적 0 초기화를 피해야 합니다.

데이터 섹션을 메모리 상주로 만들고 메모리에 잠그기 위해 드라이버는 MmLockPagableDataSection을 호출하여 페이징 가능한 데이터 섹션에 표시되는 데이터 항목을 전달합니다. MmLockPagableDataSection 은 후속 잠금 또는 잠금 해제 요청에 사용할 핸들을 반환합니다.

잠긴 섹션의 페이징 가능한 상태 복원하려면 MmUnlockPagableImageSection을 호출하여 MmLockPagableCodeSection 또는 MmLockPagableDataSection에서 반환된 핸들 값을 적절하게 전달합니다. 드라이버의 언로드 루틴은 MmUnlockPagableImageSection 을 호출하여 잠금 가능한 코드 및 데이터 섹션에 대해 가져온 각 핸들을 해제해야 합니다.

메모리 관리자는 페이지를 메모리로 잠그기 전에 로드된 모듈 목록을 검색해야 하므로 섹션 잠금은 비용이 많이 듭니다. 드라이버가 코드의 여러 위치에서 섹션을 잠그는 경우 MmLockPagableXxx섹션에 대한 초기 호출 후 보다 효율적인 MmLockPagableSectionByHandle을 사용해야 합니다.

MmLockPagableSectionByHandle에 전달된 핸들은 MmLockPagableCodeSection 또는 MmLockPagableDataSection에 대한 이전 호출에서 반환된 핸들입니다.

메모리 관리자는 각 섹션 핸들에 대한 개수를 유지하고 드라이버가 해당 섹션에 대해 MmLockPagableXxx 를 호출할 때마다 이 수를 증분합니다. MmUnlockPagableImageSection을 호출하면 개수가 감소합니다. 모든 섹션 핸들에 대한 카운터는 0이 아니지만 해당 섹션은 메모리에 잠겨 있습니다.

섹션에 대한 핸들은 드라이버가 로드되는 한 유효합니다. 따라서 드라이버는 MmLockPagableXxx섹션 을 한 번만 호출해야 합니다. 드라이버에 추가 잠금 호출이 필요한 경우 MmLockPagableSectionByHandle을 사용해야 합니다.

드라이버가 이미 잠겨 있는 섹션에 대해 MmLockPagableXxx 루틴을 호출하는 경우 메모리 관리자는 섹션에 대한 참조 횟수를 증가합니다. 잠금 루틴을 호출할 때 섹션이 페이징되면 메모리 관리자가 섹션의 페이지를 만들고 해당 참조 수를 1로 설정합니다.

이 기술을 사용하면 드라이버가 시스템 리소스에 미치는 영향을 최소화할 수 있습니다. 드라이버가 실행되면 상주해야 하는 코드와 데이터를 메모리에 잠글 수 있습니다. 디바이스에 대한 미해결 I/O 요청이 없는 경우(즉, 디바이스가 닫혀 있거나 디바이스가 열리지 않은 경우) 드라이버는 동일한 코드 또는 데이터의 잠금을 해제하여 페이징할 수 있도록 할 수 있습니다.

그러나 드라이버가 인터럽트 연결 후 인터럽트 처리 중에 호출할 수 있는 모든 드라이버 코드는 항상 메모리 상주해야 합니다. 일부 디바이스 드라이버를 호출 가능하거나 요청 시 메모리에 잠글 수 있지만 이러한 드라이버 코드 및 데이터의 일부 핵심 집합은 시스템 공간에 영구적으로 상주해야 합니다.

코드 또는 데이터 섹션을 잠그기 위한 다음 구현 지침을 고려합니다.

  • Mm(Un)LockXxx 루틴의 기본 사용은 일반적으로 페이지가 지정되지 않은 코드 또는 데이터를 페이징 가능으로 만들고 페이지가 없는 코드 또는 데이터로 가져올 수 있도록 하는 것입니다. 직렬 드라이버 및 병렬 드라이버와 같은 드라이버가 좋은 예입니다. 드라이버가 관리하는 디바이스에 대한 열린 핸들이 없는 경우 코드의 일부가 필요하지 않으며 페이징된 상태로 유지될 수 있습니다. 리렉터와 서버는 이 기술을 사용할 수 있는 드라이버의 좋은 예이기도 합니다. 활성 연결이 없으면 이러한 구성 요소를 모두 페이징할 수 있습니다.

  • 전체 페이지 가능 섹션이 메모리에 잠깁니다.

  • 코드 섹션 1개와 드라이버당 데이터 섹션 1개는 효율적입니다. 많은 명명된 페이지 가능 섹션은 일반적으로 비효율적입니다.

  • 순전히 페이지가 지정 가능한 섹션과 페이징되지만 요청 시 잠긴 섹션은 별도로 유지합니다.

  • MmLockPagableCodeSectionMmLockPagableDataSection을 자주 호출해서는 안 됩니다. 이러한 루틴은 메모리 관리자가 섹션을 로드할 때 I/O 작업이 많이 발생할 수 있습니다. 드라이버가 코드의 여러 위치에서 섹션을 잠가야 하는 경우 MmLockPagableSectionByHandle을 사용해야 합니다.