-
Windows 커널 IRQL 완벽 가이드: 레벨별 제약사항과 크래시 방지 전략윈도우/커널 드라이버 2025. 12. 22. 01:50반응형
개요
Windows 커널 드라이버를 개발하다 보면 가장 먼저 부딪히는 벽 중 하나가 바로 IRQL(Interrupt Request Level)입니다. IRQL을 제대로 이해하지 못하면 BSOD(Blue Screen of Death)와 친해질 수밖에 없죠. 이 글에서는 각 IRQL 레벨의 특성과 제약사항, 그리고 흔히 발생하는 크래시 시나리오를 정리합니다.
IRQL이란?
IRQL은 Windows 커널에서 인터럽트 우선순위를 관리하는 메커니즘입니다. 현재 프로세서의 IRQL보다 낮거나 같은 우선순위의 인터럽트는 마스킹되어 처리가 지연됩니다.
높은 우선순위 ↑ │ HIGH_LEVEL (31) - 머신 체크, 치명적 오류 │ POWER_LEVEL (30) - 전원 실패 │ IPI_LEVEL (29) - 프로세서 간 인터럽트 │ CLOCK_LEVEL (28) - 클럭 인터럽트 │ PROFILE_LEVEL (27) - 프로파일링 타이머 │ DIRQL (3-26) - 디바이스 인터럽트 │ DISPATCH_LEVEL (2) - 스레드 스케줄링, DPC │ APC_LEVEL (1) - 비동기 프로시저 콜 ↓ PASSIVE_LEVEL (0) - 일반 스레드 실행 낮은 우선순위레벨별 상세 분석
PASSIVE_LEVEL (0)
가장 낮은 IRQL로, 일반적인 스레드 코드가 실행되는 레벨입니다.
허용되는 작업:
- 모든 커널 API 호출
- Paged Pool 메모리 접근
- 파일 시스템 I/O
- 레지스트리 접근
- 동기화 객체 대기 (KeWaitForSingleObject 등)
- 유저모드 메모리 접근 (ProbeForRead/Write)
제약사항:
- 없음 (가장 자유로운 레벨)
일반적인 사용처:
- DriverEntry, DriverUnload
- IRP_MJ_CREATE, IRP_MJ_CLOSE
- 대부분의 IOCTL 처리
- 작업 스레드(Worker Thread)
// PASSIVE_LEVEL에서만 안전한 코드 예시 NTSTATUS SafeFileOperation(PUNICODE_STRING FilePath) { PAGED_CODE(); // PASSIVE_LEVEL 검증 매크로 HANDLE fileHandle; IO_STATUS_BLOCK ioStatus; OBJECT_ATTRIBUTES objAttr; InitializeObjectAttributes(&objAttr, FilePath, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL); // 파일 I/O는 반드시 PASSIVE_LEVEL에서 return ZwCreateFile(&fileHandle, ...); }APC_LEVEL (1)
APC(Asynchronous Procedure Call)가 실행되는 레벨입니다.
허용되는 작업:
- Non-Paged Pool 메모리 할당/접근
- Paged Pool 메모리 접근 (주의 필요)
- 스핀락 획득
- Fast Mutex 해제
제약사항:
- 커널 APC 비활성화됨
- KeWaitForSingleObject 호출 시 Alertable = FALSE 권장
- 일부 동기화 프리미티브 사용 불가
크래시 시나리오:
// ❌ 위험: APC_LEVEL에서 ERESOURCE 획득 시도 VOID DangerousApcRoutine(PVOID Context) { // 현재 IRQL: APC_LEVEL ExAcquireResourceExclusiveLite(&SomeResource, TRUE); // ERESOURCE는 내부적으로 APC를 사용하므로 데드락 가능! }DISPATCH_LEVEL (2) ⚠️ 가장 주의 필요
DPC(Deferred Procedure Call)와 스레드 스케줄러가 동작하는 레벨입니다. 드라이버 크래시의 대부분이 이 레벨에서 발생합니다.
허용되는 작업:
- Non-Paged Pool 메모리 할당/접근
- 스핀락 획득/해제
- DPC 큐잉
- 인터럽트 동기화
제약사항 (위반 시 즉시 BSOD):
금지 작업 이유 결과 Paged Pool 접근 페이지 폴트 처리 불가 IRQL_NOT_LESS_OR_EQUAL 파일 시스템 I/O 대기 필요 DRIVER_IRQL_NOT_LESS_OR_EQUAL 레지스트리 접근 대기 필요 시스템 행 또는 크래시 KeWaitForXxx (Timeout != 0) 스케줄러 호출 불가 IRQL_NOT_LESS_OR_EQUAL 유저모드 버퍼 접근 페이지 폴트 가능 PAGE_FAULT_IN_NONPAGED_AREA ExAllocatePoolWithTag(PagedPool) 페이지 폴트 가능 IRQL_NOT_LESS_OR_EQUAL 흔한 크래시 패턴:
// ❌ 패턴 1: Paged 메모리 접근 VOID DpcRoutine(PKDPC Dpc, PVOID Context, PVOID Arg1, PVOID Arg2) { // 현재 IRQL: DISPATCH_LEVEL PDEVICE_EXTENSION ext = (PDEVICE_EXTENSION)Context; // ext가 PagedPool에 할당되어 있다면 크래시! ext->SomeField = 0; // IRQL_NOT_LESS_OR_EQUAL } // ❌ 패턴 2: 대기 시도 VOID SpinlockProtectedCode(PKSPIN_LOCK Lock) { KIRQL oldIrql; KeAcquireSpinLock(Lock, &oldIrql); // 현재 IRQL: DISPATCH_LEVEL LARGE_INTEGER timeout; timeout.QuadPart = -10000000; // 1초 KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, &timeout); // 크래시! DISPATCH_LEVEL에서 대기 불가 KeReleaseSpinLock(Lock, oldIrql); } // ❌ 패턴 3: 문자열 함수 사용 VOID DpcStringOperation(PKDPC Dpc, PVOID Context, PVOID Arg1, PVOID Arg2) { UNICODE_STRING str; RtlInitUnicodeString(&str, L"Test"); // OK (스택) UNICODE_STRING copy; RtlDuplicateUnicodeString(0, &str, ©); // 내부적으로 PagedPool 할당 시도 → 크래시! }안전한 DISPATCH_LEVEL 코드:
// ✅ 안전한 DPC 루틴 VOID SafeDpcRoutine(PKDPC Dpc, PVOID Context, PVOID Arg1, PVOID Arg2) { PNONPAGED_CONTEXT ctx = (PNONPAGED_CONTEXT)Context; // Non-Paged 메모리만 접근 ctx->Counter++; // 작업이 복잡하면 Work Item으로 위임 if (ctx->NeedsComplexWork) { IoQueueWorkItem(ctx->WorkItem, WorkerRoutine, DelayedWorkQueue, ctx); } }DIRQL (Device IRQL, 3-26)
하드웨어 인터럽트 서비스 루틴(ISR)이 실행되는 레벨입니다.
허용되는 작업:
- 해당 디바이스 레지스터 접근
- 인터럽트 스핀락 획득/해제
- DPC 요청 (IoRequestDpc)
- 간단한 Non-Paged 메모리 접근
제약사항:
- DISPATCH_LEVEL의 모든 제약 + 추가 제약
- 대부분의 커널 API 호출 불가
- 가능한 짧게 실행해야 함
// ✅ 올바른 ISR 패턴 BOOLEAN DeviceIsr(PKINTERRUPT Interrupt, PVOID Context) { PDEVICE_EXTENSION ext = (PDEVICE_EXTENSION)Context; // 1. 인터럽트 확인 if (!IsMyInterrupt(ext)) { return FALSE; // 다른 디바이스 인터럽트 } // 2. 인터럽트 비활성화 (하드웨어) DisableDeviceInterrupt(ext); // 3. 최소한의 정보만 저장 ext->InterruptStatus = ReadInterruptStatus(ext); // 4. DPC로 실제 처리 위임 IoRequestDpc(ext->DeviceObject, NULL, ext); return TRUE; }HIGH_LEVEL (31)
최상위 IRQL로, 시스템 크래시 덤프나 NMI 처리에 사용됩니다.
특징:
- 모든 인터럽트 마스킹
- 거의 아무것도 할 수 없음
- 드라이버에서 직접 사용하지 않음
IRQL 관련 주요 크래시 코드
Bug Check Code 원인 해결책 IRQL_NOT_LESS_OR_EQUAL (0xA) 높은 IRQL에서 Paged 메모리 접근 Non-Paged Pool 사용, IRQL 확인 DRIVER_IRQL_NOT_LESS_OR_EQUAL (0xD1) 드라이버에서 잘못된 메모리 접근 메모리 할당 타입 확인 PAGE_FAULT_IN_NONPAGED_AREA (0x50) Non-Paged로 예상했으나 실제론 Paged 할당 플래그 확인 SPIN_LOCK_ALREADY_OWNED (0xF) 같은 스핀락 중복 획득 락 계층 구조 검토 KERNEL_APC_PENDING_DURING_EXIT (0x20) APC_LEVEL에서 스레드 종료 IRQL 복원 확인 안전한 코딩을 위한 체크리스트
컴파일 타임 검증
// PASSIVE_LEVEL 전용 함수 표시 #pragma alloc_text(PAGE, MyPagedFunction) NTSTATUS MyPagedFunction() { PAGED_CODE(); // 디버그 빌드에서 IRQL 검증 // ... }런타임 IRQL 확인
VOID SafeFunction(PVOID Buffer, SIZE_T Size) { KIRQL currentIrql = KeGetCurrentIrql(); if (currentIrql >= DISPATCH_LEVEL) { // Non-Paged 처리 경로 ProcessAtHighIrql(Buffer, Size); } else { // Paged 처리 가능 ProcessAtLowIrql(Buffer, Size); } }Work Item 패턴 (IRQL 낮추기)
typedef struct _WORK_CONTEXT { PIO_WORKITEM WorkItem; PVOID Data; SIZE_T DataSize; } WORK_CONTEXT, *PWORK_CONTEXT; // DISPATCH_LEVEL에서 호출 VOID QueueWorkAtPassiveLevel(PDEVICE_OBJECT DeviceObject, PVOID Data) { PWORK_CONTEXT ctx = ExAllocatePoolWithTag( NonPagedPool, sizeof(WORK_CONTEXT), 'kroW'); if (ctx) { ctx->WorkItem = IoAllocateWorkItem(DeviceObject); ctx->Data = Data; // PASSIVE_LEVEL에서 실행될 작업 큐잉 IoQueueWorkItem(ctx->WorkItem, PassiveLevelWorker, DelayedWorkQueue, ctx); } } // PASSIVE_LEVEL에서 실행됨 VOID PassiveLevelWorker(PDEVICE_OBJECT DeviceObject, PVOID Context) { PWORK_CONTEXT ctx = (PWORK_CONTEXT)Context; // 이제 파일 I/O, 레지스트리 접근 등 가능 ProcessDataSafely(ctx->Data); IoFreeWorkItem(ctx->WorkItem); ExFreePoolWithTag(ctx, 'kroW'); }Driver Verifier 활용
IRQL 관련 버그를 잡기 위해 Driver Verifier를 활용하세요.
:: IRQL 검증 활성화 verifier /standard /driver YourDriver.sys :: 특정 검증만 활성화 verifier /flags 0x2 /driver YourDriver.sys :: IRQL checking :: 상태 확인 verifier /query요약 테이블
IRQL Paged 접근 파일 I/O Wait 스핀락 일반 API PASSIVE_LEVEL ✅ ✅ ✅ ✅ ✅ APC_LEVEL ✅ ✅ ⚠️ ✅ ⚠️ DISPATCH_LEVEL ❌ ❌ ❌ ✅ ❌ DIRQL ❌ ❌ ❌ ⚠️ ❌ HIGH_LEVEL ❌ ❌ ❌ ❌ ❌ 결론
IRQL을 마스터하는 것은 Windows 커널 드라이버 개발의 기본입니다. 핵심은 간단합니다:
- DISPATCH_LEVEL 이상에서는 Paged 메모리 접근 금지
- DISPATCH_LEVEL 이상에서는 대기(Wait) 금지
- 확실하지 않으면 Work Item으로 PASSIVE_LEVEL에서 처리
- Driver Verifier로 항상 테스트
이 규칙만 지켜도 IRQL 관련 BSOD의 90%는 예방할 수 있습니다.
반응형'윈도우 > 커널 드라이버' 카테고리의 다른 글
EWDK(Enterprise WDK) 를 사용하여 여러 Visual Studio 에서 Driver 개발하기 (0) 2026.01.08