인터럽트 (Interrupt)
인터럽트는 프로그램을 실행하는 도중에 먼저 처리해야 할 일이 생겼을 때, 하던 일을 잠시 멈추고 그 일을 먼저 처리하라고 CPU에게 알리는 신호입니다. 급한 일을 처리하고 나면 CPU는 멈췄던 자리로 돌아가 하던 일을 계속합니다.
한눈에 보기
- 지금 하는 일보다 더 중요한 일(입출력 완료, 오류, 우선순위가 높은 연산 등)이 생기면, 그 일을 먼저 처리한 뒤 원래 일로 돌아와야 합니다.
- 비유: 책을 읽다가 초인종이 울리는 상황과 같습니다.
- 읽던 페이지에 책갈피를 꽂습니다. (지금 상태 저장)
- 문을 열고 택배를 받습니다. (인터럽트 처리)
- 책갈피 자리로 돌아와 이어서 읽습니다. (상태 복원 후 재개)
- 초인종이 없다면 택배가 왔는지 알기 위해 몇 분마다 현관에 나가 봐야 합니다. 이렇게 계속 확인하는 방식을 폴링(Polling) 이라고 하며, 아래에서 인터럽트와 비교합니다.
인터럽트의 종류
인터럽트는 어디서 생기느냐에 따라 세 가지로 나눕니다.
- 외부 인터럽트와 내부 인터럽트는 하드웨어 신호로 발생합니다. 외부 인터럽트는 CPU 바깥의 장치가 보내는 신호이고, 내부 인터럽트는 CPU가 명령어를 실행하다가 스스로 문제를 발견해서 생깁니다.
- 소프트웨어 인터럽트는 프로그램이 특정 명령어를 실행해서 일부러 발생시킵니다.
외부 인터럽트
입출력 장치, 타이머 같은 타이밍 장치, 전원 등 CPU 바깥의 요인으로 발생합니다.
- 전원 이상 (정전 등)
- 기계 착오 (하드웨어 고장)
- 외부 신호 (타이머가 정해진 시간이 됐다고 알림, 운영자가 키를 누름)
- 입출력 (키보드 입력이 들어옴, 디스크 읽기가 끝남)
내부 인터럽트 (예외, Exception)
잘못된 명령이나 잘못된 데이터를 사용할 때 CPU 안에서 발생합니다. 교재에 따라 트랩(Trap) 또는 예외(Exception)라고 부릅니다.
- 0으로 나누기
- 오버플로우 (값이 담을 수 있는 범위를 넘음)
- 명령어를 잘못 사용한 경우 (존재하지 않는 명령, 권한 없는 명령)
- 접근하면 안 되는 메모리 주소에 접근한 경우
소프트웨어 인터럽트 (SVC 인터럽트)
프로그램이 명령어로 직접 요청해서 발생하는 인터럽트입니다. SVC는 Supervisor Call(감독자 호출)의 줄임말입니다.
- 프로그램이 파일 읽기, 새 프로그램 실행, 메모리 할당처럼 운영체제의 도움이 필요한 일을 부탁할 때 발생합니다. 이것이 바로 시스템 콜이 동작하는 방식입니다.
- 예를 들어 프로그램을 쓰다가 다른 프로그램을 실행하면, 운영체제가 새 프로세스에 자원을 할당하고 여러 프로세스가 CPU를 나눠 쓰도록(시분할) 처리하는 동작이 수행됩니다.
용어 주의: "트랩"이라는 이름은 교재마다 쓰임이 다릅니다. 내부 인터럽트(예외)를 트랩이라고 부르는 책도 있고, 시스템 콜처럼 프로그램이 일부러 일으키는 소프트웨어 인터럽트를 트랩이라고 부르는 책도 있습니다. 면접에서는 "어디서, 왜 발생했는가"로 구분해 설명하면 안전합니다.
| 종류 | 발생 원인 | 예 |
|---|---|---|
| 외부 인터럽트 | CPU 바깥 장치의 하드웨어 신호 | 키보드 입력, 디스크 읽기 완료, 타이머, 정전 |
| 내부 인터럽트 (예외) | 명령어 실행 중 CPU가 발견한 오류 | 0으로 나누기, 오버플로우, 잘못된 명령 |
| 소프트웨어 인터럽트 | 프로그램이 명령어로 직접 요청 | 시스템 콜 (파일 읽기, 프로세스 생성) |
인터럽트 처리 과정
주 프로그램 실행 중
│
▼
① 인터럽트 발생 ───────────────────────────┐
│ │
② 지금 하던 명령까지만 마치고 멈춤 │
│ │
③ 상태 레지스터, PC(다음 명령 주소) 등을 │
스택에 잠시 저장 (책갈피 꽂기) │
│ │
④ 인터럽트 서비스 루틴(ISR) 실행 ◀─────────┘
(택배 받기)
│
⑤ 스택에 저장해 둔 값을 복원
│
▼
⑥ 멈췄던 자리부터 주 프로그램 다시 실행- 주 프로그램이 실행되다가 인터럽트가 발생합니다.
- CPU는 지금 실행 중인 명령어까지만 마치고 프로그램을 멈춥니다.
- 상태 레지스터와 PC(Program Counter, 다음에 실행할 명령어의 주소) 등을 스택에 잠시 저장합니다.
- 잠시 저장하는 이유는, 인터럽트 처리가 끝난 뒤 원래 작업으로 정확히 돌아와야 하기 때문입니다.
- 그 인터럽트를 처리하는 전용 코드인 인터럽트 서비스 루틴(ISR, Interrupt Service Routine) 으로 이동해 실행합니다. 인터럽트 종류마다 어떤 ISR을 실행할지는 인터럽트 벡터 테이블이라는 표에 정리되어 있습니다.
- ISR이 끝나면 저장해 둔 값을 복원합니다.
- 멈췄던 자리부터 주 프로그램을 다시 실행합니다.
같은 과정을 그림으로 보면 다음과 같습니다. 복귀 주소를 저장하고(③), 인터럽트 벡터에서 처리 루틴 위치를 찾아 이동한 뒤(④), 처리가 끝나면 복귀 주소를 다시 불러와(⑦) 원래 자리로 돌아갑니다(⑧).

