인터럽트
인터럽트(Interrupt)는 CPU가 현재 작업을 수행하는 도중 긴급한 이벤트가 발생했음을 알리는 신호입니다. CPU는 하던 일을 잠시 중단하고 해당 이벤트를 처리한 뒤, 원래 작업으로 돌아옵니다. 운영체제와 하드웨어 간 효율적인 소통의 핵심 메커니즘입니다.
핵심 개념
하드웨어 인터럽트
키보드 입력, 마우스 클릭, 네트워크 패킷 수신 등 외부 하드웨어 장치가 CPU에 보내는 신호입니다. 비동기적으로 발생하며 CPU는 현재 명령어를 끝낸 뒤 처리합니다.
핵심 개념
소프트웨어 인터럽트 (트랩)
프로그램이 의도적으로 발생시키는 인터럽트입니다. 시스템 콜(System Call)이 대표적이며, 사용자 모드에서 커널 기능을 요청할 때 사용됩니다.
핵심 개념
예외 (Exception)
0으로 나누기, 페이지 폴트, 잘못된 메모리 접근 등 CPU 내부에서 발생하는 오류 상황입니다. 동기적으로 발생하며 즉시 처리가 필요합니다.
핵심 개념
인터럽트 서비스 루틴 (ISR)
인터럽트 발생 시 실행되는 처리 함수입니다. x86 실제 모드에서는 IVT(Interrupt Vector Table), 보호 모드(현대 OS)에서는 IDT(Interrupt Descriptor Table)에 각 인터럽트 번호별 ISR 주소가 등록되어 있습니다.
핵심 개념
인터럽트 디스크립터 테이블 (IDT / IVT)
인터럽트 번호와 ISR 주소를 매핑한 테이블입니다. x86 실제 모드에서는 IVT를, 보호 모드(현대 OS)에서는 IDT를 사용합니다. CPU는 인터럽트 발생 시 이 테이블을 참조해 해당 ISR로 점프합니다.
핵심 개념
인터럽트 우선순위
여러 인터럽트가 동시에 발생하면 우선순위가 높은 것을 먼저 처리합니다. 중첩을 허용하는 구성에서는 우선순위가 낮은 ISR 실행 중 높은 우선순위 인터럽트가 오면 중단하고 처리합니다.
인터럽트의 종류와 번호
x86에서 인터럽트와 예외는 0~255번 벡터 번호로 구분됩니다. 인텔 설명서에서 0~31번은 아키텍처가 정의한 예외와 인터럽트용으로 예약되어 있고, 32~255번은 운영체제와 장치가 쓸 수 있는 번호입니다. 예를 들어 0번은 0으로 나누기 오류(#DE), 14번은 페이지 폴트(#PF)입니다. 마스크로 막을 수 없는 NMI(Non-Maskable Interrupt)도 따로 있습니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
인터럽트, 예외, 트랩은 어떻게 다른가요?
인터럽트는 외부 장치가 비동기로 보내는 신호이고, 예외는 명령어 실행 중 CPU가 감지한 오류 상황으로 동기로 발생합니다. 트랩은 프로그램이 의도적으로 일으키는 소프트웨어 인터럽트로 시스템 콜이 대표적입니다. 책마다 용어를 묶는 방식이 조금씩 다릅니다.
면접 질문
인터럽트가 발생하면 CPU는 무엇을 하나요?
현재 명령어를 끝낸 뒤 복귀에 필요한 상태(32비트 보호 모드에서는 EIP, CS, EFLAGS이고, 권한 수준이 바뀌면 SS:ESP도, 일부 예외는 에러 코드도)를 스택에 저장하고, 벡터 번호로 IDT를 찾아 ISR로 점프합니다. ISR이 끝나면 IRET으로 저장한 상태를 복원해 원래 흐름으로 돌아갑니다.
면접 질문
ISR 안에서 다른 인터럽트가 오면 어떻게 되나요?
x86에서 인터럽트 게이트로 진입하면 CPU가 인터럽트 플래그(IF)를 꺼서 마스커블 인터럽트를 막습니다. ISR이 IF를 다시 켜면 더 높은 우선순위의 인터럽트를 받는 중첩이 가능합니다. 방식은 구조와 운영체제마다 다릅니다.
면접 질문
폴링과 인터럽트는 언제 무엇을 쓰나요?
이벤트가 드물고 언제 올지 모르면 인터럽트가 CPU 시간을 아낍니다. 이벤트가 매우 자주 오거나 응답 지연을 극단적으로 줄여야 하면 폴링이 나을 수 있습니다.
실제 시스템에서는
cat /proc/interrupts를 실행하면 인터럽트 번호별로 CPU마다 처리한 횟수를 볼 수 있습니다. 키보드를 누르거나 네트워크로 패킷을 받을 때 해당 줄의 숫자가 늘어나는 것을 확인할 수 있습니다.
함께 보면 좋은 토픽
참고 자료
C — 인터럽트 벡터 테이블 등록 및 ISR 구현
// 인터럽트 서비스 루틴 (ISR) 타입 정의 typedef void (*isr_t)(void); // x86 보호 모드에서는 IDT(Interrupt Descriptor Table)를 사용한다. // IVT(Interrupt Vector Table)는 x86 실제 모드(Real Mode)의 구조이며, // 보호 모드부터는 IDT가 IVT를 대체한다. 아래는 개념 설명용 단순화 구현이다. #define IDT_SIZE 256 isr_t idt[IDT_SIZE]; // ISR 등록 함수 void register_isr(int irq_num, isr_t handler) { idt[irq_num] = handler; } // 키보드 인터럽트 핸들러 (IRQ1) void keyboard_isr(void) { /* 1. 키보드 포트에서 스캔 코드 읽기 */ uint8_t scancode = inb(0x60); /* 2. 스캔 코드를 ASCII로 변환해 버퍼에 저장 */ key_buffer[buf_head] = scancode_to_ascii(scancode); buf_head = (buf_head + 1) % KEY_BUF_SIZE; /* 3. PIC에 EOI(End of Interrupt) 신호 전송 */ outb(0x20, 0x20); /* IRET — ISR 종료 시 CPU가 자동으로 이전 컨텍스트 복원 */ } // 타이머 인터럽트 핸들러 (IRQ0) void timer_isr(void) { tick_count++; // 시스템 틱 증가 schedule(); // 프로세스 스케줄러 호출 outb(0x20, 0x20); // EOI 전송 } // 초기화: IRQ0(타이머), IRQ1(키보드) 등록 void init_interrupts(void) { register_isr(0, timer_isr); // IRQ0 → 타이머 register_isr(1, keyboard_isr); // IRQ1 → 키보드 sti(); // 인터럽트 활성화 (IF 플래그 SET) }
x86 보호 모드에서 인터럽트 디스패치는 IDT(Interrupt Descriptor Table)를 사용합니다.
IRQ 번호를 인덱스로 ISR 함수 포인터(게이트 디스크립터)를 저장하며,
인터럽트 발생 시 CPU는 IDTR 레지스터가 가리키는 IDT를 참조해 해당 ISR로 점프하고,
IRET 명령어로 이전 컨텍스트를 복원합니다.
C — 컨텍스트 저장 및 복원 (어셈블리 수준)
/* 인터럽트 발생 시 CPU가 자동으로 수행하는 동작 (x86 보호 모드 기준) */ // ── 인터럽트 발생 시 (하드웨어 자동) ────────────────────── // 1. IF(Interrupt Flag) 클리어 → 추가 인터럽트 차단 // 2. 스택에 컨텍스트 PUSH: // - 유저 모드 → 커널 모드: SS, ESP, EFLAGS, CS, EIP 순으로 PUSH // - 커널 모드 → 커널 모드: 스택 전환 없음, EFLAGS, CS, EIP만 PUSH // 3. IDT(Interrupt Descriptor Table)에서 ISR 주소 로드 // 4. ISR로 점프 (EIP = ISR 주소) // ── ISR 진입부 (어셈블리로 작성) ────────────────────────── __asm__( "keyboard_isr_entry:\n" " pusha\n" // EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI 저장 " call keyboard_isr\n" // C ISR 함수 호출 " popa\n" // 레지스터 복원 " iret\n" // EIP, CS, EFLAGS 복원 + IF 재활성화 ); // ── IRET 명령이 복원하는 것 ──────────────────────────────── typedef struct { uint32_t eip; // 중단된 명령어 주소 → 여기서 재개 uint32_t cs; // 코드 세그먼트 uint32_t eflags; // 플래그 레지스터 (IF 포함) } InterruptFrame; // ── 인터럽트 마스킹 예시 ────────────────────────────────── void critical_section(void) { cli(); // 인터럽트 비활성화 (IF = 0) /* 원자적으로 실행해야 하는 코드 */ shared_resource++; sti(); // 인터럽트 재활성화 (IF = 1) }
iret는 인터럽트 복귀 전용 명령어입니다. 스택에서 EIP, CS, EFLAGS를
한 번에 복원하고 인터럽트 플래그(IF)를 재활성화합니다.
cli/sti로 임계 구역을 보호하면 인터럽트에 의한
데이터 경합을 방지할 수 있습니다.
Java — 소프트웨어 인터럽트 (시스템 콜 시뮬레이션)
/** * 소프트웨어 인터럽트(트랩) 개념 시뮬레이션 * Java에서 직접 시스템 콜을 발생시키진 않지만, * JVM이 내부적으로 OS 시스템 콜을 트랩으로 처리하는 흐름을 표현 */ public class SyscallSimulator { // 시스템 콜 번호 정의 static final int SYS_READ = 0; static final int SYS_WRITE = 1; static final int SYS_EXIT = 60; // 시스템 콜 핸들러 테이블 (IVT와 동일한 개념) static final Runnable[] syscallTable = { () -> System.out.println("[KERNEL] SYS_READ 처리"), // 0 () -> System.out.println("[KERNEL] SYS_WRITE 처리"), // 1 }; /** * 소프트웨어 인터럽트 발생 시뮬레이션 * 실제 x86-32: INT 0x80 명령 → 커널 모드 전환 → 시스템 콜 처리 * (INT 0x80은 레거시 방식. 현재 x86-64는 syscall 명령어, * x86-32 빠른 경로는 sysenter 명령어를 사용한다.) */ static void syscall(int callNumber) { System.out.println("[USER] INT 0x80 발생 → 커널 모드 전환"); System.out.println("[CPU] 컨텍스트 저장 (PC, 레지스터 → 스택)"); if (callNumber >= 0 && callNumber < syscallTable.length) { syscallTable[callNumber].run(); // ISR 실행 } System.out.println("[CPU] 컨텍스트 복원 (스택 → PC, 레지스터)"); System.out.println("[USER] 사용자 모드 복귀\n"); } public static void main(String[] args) { System.out.println("=== 사용자 프로그램 실행 중 ==="); syscall(SYS_WRITE); // 소프트웨어 인터럽트 발생 syscall(SYS_READ); System.out.println("=== 프로그램 계속 실행 ==="); } }
Java에서 System.out.println()을 호출하면 내부적으로 JVM이
OS의 write 시스템 콜을 발생시킵니다. 이 과정에서 소프트웨어
인터럽트(트랩)가 발생해 사용자 모드에서 커널 모드로 전환되고,
처리 후 다시 사용자 모드로 복귀합니다.
Q1
인터럽트가 발생했을 때 CPU가 가장 먼저 하는 일은?
Q2
하드웨어 인터럽트와 소프트웨어 인터럽트의 차이는?
Q3
ISR(Interrupt Service Routine) 실행이 끝난 후 CPU는 어떻게 하나요?