1. 개념 요약

interrupt는 CPU가 코드를 실행하는 도중에 외부 장치나 예외 상황이 발생하면 현재 실행 흐름을 잠시 멈추고 정해진 처리 루틴(handler)으로 이동했다가 다시 돌아오게 하는 기능이다.

키보드 입력이나 타이머 같은 이벤트는 언제 들어올지 알 수 없다. interrupt가 없으면 CPU는 이벤트가 왔는지 계속 직접 확인해야 한다. interrupt를 쓰면 이벤트가 발생한 순간에만 CPU가 handler로 제어를 넘기므로 평소에는 다른 코드를 실행하거나 대기할 수 있다.

Phase 2는 이 interrupt 구조를 세워 키보드와 타이머에 반응하는 커널을 만드는 단계다. 어떤 vector 번호가 어떤 handler로 연결되는지 적어 두는 표인 IDT를 만들고 CPU에 등록한다.

2. 왜 필요한가

입력을 받는 방법은 polling과 interrupt 두 가지다. 두 방식은 이벤트가 왔는지 누가 언제 확인하는지에서 갈린다.

방법동작비용
polling코드가 직접 반복하면서 이벤트가 왔는지 계속 확인한다.이벤트가 없어도 CPU를 계속 사용한다.
interrupt평소에는 다른 코드를 실행하거나 대기하다가 이벤트가 발생하면 CPU가 IDT를 참조해 handler로 제어를 넘긴다.이벤트가 있을 때만 CPU를 사용한다.

키보드나 타이머처럼 이벤트가 드물게 들어오는 입력에서는 polling이 CPU를 낭비한다. 그래서 운영체제는 interrupt 방식을 사용한다. go-os도 Phase 2부터 interrupt로 입력을 받는다.

3. 등장 배경

현재 커널은 for {}로 멈춰 있어 외부 이벤트에 반응하지 못한다. 키 입력이나 타이머 틱이 들어와도 실행 흐름을 멈추고 handler로 이동할 방법이 없기 때문이다.

CPU가 외부 이벤트나 예외 상황을 처리하려면 현재 실행 흐름을 잠시 멈추고 정해진 handler로 이동했다가 돌아올 수 있어야 한다. 이 동작을 하드웨어 차원에서 제공하는 것이 interrupt다.

go-os의 현재 단계에서는 Phase 1에서 Long mode로 전환한 64비트 커널을 기준으로 한다. interrupt handler도 Phase 1에서 만든 64비트 코드 세그먼트 위에서 실행된다.

4. 필요한 전제 조건

interrupt가 발생했을 때 CPU가 handler로 제어를 넘기려면 다음 설정이 준비돼 있어야 한다.

조건설명
IDT 준비vector 번호와 handler 주소를 연결하는 표를 만들어야 한다.
IDTR 등록lidt로 IDT의 위치를 CPU의 IDTR에 등록해야 CPU가 그 표를 참조한다.
64비트 코드 세그먼트IDT 엔트리가 참조할 GDT의 64비트 코드 세그먼트가 있어야 한다(Long mode 글 참고).
PIC 리맵과 IRQ 허용하드웨어 IRQ를 받으려면 PIC가 보내는 번호를 32번부터로 옮기고 필요한 IRQ 라인의 mask를 해제해야 한다.
interrupt 활성화sti로 RFLAGS.IF를 켜야 maskable hardware interrupt를 받을 수 있다.

예외 handler는 IDT 등록만으로도 확인할 수 있다. 하지만 키보드나 타이머 같은 maskable hardware IRQ를 받으려면 PIC 리맵과 IRQ mask 설정을 끝낸 뒤 sti로 interrupt를 허용해야 한다.

PIC 리맵은 빠뜨리기 쉽다. 리맵하지 않으면 하드웨어 IRQ 번호가 CPU 예외 번호와 겹쳐서 타이머 IRQ가 예외 handler로 잘못 들어갈 수 있다.

5. 핵심 구성 요소

interrupt 처리에는 번호 체계(vector), 번호를 handler로 연결하는 표(IDT), 표의 한 엔트리(게이트), 처리 루틴(handler), 하드웨어 IRQ를 전달하는 컨트롤러(PIC)가 관여한다.

5.1 interrupt의 종류

interrupt는 모두 vector 번호로 구분하고 처리 메커니즘이 같다. 다만 누가 일으키는지로 나누면 세 종류다.

종류일으키는 주체
하드웨어 interrupt (IRQ)외부 장치키보드 눌림, 타이머 틱, 디스크 완료
예외 (exception)CPU 자신0으로 나누기, 페이지 폴트, 잘못된 명령
소프트웨어 interrupt코드가 int 명령 실행시스템콜(유저에서 커널로)

5.2 vector 배정

vector는 0부터 255까지의 interrupt 번호다. 앞쪽 번호는 CPU 예외용으로 고정되어 있다.

vector용도
0~31CPU 예외(Intel 고정). 0은 0으로 나누기, 14는 페이지 폴트 등이다.
32~255자유롭게 쓸 수 있다. 하드웨어 IRQ는 PIC 리맵으로 32번부터 배정한다.

