인터럽트는 어떻게 처리하나요?
CPU가 인터럽트를 감지하면 현재 상태 저장 → IDT 조회 → 커널 모드 전환 → ISR 실행 → 상태 복원 → 복귀의 과정으로 처리합니다.
전체 처리 흐름 요약
① 인터럽트 발생 (하드웨어 신호 또는 소프트웨어 명령어)
② CPU가 현재 명령어 실행 완료
③ 인터럽트 수락 여부 확인 (IF 플래그)
④ 현재 상태 저장 (PC, 레지스터, 플래그 → 스택)
⑤ IDT에서 벡터 번호로 핸들러 주소 조회
⑥ 커널 모드 전환 (모드 비트 1 → 0)
⑦ ISR(Interrupt Service Routine) 실행
⑧ EOI 전송 (하드웨어 인터럽트의 경우)
⑨ 저장된 상태 복원
⑩ iret 명령어로 원래 프로그램 복귀
단계별 상세 설명
1단계: 인터럽트 발생
인터럽트 종류에 따라 발생 경로가 다릅니다.
[하드웨어 인터럽트]
장치 → IRQ 신호 → 인터럽트 컨트롤러(PIC/APIC)
→ 우선순위 판단 → CPU INTR 핀에 신호 + 벡터 번호 전달
[소프트웨어 인터럽트 — 트랩]
프로그램 → syscall 명령어 실행 → CPU가 직접 감지
[소프트웨어 인터럽트 — 예외]
프로그램 실행 중 오류 → CPU가 직접 감지 → 벡터 번호 결정
2단계: CPU가 현재 명령어 실행 완료
CPU는 명령어 실행 도중에 인터럽트를 처리하지 않음
현재 실행 중인 명령어를 완료한 뒤 인터럽트를 확인
명령어1 실행 → 명령어2 실행 → [인터럽트 확인 시점] → 인터럽트 처리
↑
매 명령어 완료 후 확인
3단계: 인터럽트 수락 여부 확인
CPU: RFLAGS 레지스터의 IF(Interrupt Flag) 확인
│
├── IF = 1 (활성화) → 인터럽트 수락, 처리 진행
│
└── IF = 0 (비활성화) → 마스커블 인터럽트 무시
(NMI는 IF와 무관하게 항상 수락)
| 인터럽트 종류 | IF = 0 일 때 |
|---|---|
| 마스커블 (키보드, 디스크 등) | 무시됨 (나중에 처리) |
| 논마스커블 NMI (전원 이상 등) | 무시 불가, 즉시 처리 |
| 소프트웨어 인터럽트 (트랩, 예외) | IF와 무관하게 처리 |
4단계: 현재 상태 저장
CPU가 자동으로 커널 스택에 저장하는 값:
커널 스택
┌───────────────────┐ ← 높은 주소
│ SS (사용자 스택 세그먼트) │
│ RSP (사용자 스택 포인터) │
│ RFLAGS (플래그 레지스터) │
│ CS (사용자 코드 세그먼트) │
│ RIP (복귀할 명령어 주소) │
│ Error Code (예외 시) │
└───────────────────┘ ← 낮은 주소 (현재 RSP)
이후 ISR이 추가 레지스터를 수동으로 저장:
push 범용 레지스터 (RAX, RBX, RCX, RDX, RSI, RDI, R8~R15 등)
5단계: IDT에서 핸들러 조회
CPU: 벡터 번호 확정
│
▼
IDTR 레지스터 → IDT 시작 주소
│
▼
IDT[벡터 번호] → 디스크립터 읽기
│
├── Selector → 코드 세그먼트 확인
├── Offset → ISR 시작 주소
├── DPL → 접근 권한 검사
└── Type → Interrupt Gate / Trap Gate
│
▼
ISR 주소로 점프 준비 완료
예시: 키보드 입력 (IRQ1 → 벡터 33)
IDTR → IDT 시작 주소: 0xFFFF8000
IDT[33] 조회:
┌─────────────────────────────────┐
│ Offset: 0xFFFF8000_001A3400 │ → ISR 주소
│ Selector: 0x10 (커널 코드 세그먼트) │
│ Type: Interrupt Gate │ → IF 자동 클리어
│ DPL: 0 (커널 권한) │
└─────────────────────────────────┘
6단계: 커널 모드 전환 + ISR 실행
모드 비트: 1 → 0 (사용자 → 커널)
스택 전환: 사용자 스택 → 커널 스택 (TSS에서 커널 RSP 로드)
┌──────────────────────────────────────┐
│ ISR 실행 내용 (인터럽트 종류에 따라 다름) │
├──────────────────────────────────────┤
│ │
│ [하드웨어 인터럽트 — 키보드 예시] │
│ ① in 명령으로 키보드 컨트롤러에서 │
│ 스캔 코드 읽기 │
│ ② 입력 버퍼에 저장 │
│ ③ Bottom Half 등록 │
│ │
│ [소프트웨어 인터럽트 — 시스템 콜 예시] │
│ ① RAX에서 시스템 콜 번호 추출 │
│ ② sys_call_table[RAX] 조회 │
│ ③ 커널 함수 실행 (sys_read 등) │
│ ④ 결과를 RAX에 저장 │
│ │
│ [예외 — 페이지 폴트 예시] │
│ ① CR2 레지스터에서 폴트 주소 확인 │
│ ② 유효한 접근인지 판단 │
│ ③ 디스크에서 페이지 로드 │
│ ④ 페이지 테이블 갱신 │
│ │
└──────────────────────────────────────┘
7단계: EOI 전송 (하드웨어 인터럽트만)
ISR 처리 완료
│
▼
인터럽트 컨트롤러에 EOI(End of Interrupt) 전송
│
▼
인터럽트 컨트롤러가 해당 IRQ 라인을 다시 수신 가능 상태로 전환
※ EOI를 보내지 않으면:
해당 IRQ 이하 우선순위의 인터럽트가 영원히 차단됨
8단계: 상태 복원 및 복귀
ISR 완료
│
├── ① 수동 저장한 범용 레지스터 복원 (pop)
│
├── ② iret 명령어 실행
│ ├── RIP 복원 (복귀할 명령어 주소)
│ ├── CS 복원 (코드 세그먼트)
│ ├── RFLAGS 복원 (플래그 레지스터, IF 포함)
│ ├── RSP 복원 (사용자 스택 포인터)
│ └── SS 복원 (사용자 스택 세그먼트)
│
└── ③ 모드 비트: 0 → 1 (커널 → 사용자)
│
▼
원래 프로그램이 중단된 지점부터 계속 실행
키보드 입력 처리 전체 예시
사용자가 'A' 키를 누름
│
▼ ──────────── 1단계: 인터럽트 발생 ────────────
키보드 컨트롤러 → IRQ1 → APIC → CPU INTR 핀 (벡터 33)
│
▼ ──────────── 2단계: 명령어 완료 ──────────────
CPU: 현재 명령어 실행 완료
│
▼ ──────────── 3단계: IF 확인 ─────────────────
RFLAGS.IF = 1 → 인터럽트 수락
│
▼ ──────────── 4단계: 상태 저장 ────────────────
SS, RSP, RFLAGS, CS, RIP → 커널 스택에 push
│
▼ ──────────── 5단계: IDT 조회 ────────────────
IDTR → IDT[33] → 키보드 ISR 주소 획득
│
▼ ──────────── 6단계: ISR 실행 ────────────────
[Top Half]
- 스캔 코드(0x1E) 읽기
- 입력 버퍼에 저장
- Tasklet 등록
│
▼ ──────────── 7단계: EOI 전송 ────────────────
APIC에 EOI → IRQ1 다시 수신 가능
│
▼ ──────────── 8단계: 상태 복원 + 복귀 ──────────
iret → RIP, CS, RFLAGS, RSP, SS 복원 → 사용자 모드 복귀
│
▼ ──────────── Bottom Half (나중에) ────────────
[Tasklet]
- 스캔 코드 → 'A' 문자 변환
- read()로 대기 중인 프로세스 깨우기
인터럽트 종류별 처리 차이
| 단계 | 하드웨어 인터럽트 | 시스템 콜 (트랩) | 예외 |
|---|---|---|---|
| 발생 | 장치 → APIC → CPU | syscall 명령어 | CPU가 오류 감지 |
| 벡터 결정 | APIC가 전달 | 0x80 또는 MSR 기반 | CPU가 자동 결정 |
| 상태 저장 | 동일 | 동일 (RCX, R11 사용) | 동일 + Error Code |
| ISR 내용 | 장치 데이터 처리 | sys_call_table 조회 | 오류 복구 또는 종료 |
| EOI | 필요 | 불필요 | 불필요 |
| 복귀 | iret | sysret | iret (Fault: 같은 명령어, Trap: 다음) |
Java에서의 인터럽트 처리 체감
// 파일 읽기 한 줄에서 발생하는 인터럽트 처리 과정
byte[] data = Files.readAllBytes(Path.of("data.txt"));
// 1. [트랩] open() 시스템 콜
// → 상태 저장 → IDT → sys_open() → 복귀
// 2. [트랩] read() 시스템 콜
// → 상태 저장 → IDT → sys_read()
// → 디스크 I/O 요청 → 프로세스 Sleep
// 3. [하드웨어 인터럽트] 디스크 I/O 완료
// → 상태 저장 → IDT → 디스크 ISR
// → Top Half: DMA 완료 확인
// → Bottom Half: 프로세스 깨우기
// → EOI → 복귀
// 4. 프로세스 재개 → 데이터를 Java 배열에 복사
// 5. [트랩] close() 시스템 콜
// → 상태 저장 → IDT → sys_close() → 복귀