외부 사건을 감지하는 두 가지 방식.
- 폴링 — : CPU가 주기적으로 "준비됐니?" 하고 물어본다 인터럽트 : 하드웨어가 "준비됐어!" 하고 CPU를 부른다
코드로 보는 차이
// 폴링
while (1) {
if (UART->SR & RXNE) { handle(UART->DR); }
do_other_work();
}
// 인터럽트
void USART1_IRQHandler(void) { handle(UART->DR); }
int main(void) { while (1) { do_other_work(); } }
비교
| 폴링 | 인터럽트 | |
|---|---|---|
| CPU 낭비 | 크다 (없는 사건도 계속 확인) | 없다 |
| 응답 지연 | 폴링 주기에 좌우 | 즉시 |
| 저전력 | ✗ (계속 깨어 있어야) | ○ (WFI로 자다가 깨어남) |
| 코드 흐름 | 선형. 이해하기 쉽다 | 비동기. 추론이 어렵다 |
| 동시성 문제 | 없다 | 경쟁 조건·재진입 |
| 타이밍 예측 | 쉽다 | 어렵다 (언제 끼어들지 모름) |
| 오버헤드 | 확인 비용 × 횟수 | 진입·복귀 비용 × 발생 횟수 |
폴링이 유리한 경우 — 의외로 많다
① 사건이 매우 빈번할 때
- 1μs마다 데이터가 오는데 인터럽트를 쓰면?
- 진입·복귀 오버헤드(약 12+12 사이클)가 처리 시간보다 크다
- 차라리 타이트 루프로 폴링하는 것이 빠르다
고속 SPI 전송이나 비트뱅잉에서 실제로 폴링을 쓴다.
② 타이밍이 결정적이어야 할 때
인터럽트는 "언제 올지" 모른다 → 지터 발생 폴링 루프는 매 사이클이 예측 가능하다
③ 아주 짧은 대기
while (!(SPI->SR & TXE)) { } // 몇 사이클이면 끝난다
인터럽트를 설정하고 되돌아오는 비용이 대기 시간보다 크다.
④ 초기화 코드 부팅 중에는 어차피 할 일이 없고, 순서대로 진행되는 편이 명확하다.
인터럽트가 유리한 경우
✅ 사건이 드물고 언제 올지 모른다 (버튼, 통신 수신) ✅ 응답 지연이 중요하다 (모터 제어, 안전 정지) ✅ 저전력이 중요하다 (배터리 기기) ✅ CPU가 다른 일을 해야 한다
저전력 관점 — 배터리 기기에서 결정적
while (1) {
if (!has_work()) __WFI(); // Wait For Interrupt — 잠든다
process();
}
폴링은 잠들 수가 없다. 확인하려면 깨어 있어야 하기 때문이다. 인터럽트 기반이면 대부분의 시간을 슬립 모드에서 보내 소비 전류를 mA에서 μA 단위로 낮출 수 있다.
배터리로 수년을 버티는 IoT 기기는 인터럽트 없이는 불가능하다.
실무의 정답 — 섞어 쓴다
- ① 인터럽트로 감지하고 → 플래그만 세우고 즉시 리턴
- ② 메인 루프가 플래그를 폴링해서 실제 처리
volatile uint8_t rx_ready = 0;
volatile uint8_t rx_data;
void USART1_IRQHandler(void) { // 짧게
rx_data = USART1->DR;
rx_ready = 1;
}
int main(void) {
while (1) {
if (rx_ready) { // 여유 있을 때 처리
rx_ready = 0;
parse(rx_data); // 오래 걸려도 괜찮다
}
__WFI();
}
}
인터럽트의 즉시성과 메인 루프의 안전성을 모두 얻는다. 이것이 deferred 처리이며 임베디드의 표준 패턴이다.
DMA — 제3의 선택지
- 폴링 — : CPU가 매번 확인하고 옮긴다
- 인터럽트 : 사건마다 CPU가 잠깐 개입한다
- DMA — : CPU가 아예 개입하지 않는다 ← 대량 전송의 답
대량 연속 데이터는 DMA + 완료 인터럽트가 정석이다. 1KB를 인터럽트로 받으면 1024번 진입하지만, DMA는 1번이면 된다.
면접 답변 골격
"사건의 빈도와 응답 요구로 판단합니다. 드물고 즉시성이 필요하면 인터럽트, 매우 빈번하거나 타이밍이 결정적이어야 하면 폴링, 대량 연속 데이터는 DMA를 씁니다. 실무에서는 인터럽트로 감지하고 실제 처리는 메인 루프로 미루는 방식을 기본으로 씁니다."