0~31이 예약되어 있으므로 하드웨어 IRQ는 그 뒤로 옮겨서 써야 한다.

5.3 IDT와 게이트 엔트리

IDT(Interrupt Descriptor Table)는 vector 번호와 handler 주소를 연결하는 표다. x86에서는 최대 256개의 vector를 사용할 수 있고 go-os에서는 이 범위에 맞춰 IDT 엔트리를 준비한다.

IDT의 한 엔트리를 게이트 엔트리라고 부른다. 게이트 엔트리 하나는 16바이트이고 GDT 엔트리(8바이트)보다 크다. 한 엔트리에는 점프할 handler 주소, 그 handler가 실행될 코드 세그먼트 셀렉터, 타입과 플래그가 들어간다.

항목설명
handler 주소interrupt가 났을 때 점프할 주소다.
코드 세그먼트 셀렉터GDT의 64비트 코드 세그먼트를 가리킨다(Long mode 글 참고).
타입과 플래그interrupt gate 또는 trap gate, present 비트 등이다.

5.4 handler

handler는 interrupt가 났을 때 실행되는 처리 루틴이다. 키 입력을 읽거나 타이머 틱을 세는 동작이 여기에 들어간다. 64비트 Long mode에서는 handler가 처리를 마친 뒤 iretq로 멈췄던 흐름으로 복귀한다.

5.5 PIC

PIC(Programmable Interrupt Controller)는 하드웨어 IRQ를 CPU에 전달하는 컨트롤러다. PIC가 보내는 번호는 옮길 수 있다. 이 작업을 리맵이라고 부르며 IRQ 번호를 32번부터로 옮겨 CPU 예외 번호와 겹치지 않게 한다.

PIC는 IRQ 라인별로 interrupt 전달을 막거나 허용하는 mask도 가진다. 타이머나 키보드 IRQ를 받으려면 해당 IRQ 라인의 mask를 해제해야 한다.

하드웨어 IRQ handler가 끝나면 PIC에 EOI(End Of Interrupt)를 보내야 한다. EOI를 보내지 않으면 PIC가 해당 IRQ 처리가 끝났다고 판단하지 못해 다음 IRQ 전달이 막힐 수 있다.

6. 동작 흐름

interrupt가 발생하면 CPU가 현재 상태를 스택에 저장하고 handler로 이동했다가 iretq로 멈췄던 자리로 돌아온다.