그림 출처: gyoogle 기술 면접 정리, Interrupt
상태를 저장하고 복원한다는 점에서, 프로세스를 바꿔 끼우는 컨텍스트 스위칭과 닮았습니다. 실제로 타이머 인터럽트는 운영체제가 프로세스를 교체하는 대표적인 계기입니다.
폴링 vs 인터럽트
인터럽트 기능이 없다면, 컨트롤러는 어떤 일을 해야 할 시점을 알기 위해 계속 상태를 확인해야 합니다. 이것을 폴링(Polling) 이라고 합니다. 폴링을 하는 동안에는 원래 하던 일에 집중할 수 없어서, 많은 기능을 제대로 수행하지 못하는 단점이 있습니다.
정리하면, 컨트롤러(MCU, 마이크로컨트롤러 같은 작은 컴퓨터 칩)가 입력을 알아채고 어떤 일을 먼저 할지(우선순위) 정하는 방법은 두 가지입니다.
폴링 방식
- 프로그램이 명령어를 사용해 입력 핀의 값을 계속 읽어서 변화를 알아내는 방식입니다.
- 우선순위를 정할 때도, 인터럽트 요청 플래그(장치가 "저 요청 있어요" 하고 세워 두는 표시)를 소프트웨어로 하나씩 차례로 확인해서 우선순위가 가장 높은 요청을 찾고, 그에 맞는 서비스 루틴을 실행합니다.
- 하드웨어 방식보다 속도가 느립니다.
인터럽트 방식
- 컨트롤러 자체가 하드웨어적으로 변화를 감지하고, 변화가 있을 때만 정해진 동작을 하는 방식입니다.
- 여러 장치가 동시에 인터럽트를 요청하면 하드웨어가 우선순위를 정합니다. 대표적인 방법은 두 가지입니다.
| 방법 | 동작 | 비유 |
|---|---|---|
| 데이지 체인 (Daisy Chain) | 장치들을 우선순위 순서대로 한 줄로 직렬 연결. CPU의 응답 신호가 앞 장치부터 차례로 전달되어, 요청한 장치 중 가장 앞(우선순위가 높은) 장치가 먼저 처리됨 | 줄 서 있는 사람들에게 앞에서부터 차례로 "용건 있어요?" 하고 물어보기 |
| 병렬 우선순위 부여 | 장치마다 따로 선을 연결하고, 우선순위 판별 회로가 동시에 비교해서 가장 높은 요청을 고름 | 모든 사람이 동시에 번호표를 내밀면 가장 작은 번호를 바로 고르기 |
둘의 비교
| 항목 | 폴링 | 인터럽트 |
|---|---|---|
| 변화 감지 | 프로그램이 계속 직접 확인 | 하드웨어가 감지해서 알려 줌 |
| CPU 낭비 | 확인하는 동안 다른 일을 못 함 | 일이 생겼을 때만 처리 |
| 반응 속도 | 확인 주기만큼 늦을 수 있음 | 즉시 반응 |
| 필요 조건 | 특별한 하드웨어 없이 가능 | 하드웨어 지원이 필요 |
인터럽트 방식은 하드웨어 지원이 필요하다는 제약이 있지만, 폴링보다 훨씬 빠르게 대응할 수 있습니다. 그래서 실시간 대응이 필요한 곳에서는 꼭 필요한 기능입니다.
즉, 인터럽트는 언제 일어날지 예측하기 어려운 일에 가장 빠르게 대응하는 방법입니다.
코드로 감 잡기: 프로세스가 받는 "알림"
진짜 하드웨어 인터럽트는 운영체제 커널 안에서 처리되어 일반 프로그램에서는 직접 볼 수 없습니다. 대신 운영체제가 프로세스에게 보내는 시그널(Signal) 은 "하던 일을 멈추고, 등록해 둔 함수를 실행한 뒤, 원래 자리로 돌아간다"는 점에서 인터럽트와 똑같은 흐름을 가집니다. 그래서 시그널을 "소프트웨어 버전의 인터럽트"라고 부르기도 합니다. (시그널은 메모리 누수와 OOM 글의 SIGKILL 부분에서도 다룹니다.)
import signal, time
def on_alarm(signum, frame): # 알림이 오면 실행될 함수 (서비스 루틴 역할)
print(" [알림 처리] 1초가 지났어요!")
signal.signal(signal.SIGALRM, on_alarm) # "이 알림이 오면 저 함수를 실행해 줘" 등록
signal.alarm(1) # 1초 뒤에 알림이 오도록 예약
for i in range(1, 5): # 원래 하던 일
print(f"하던 일 {i}")
time.sleep(0.4)
print("하던 일 끝")실행 결과 (macOS, Linux에서 실행):
하던 일 1
하던 일 2
하던 일 3
[알림 처리] 1초가 지났어요!
하던 일 4
하던 일 끝- 프로그램은 1초가 지났는지 한 번도 확인하지 않았습니다. (폴링 없음)
- 1초가 되자 하던 일 중간에 끼어들어
on_alarm이 실행되고, 끝나자 원래 반복문으로 돌아와 "하던 일 4"를 이어서 했습니다.
정리
| 개념 | 비전공자 버전 요약 |
|---|---|
| 인터럽트 | 하던 일을 멈추고 급한 일부터 처리하라는 초인종 |
| 외부 인터럽트 | 키보드, 디스크, 타이머, 전원 같은 바깥 장치가 누르는 초인종 |
| 내부 인터럽트 (예외) | 0으로 나누기처럼 실행 중 문제가 생겨 CPU가 스스로 멈춤 |
| 소프트웨어 인터럽트 | 프로그램이 운영체제에게 일부러 도움을 요청 (시스템 콜) |
| 상태 저장과 복원 | 책갈피를 꽂아 두고 일을 처리한 뒤 그 자리로 돌아옴 |
| 인터럽트 서비스 루틴 (ISR) | 각 초인종에 맞춰 미리 정해 둔 처리 방법 |
| 폴링 | 택배 왔는지 몇 분마다 현관에 나가 보는 방식. 느리고 낭비가 큼 |
| 데이지 체인 / 병렬 우선순위 | 여러 요청이 겹칠 때 하드웨어가 누구부터 처리할지 고르는 방법 |
