Windows 커널 & 드라이버 시리즈 — 18화
인터럽트, ISR, DPC — 하드웨어 신호를 두 단계로 처리하는 이유
키보드를 누르면 어떻게 드라이버까지 신호가 전달될까요? 하드웨어는 CPU에 "인터럽트"를 보내고, CPU는 하던 일을 잠깐 멈추고 드라이버의 ISR을 호출해요. 그런데 ISR은 최대한 빨리 끝내야 하기 때문에, 무거운 처리는 DPC로 미뤄둡니다. 이 두 단계 처리 구조를 이번 화에서 파헤쳐 볼게요.
인터럽트란?
하드웨어 장치는 CPU에 비동기적으로 신호를 보낼 수 있어요. 이게 인터럽트입니다. CPU는 인터럽트를 받으면 현재 실행 중이던 코드를 잠깐 멈추고, 미리 등록된 인터럽트 핸들러(ISR)를 호출합니다. ISR이 끝나면 원래 코드로 돌아가요.
인터럽트를 사용하는 장치들:
- 키보드, 마우스 — 키/버튼 입력 시
- 네트워크 카드(NIC) — 패킷 수신 시
- 하드 디스크/NVMe — I/O 완료 시
- 타이머 — 주기적으로
- USB 컨트롤러 — 장치 연결/데이터 수신 시
2단계 처리 구조 — ISR + DPC
ISR은 DIRQL이라는 매우 높은 IRQL에서 실행돼요. 이 레벨에서는 할 수 있는 게 극도로 제한되어 있어요(Paged Pool 접근 불가, 대기 불가, 심지어 DbgPrint도 안 됩니다). 그래서 ISR은 "하드웨어에서 데이터 읽기, 인터럽트 확인, DPC 큐잉" 세 가지만 하고 최대한 빨리 끝내요.
⚡
ISR (Interrupt Service Routine) — DIRQL에서 실행
역할: 인터럽트 확인, 데이터 빠르게 읽기, DPC 큐에 넣기, 인터럽트 해제
역할: 인터럽트 확인, 데이터 빠르게 읽기, DPC 큐에 넣기, 인터럽트 해제
↓ KeInsertQueueDpc()
🔧
DPC (Deferred Procedure Call) — DISPATCH_LEVEL에서 실행
역할: 데이터 처리, IRP 완료, 추가 I/O 요청 — NonPaged Pool은 OK, 대기는 불가
역할: 데이터 처리, IRP 완료, 추가 I/O 요청 — NonPaged Pool은 OK, 대기는 불가
↓ IoCompleteRequest()
📋
I/O 완료 루틴 / 앱 통지 — PASSIVE_LEVEL에서
앱의 비동기 I/O 완료 이벤트 발생, 콜백 호출 등
앱의 비동기 I/O 완료 이벤트 발생, 콜백 호출 등
인터럽트 등록 — IoConnectInterruptEx
// ISR 함수 선언 BOOLEAN MyInterruptIsr( _In_ PKINTERRUPT Interrupt, _In_ PVOID ServiceContext) // AddDevice에서 넘긴 DeviceExtension 포인터 { PMY_DEV_EXT ext = (PMY_DEV_EXT)ServiceContext; // 1. 정말 내 장치의 인터럽트인가? (공유 인터럽트 환경에서 중요) if (!MyDevice_IsMyInterrupt(ext)) { return FALSE; // 내 것이 아님 → 다음 ISR에 기회 줌 } // 2. 하드웨어 레지스터에서 데이터 읽기 (빠르게!) ext->LastData = READ_PORT_UCHAR(ext->IoBase + DATA_REG); // 3. 인터럽트 해제 (ACK) WRITE_PORT_UCHAR(ext->IoBase + CTRL_REG, INT_ACK); // 4. DPC 큐에 넣기 KeInsertQueueDpc(&ext->Dpc, NULL, NULL); return TRUE; // 처리 완료 } // DriverEntry 또는 AddDevice에서 인터럽트 등록 IO_CONNECT_INTERRUPT_PARAMETERS params = {0}; params.Version = CONNECT_FULLY_SPECIFIED; params.FullySpecified.PhysicalDeviceObject = pdo; params.FullySpecified.InterruptObject = &ext->Interrupt; params.FullySpecified.ServiceRoutine = MyInterruptIsr; params.FullySpecified.ServiceContext = ext; params.FullySpecified.Vector = translatedVector; params.FullySpecified.Irql = translatedIrql; params.FullySpecified.SynchronizeIrql = translatedIrql; params.FullySpecified.InterruptMode = translatedMode; params.FullySpecified.ProcessorEnableMask = affinity; params.FullySpecified.ShareVector = shareVector; status = IoConnectInterruptEx(¶ms);DPC 등록과 사용
// DPC 콜백 함수 VOID MyDpcRoutine( _In_ PKDPC Dpc, _In_ PVOID DeferredContext, // InitializeDpc에서 넘긴 컨텍스트 _In_ PVOID SystemArgument1, _In_ PVOID SystemArgument2) { PMY_DEV_EXT ext = (PMY_DEV_EXT)DeferredContext; UNREFERENCED_PARAMETER(Dpc); UNREFERENCED_PARAMETER(SystemArgument1); UNREFERENCED_PARAMETER(SystemArgument2); // DISPATCH_LEVEL에서 실행 — NonPaged Pool OK, Paged Pool NO, 대기 NO ULONG data = ext->LastData; // IRP 큐에서 하나 꺼내서 완료 처리 PIRP pendingIrp = GetNextPendingIrp(ext); if (pendingIrp) { pendingIrp->IoStatus.Status = STATUS_SUCCESS; pendingIrp->IoStatus.Information = sizeof(ULONG); *(PULONG)pendingIrp->AssociatedIrp.SystemBuffer = data; IoCompleteRequest(pendingIrp, IO_DISK_INCREMENT); } } // DeviceExtension 초기화 시 DPC 등록 KeInitializeDpc( &ext->Dpc, // KDPC 구조체 MyDpcRoutine, // 콜백 함수 ext); // 컨텍스트 (DeferredContext로 전달됨)ISR 작성 시 주의사항
- 최대한 짧게 — ISR이 실행되는 동안 해당 IRQL 이하의 모든 인터럽트가 차단돼요. 길면 길수록 시스템 응답성이 나빠져요.
- 재진입 가능(reentrant)해야 해요 — 멀티코어 시스템에서는 다른 코어의 인터럽트가 동시에 발생할 수 있어요.
- 공유 인터럽트 확인 — PCI 환경에서는 여러 장치가 같은 IRQ를 공유할 수 있어요. ISR에서 "내 장치의 인터럽트인가?"를 반드시 확인하고, 아니면 FALSE를 반환해야 해요.
- DbgPrint 사용 금지 — DIRQL에서는 DbgPrint가 내부적으로 락을 사용해서 데드락이 생길 수 있어요.
📌 DPC 타이머 — 주기적 작업
DPC를 일정 시간 후에, 또는 주기적으로 실행하고 싶다면 KTIMER + KDPC 조합을 쓰세요. KeInitializeTimer + KeSetTimerEx로 주기적 타이머를 설정하고, 만료 시 DPC를 실행하게 할 수 있어요. 폴링 대신 인터럽트를 쓰기 어려운 상황에서 유용합니다.
✅ 18화 요약 — 2개월차 완성!
- 인터럽트 처리는 ISR(초단기, DIRQL)과 DPC(처리, DISPATCH_LEVEL) 두 단계로 나뉩니다.
- ISR은 인터럽트 확인, 데이터 읽기, DPC 큐잉만 하고 바로 끝내야 해요.
- DPC에서 실제 데이터 처리와 IRP 완료를 합니다.
- IoConnectInterruptEx로 ISR을, KeInitializeDpc로 DPC를 등록합니다.
- 공유 인터럽트 환경에서는 ISR에서 "내 장치 인터럽트인지" 반드시 확인하세요.
다음 화 — 3개월차 시작: 고급 기법으로!
19화 — WDF 프레임워크 소개: WDM과 무엇이 다른가 →
#인터럽트 #ISR #DPC #IoConnectInterrupt #DIRQL
'Programming > 7. Device Driver' 카테고리의 다른 글
| 고급 기법 & 실무 적용 - 20화 KMDF 드라이버 개발 실습 (0) | 2026.06.10 |
|---|---|
| 고급 기법 & 실무 적용 - 19화 WDF 프레임워크 소개 (0) | 2026.06.09 |
| 드라이버 개발 핵심 - 17화 MDL (0) | 2026.06.05 |
| 드라이버 개발 핵심 - 16화 커널 메모리 풀 관리 (0) | 2026.06.04 |
| 드라이버 개발 핵심 - 15화 스핀락과 동기화 기법 (0) | 2026.06.03 |