flowchart TD
    A["평소 실행
RIP=현재 명령"] B["IRQ 발생
CPU가 RIP/CS/RFLAGS 등 저장"] C["RIP ← IDT[번호]의 handler 주소"] D["handler 실행 (키 처리 등)"] EO["하드웨어 IRQ면 PIC에 EOI 전송"] E["iretq
저장한 상태 복구"] F["원래 명령으로 복귀"] A --> B --> C --> D --> EO --> E --> F

CPU는 interrupt가 나는 순간 돌아갈 위치와 플래그를 스택에 저장한다. 그다음 IDT에서 해당 번호의 handler 주소를 찾아 점프한다. 하드웨어 IRQ라면 handler가 처리를 마친 뒤 PIC에 EOI를 보내고 iretq로 저장한 상태를 복구해 멈췄던 명령부터 다시 실행한다.

이 동작은 함수 호출과 비슷해 보일 수 있지만 호출 주체가 다르다. 함수 호출은 코드가 직접 실행하고 interrupt는 외부 이벤트나 예외에 의해 CPU가 handler로 제어를 넘긴다.

7. 상태 변화

64비트 Long mode에서는 interrupt 진입 시 복귀에 필요한 값이 interrupt stack frame으로 저장된다. 커널 모드에서 커널 모드 handler로 들어가는 단순한 경우에는 RIP, CS, RFLAGS가 저장된다. 일부 예외는 error code를 추가로 push한다. privilege level 변경이나 IST 사용처럼 스택 전환이 발생하는 경우에는 SS와 RSP도 함께 저장될 수 있다.

스택 (높은 주소에서 낮은 주소로)저장된 값의미
원래 RSP 위이전 스택 내용저장 전 상태다.
RFLAGS플래그 값이다.
CS코드 세그먼트다.
새 RSPRIP돌아갈 명령 주소다.

iretq는 이 값들을 복구해 멈췄던 명령부터 다시 실행하게 한다. CPU가 자동으로 저장하는 값은 복귀에 필요한 최소 상태다. handler가 범용 레지스터를 사용한다면 직접 저장하고 복구해야 한다.

8. 코드나 설정에서 나타나는 형태

interrupt 개념은 IDT를 CPU에 등록하는 부분과 게이트 엔트리를 구성하는 부분에서 나타난다.

8.1 IDT 등록

다음 코드는 만든 IDT를 CPU에 등록하는 형태다.

lidt [idt_pointer]

lidt는 IDT의 위치를 IDTR에 등록하는 명령이다. GDT를 lgdt로 등록하는 것과 짝이 되는 동작이다. idt_pointer는 IDT의 크기와 시작 주소를 담은 구조체의 주소다. 이 등록은 interrupt를 받기 전에 끝나 있어야 하며 등록해 둬야 CPU가 interrupt가 났을 때 그 표를 참조한다.

8.2 게이트 엔트리의 타입

게이트 엔트리의 타입은 handler에 진입할 때 IF를 내리는지로 나뉜다.

타입동작
interrupt gatehandler로 진입할 때 IF를 내려 maskable hardware interrupt를 막는다.
trap gateIF를 유지하므로 handler 실행 중에도 maskable hardware interrupt를 받을 수 있다.

하드웨어 IRQ handler는 처리 중에 다른 maskable IRQ가 끼어드는 것을 막기 위해 보통 interrupt gate를 사용한다.

9. 자주 헷갈리는 지점

9.1 PIC 리맵을 빠뜨리면 번호가 겹친다

vector 0~31은 CPU 예외용으로 예약되어 있다. 하드웨어 IRQ를 그대로 두면 예외 번호와 겹친다. 그래서 PIC를 리맵해 IRQ를 32번부터로 옮겨야 한다. 리맵을 빠뜨리면 타이머 IRQ가 예외 번호로 들어와 엉뚱한 handler가 실행될 수 있다.

9.2 interrupt gate와 trap gate는 IF 처리가 다르다

interrupt gate는 handler에 진입할 때 IF를 내려 maskable hardware interrupt를 막는다. trap gate는 IF를 유지하므로 handler 실행 중에도 maskable hardware interrupt를 받을 수 있다. 하드웨어 IRQ handler는 처리 중에 다른 maskable IRQ가 끼어드는 것을 막기 위해 보통 interrupt gate를 쓴다.

9.3 interrupt는 코드가 호출하는 것이 아니다

interrupt 처리는 함수 호출과 비슷해 보이지만 코드가 직접 호출하는 것이 아니다. 외부 장치나 예외 상황이 발생하면 CPU가 IDT를 참조해 handler로 제어를 넘긴다. 그래서 handler는 언제 호출될지 예측하기 어렵고 멈췄던 흐름을 건드리지 않도록 작성해야 한다.

10. 확인 방법

interrupt 설정은 다음 항목으로 확인할 수 있다.

확인 대상보는 값판단 기준
IDT 등록IDTR 값과 IDT 엔트리의 handler 주소를 본다.IDTR이 의도한 IDT를 가리키고 엔트리에 handler 주소가 들어 있으면 IDT 등록 상태를 확인할 수 있다.
예외 handler 동작0으로 나누기 같은 예외를 일부러 발생시킨다.예외가 handler로 진입하면 IDT가 동작하는 상태다.
하드웨어 IRQ 수신PIT 타이머나 키보드 IRQ가 들어오는지 본다.handler가 실행되면 PIC 리맵, IRQ mask 해제, sti가 적용된 상태다.

확인 방법은 어떤 상태를 보면 되는지 정도만 적는다. 자세한 작업 절차는 운영 메뉴얼 쪽에서 다룬다.

11. 다음에 볼 개념

IDT 동작을 이해했다면 다음에는 PIC 리맵과 PIT 타이머를 봐야 한다. 하드웨어 IRQ를 받으려면 PIC가 보내는 번호를 32번부터로 옮기고 필요한 IRQ 라인의 mask를 해제한 뒤 PIT가 일정 간격으로 타이머 IRQ를 보내도록 설정하고 sti로 interrupt를 허용하는 과정을 이해해야 한다.

그다음에는 키보드 입력(PS/2)을 받는 흐름으로 이어진다. 키보드 IRQ가 들어오면 handler가 키 코드를 읽고 이를 문자로 바꿔 화면에 출력하는 단계로 확장한다.

부록 A. 참고 레퍼런스

주제출처
IDT 구조와 게이트 타입Interrupt Descriptor Table - OSDev Wiki
PIC 리맵과 IRQ 번호, EOI8259 PIC - OSDev Wiki
interrupt 처리를 포함한 Go 기반 OS 구현dmarro89/go-dav-os

부록 B. 다시 확인할 질문

질문답 요약관련 절
polling과 interrupt의 차이는?polling은 이벤트가 없어도 CPU를 계속 쓰고 interrupt는 이벤트가 있을 때만 쓴다.§2
하드웨어 IRQ를 받으려면 무엇이 필요한가?IDT 등록, PIC 리맵, 필요한 IRQ 라인의 mask 해제를 끝낸 뒤 sti로 RFLAGS.IF를 켜야 한다.§4
vector 0~31은 왜 따로 쓰는가?CPU 예외용으로 예약되어 있어 하드웨어 IRQ는 32번부터로 옮겨야 한다.§5.2
handler가 끝나면 무엇을 해야 하는가?하드웨어 IRQ면 PIC에 EOI를 보내고 iretq로 복귀한다. 범용 레지스터를 썼다면 직접 복구한다.§5.5, §7
interrupt gate와 trap gate의 차이는?interrupt gate는 진입 시 IF를 내려 maskable IRQ를 막고 trap gate는 IF를 유지한다.§9.2
interrupt는 코드가 호출하는가?아니다. 외부 장치나 예외가 발생하면 CPU가 IDT를 참조해 handler로 넘긴다.§9.3