본문 바로가기
Programming/7. Device Driver

드라이버 개발 핵심 - 11화 디바이스 오브젝트와 디바이스 스택

by S.W 2026. 5. 29.
Windows 커널 & 드라이버 시리즈 — 11화

디바이스 오브젝트와 디바이스 스택 — 드라이버들이 쌓이는 원리

📅 2026 ⏱ 읽기 약 13분 🏷 DEVICE_OBJECT / 디바이스 스택 / Attach
7화에서 I/O 요청이 "드라이버 스택"을 타고 내려간다고 했는데, 그 스택이 어떻게 만들어지는지 이번 화에서 알아볼게요. 디바이스 오브젝트들이 어떻게 서로 연결되는지, 그리고 필터 드라이버가 어떻게 기존 스택에 끼어드는지 — 이게 이해되면 드라이버 아키텍처의 절반은 잡은 거예요.

디바이스 스택의 구조

디바이스 하나에는 여러 드라이버가 관여할 수 있어요. 예를 들어, USB 마우스를 연결하면 다음과 같은 스택이 구성됩니다:

필터 드라이버 (선택)
mouhid.sys — HID 마우스 필터
FDO
↕ IRP 전달 (IoCallDriver)
함수 드라이버 (필수)
hidusb.sys — USB HID 클래스 드라이버
FDO
버스 드라이버
usbhub.sys — USB 허브 드라이버
PDO
버스 드라이버 (루트)
usbhcd.sys — USB 호스트 컨트롤러
PDO

IRP는 맨 위에서 아래로 전달되고, 처리가 완료되면 반대 방향으로 올라갑니다. 각 드라이버는 IRP를 처리하거나, 아래 드라이버로 전달하거나, 둘 다 할 수 있어요.

PDO vs FDO — 두 종류의 디바이스 오브젝트

종류이름역할누가 만드나
PDO Physical Device Object 실제 하드웨어 장치를 대표. 버스 드라이버가 장치를 발견하면 생성 버스 드라이버 (usbhub, PCI 등)
FDO Functional Device Object 장치의 실제 기능을 구현. PDO 위에 붙어서 주요 I/O 처리 함수 드라이버 (hidusb, disk 등)
Filter DO Filter Device Object FDO 위나 아래에 붙어서 I/O를 가로채거나 수정 필터 드라이버 (보안, 암호화 등)
📌 WDM에서 PnP 드라이버의 기본 구조 WDM(Windows Driver Model) 기반 드라이버는 IRP_MJ_PNP를 처리해야 해요. PnP Manager가 새 장치를 발견하면, 버스 드라이버에게 PDO를 만들게 하고, 이어서 함수 드라이버에게 FDO를 만들어 스택을 완성하도록 지시합니다.

IoAttachDeviceToDeviceStack — 스택에 끼어들기

필터 드라이버는 기존 스택에 자신의 디바이스 오브젝트를 삽입합니다. 이를 "attach"라고 해요:

// 필터 드라이버의 AddDevice 루틴 (PnP 드라이버 기준) NTSTATUS FilterAddDevice( _In_ PDRIVER_OBJECT DriverObject, _In_ PDEVICE_OBJECT PhysicalDeviceObject) // PDO (버스 드라이버가 만든 것) { PDEVICE_OBJECT filterDO = NULL; NTSTATUS status; // 1. 내 필터 디바이스 오브젝트 생성 status = IoCreateDevice( DriverObject, sizeof(FILTER_DEVICE_EXTENSION), NULL, // 이름 없음 (필터는 보통 익명) PhysicalDeviceObject->DeviceType, 0, FALSE, &filterDO); if (!NT_SUCCESS(status)) return status; // 2. 기존 스택(PDO 위)에 내 DO를 붙인다 PFILTER_DEV_EXT ext = (PFILTER_DEV_EXT)filterDO->DeviceExtension; ext->LowerDevice = IoAttachDeviceToDeviceStack(filterDO, PhysicalDeviceObject); if (!ext->LowerDevice) { IoDeleteDevice(filterDO); return STATUS_NO_SUCH_DEVICE; } // 3. 플래그 동기화 (중요!) filterDO->Flags &= ~DO_DEVICE_INITIALIZING; filterDO->Flags |= (ext->LowerDevice->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO)); return STATUS_SUCCESS; }

LowerDevice 포인터가 핵심이에요

IoAttachDeviceToDeviceStack이 반환하는 포인터가 바로 이 필터 아래에 있는 다음 디바이스 오브젝트예요. IRP를 아래로 전달할 때 이 포인터를 씁니다:

// IRP를 아래 드라이버로 그대로 전달하는 패스스루(Pass-through) 패턴 NTSTATUS FilterDispatchAny( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { PFILTER_DEV_EXT ext = DeviceObject->DeviceExtension; // 뭔가 처리하고 싶으면 여기서... // (아무것도 안 하면 순수 패스스루) // 아래 드라이버로 IRP 전달 IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(ext->LowerDevice, Irp); }

IoSkipCurrentIrpStackLocation vs IoCopyCurrentIrpStackLocationToNext

IRP를 아래로 전달할 때 두 가지 매크로 중 하나를 선택해야 해요:

매크로사용 시점
IoSkipCurrentIrpStackLocation 현재 스택 위치를 그냥 건너뜀. 완료 루틴(CompletionRoutine)을 등록하지 않을 때 사용 — 가장 가벼운 패스스루
IoCopyCurrentIrpStackLocationToNext 현재 스택 위치를 다음 위치로 복사. 완료 루틴을 등록할 때 사용 — 완료 시 추가 작업이 필요한 경우
⚠️ 이 선택을 틀리면 BSOD 또는 메모리 손상이 납니다 IoSkipCurrentIrpStackLocation 후에 완료 루틴을 등록하면 이미 해제된 스택 위치에 접근하는 문제가 생겨요. 완료 루틴이 필요하면 반드시 IoCopyCurrentIrpStackLocationToNext를 써야 합니다.

WinDbg로 디바이스 스택 확인하기

; 특정 디바이스 오브젝트의 스택 전체 보기 !devstack <DEVICE_OBJECT 주소> ; 드라이버 이름으로 디바이스 찾기 !drvobj \Driver\disk ; 예시 출력: ; !DevObj !DrvObj !DevExt ObjectName ; ffff... \Driver\partmgr ffff... ; ffff... \Driver\disk ffff... DR0 ; > ffff... \Driver\ACPI ffff...
✅ 11화 요약
  • 디바이스 스택은 PDO(버스) → FDO(함수) → Filter DO(필터) 순서로 쌓입니다.
  • 필터 드라이버는 IoAttachDeviceToDeviceStack으로 기존 스택에 삽입됩니다.
  • IRP 전달 시 IoSkipCurrentIrpStackLocation(완료 루틴 없을 때) 또는 IoCopyCurrentIrpStackLocationToNext(있을 때) 중 하나를 선택합니다.
  • ext->LowerDevice 포인터로 아래 드라이버에 IRP를 전달합니다.
다음 화
12화 — IRP 구조 완전 분석: I/O Request Packet의 모든 것 →

#DEVICE_OBJECT #디바이스스택 #PDO #FDO #IoAttachDevice