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

드라이버 개발 핵심 - 10화 DriverEntry와 드라이버 오브젝트

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

DriverEntry와 드라이버 오브젝트 — DRIVER_OBJECT 완전 해부

📅 2026 ⏱ 읽기 약 13분 🏷 DRIVER_OBJECT / DriverEntry / MajorFunction
9화에서 DriverEntry를 처음 써봤는데, 파라미터로 넘어온 PDRIVER_OBJECT가 뭔지 궁금하셨을 거예요. 이번 화에서는 이 구조체를 뜯어보고, 특히 MajorFunction 배열을 통해 드라이버가 I/O 요청을 어떻게 처리하는지 알아볼게요.

DRIVER_OBJECT 구조체

OS가 드라이버를 로드하면 DRIVER_OBJECT 구조체를 하나 만들어서 DriverEntry에 전달합니다. 이 구조체는 드라이버가 살아있는 동안 계속 존재하면서 드라이버 전체를 대표해요.

typedef struct _DRIVER_OBJECT { CSHORT Type; // 오브젝트 타입 (항상 4) CSHORT Size; PDEVICE_OBJECT DeviceObject; // 이 드라이버가 만든 디바이스 오브젝트 체인 ULONG Flags; PVOID DriverStart; // 드라이버 이미지 시작 주소 ULONG DriverSize; // 드라이버 이미지 크기 PVOID DriverSection; // LDR_DATA_TABLE_ENTRY (모듈 정보) PDRIVER_EXTENSION DriverExtension; UNICODE_STRING DriverName; // "\Driver\MyDriver" 형태 PUNICODE_STRING HardwareDatabase; PFAST_IO_DISPATCH FastIoDispatch; // 빠른 I/O 처리 (파일시스템 드라이버용) PDRIVER_INITIALIZE DriverInit; // DriverEntry 함수 포인터 PDRIVER_STARTIO DriverStartIo; // StartIO 루틴 PDRIVER_UNLOAD DriverUnload; // ← 언로드 루틴 (우리가 등록) PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; // ← 핵심! } DRIVER_OBJECT;

여기서 가장 중요한 필드가 바로 MajorFunction 배열이에요.

MajorFunction 배열 — I/O 요청의 라우팅 테이블

MajorFunction은 함수 포인터 배열이에요. 각 인덱스는 I/O 요청의 종류(Major Code)를 나타내고, 해당 인덱스에 등록된 함수가 그 요청을 처리합니다.

인덱스 (Major Code)의미트리거 시점
IRP_MJ_CREATE (0x00)파일/디바이스 열기CreateFile() 호출
IRP_MJ_CLOSE (0x02)핸들 닫기CloseHandle() 호출
IRP_MJ_READ (0x03)읽기ReadFile() 호출
IRP_MJ_WRITE (0x04)쓰기WriteFile() 호출
IRP_MJ_DEVICE_CONTROL (0x0E)디바이스 제어 명령DeviceIoControl() 호출
IRP_MJ_PNP (0x1B)플러그앤플레이 이벤트장치 연결/제거 등
IRP_MJ_POWER (0x16)전원 관리절전/복귀 등

DriverEntry에서 이 배열에 함수를 등록해두면, 앱이 CreateFile로 디바이스를 열거나 DeviceIoControl로 명령을 보낼 때 해당 함수가 호출됩니다. 마치 웹 서버의 URL 라우팅 테이블과 비슷한 개념이에요.

실용적인 DriverEntry 패턴

실제로는 이렇게 씁니다:

#include <ntddk.h> #define DEVICE_NAME L"\\Device\\MyDevice" #define SYMLINK_NAME L"\\DosDevices\\MyDevice" VOID DriverUnload(_In_ PDRIVER_OBJECT DriverObject) { UNICODE_STRING symLink = RTL_CONSTANT_STRING(SYMLINK_NAME); IoDeleteSymbolicLink(&symLink); IoDeleteDevice(DriverObject->DeviceObject); DbgPrint("[MyDriver] Unloaded.\n"); } // IRP_MJ_CREATE / IRP_MJ_CLOSE 처리 NTSTATUS DispatchCreateClose( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); NTSTATUS status; PDEVICE_OBJECT deviceObject = NULL; UNICODE_STRING devName = RTL_CONSTANT_STRING(DEVICE_NAME); UNICODE_STRING symLink = RTL_CONSTANT_STRING(SYMLINK_NAME); // 1. 디바이스 오브젝트 생성 status = IoCreateDevice( DriverObject, 0, // DeviceExtension 크기 (나중에 배워요) &devName, FILE_DEVICE_UNKNOWN, // 디바이스 타입 FILE_DEVICE_SECURE_OPEN, FALSE, &deviceObject); if (!NT_SUCCESS(status)) { DbgPrint("[MyDriver] IoCreateDevice failed: 0x%08X\n", status); return status; } // 2. 심볼릭 링크 생성 (유저모드에서 \\.\MyDevice로 열 수 있게) status = IoCreateSymbolicLink(&symLink, &devName); if (!NT_SUCCESS(status)) { IoDeleteDevice(deviceObject); return status; } // 3. MajorFunction 등록 DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreateClose; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose; DriverObject->DriverUnload = DriverUnload; DbgPrint("[MyDriver] Loaded successfully.\n"); return STATUS_SUCCESS; }

심볼릭 링크가 왜 필요한가요?

\Device\MyDevice는 커널 네임스페이스에만 있어서, 유저 모드 앱이 직접 열 수 없어요. \DosDevices\MyDevice(유저 모드에서는 \\.\MyDevice)로 심볼릭 링크를 만들어야 앱에서 CreateFile("\\\\.\\MyDevice", ...)로 열 수 있습니다.

유저 모드 앱에서 드라이버 열기

// 유저 모드 앱 (user32 기반 일반 C 코드) HANDLE hDevice = CreateFile( L"\\\\.\\MyDevice", // 심볼릭 링크 이름 GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hDevice == INVALID_HANDLE_VALUE) { printf("Failed to open device: %d\n", GetLastError()); return 1; } // 이 순간 드라이버의 IRP_MJ_CREATE 핸들러가 호출됩니다! CloseHandle(hDevice); // IRP_MJ_CLOSE 핸들러 호출
💡 등록하지 않은 MajorFunction은 어떻게 되나요? WDK의 기본 동작은, 등록되지 않은 Major Code로 IRP가 오면 STATUS_INVALID_DEVICE_REQUEST를 반환합니다. 명시적으로 핸들러를 등록하지 않으면 해당 I/O 요청은 자동 거부돼요.

DeviceExtension — 드라이버만의 개인 공간

IoCreateDevice의 두 번째 파라미터가 DeviceExtensionSize인데, 이게 뭔지 궁금하셨을 거예요. DeviceExtension은 드라이버가 디바이스 오브젝트에 붙여놓는 사설 데이터 공간이에요. 드라이버가 유지해야 하는 상태, 설정 등을 여기에 저장합니다.

// DeviceExtension 구조체 정의 typedef struct _DEVICE_EXTENSION { PDEVICE_OBJECT DeviceObject; UNICODE_STRING DeviceName; BOOLEAN IsRunning; LONG RequestCount; // ... 드라이버에 필요한 데이터 } DEVICE_EXTENSION, *PDEVICE_EXTENSION; // IoCreateDevice 호출 시 status = IoCreateDevice( DriverObject, sizeof(DEVICE_EXTENSION), // 이 크기만큼 NonPaged Pool에 할당됨 &devName, ... ); // 이후 접근 PDEVICE_EXTENSION ext = (PDEVICE_EXTENSION)deviceObject->DeviceExtension; ext->IsRunning = TRUE;
✅ 10화 요약
  • DRIVER_OBJECT는 드라이버의 신분증으로, OS가 만들어서 DriverEntry에 전달합니다.
  • MajorFunction 배열에 I/O 종류별 처리 함수를 등록하는 것이 DriverEntry의 핵심 작업이에요.
  • IoCreateDevice로 디바이스 오브젝트를, IoCreateSymbolicLink로 유저 모드 접근 경로를 만듭니다.
  • DeviceExtension은 드라이버가 디바이스별로 유지할 사설 데이터 공간이에요.
다음 화
11화 — 디바이스 오브젝트와 디바이스 스택: 드라이버들이 쌓이는 방법 →

#DRIVER_OBJECT #MajorFunction #IoCreateDevice #DeviceExtension #DriverEntry