연속된 인터럽트 사이에서 복원과 저장을 생략하는 Cortex-M의 최적화 (tail-chaining).
없다면 어떻게 되나
인터럽트 A 처리 완료
→ 스택에서 레지스터 8개 복원 (pop, 약 12사이클)
→ 메인 코드로 복귀
→ 대기 중이던 인터럽트 B 발견
→ 다시 레지스터 8개 저장 (push, 약 12사이클)
→ B 핸들러 진입
= 24사이클을 낭비. 게다가 스택에는 같은 내용이 다시 쌓인다
테일체이닝
인터럽트 A 처리 완료
→ 대기 중인 B가 있는지 확인
→ 있다! 스택 프레임을 그대로 두고 벡터만 새로 인출
→ 바로 B 핸들러 진입 (약 6사이클)
"어차피 다시 쌓을 거면 그대로 두자" 는 발상이다. 스택에 쌓인 내용은 메인 코드의 컨텍스트이고, A든 B든 그것을 보존하기만 하면 되므로 중간에 복원할 이유가 없다.
사이클 비교
- 일반 진입 — : 12 사이클
- 테일체이닝 진입 — : 6 사이클 ← 절반
- 복원 없이 연결하므로 총 절약은 약 18~24 사이클
인터럽트가 자주 몰리는 시스템에서 누적 효과가 크다.
함께 알아야 할 최적화 두 가지
① 늦은 도착 (late arrival)
- 인터럽트 A의 스태킹이 진행 중
- 더 높은 우선순위 B가 도착
- 스태킹은 이미 시작됐으니 버리지 않고
- 벡터만 B의 것으로 바꿔서 B를 먼저 처리
- A는 pending 으로 남았다가 나중에 테일체이닝으로 처리
② 선점 (preemption)
A 핸들러 실행 중
→ 더 높은 우선순위 B 도착
→ A의 컨텍스트를 스태킹하고 B로 진입 (중첩)
→ B 완료 후 A 재개
셋의 차이
테일체이닝 : A가 "끝난 뒤" B로 → 스택 재사용
늦은 도착 : A가 "시작 전" B로 → 스태킹 재사용
선점 : A가 "실행 중" B로 → 스택이 쌓임 (중첩)
왜 이런 최적화가 가능한가
자동 스태킹이 하드웨어로 구현되어 있기 때문이다. 소프트웨어가 저장·복원한다면 하드웨어가 "이번엔 생략하자"고 판단할 수 없다.
Cortex-M의 예외 모델이 통째로 하드웨어에 들어 있는 것이 이 최적화들의 전제 조건이다.
실무에서의 의미
① 인터럽트 부하 계산
초당 10000번 인터럽트, 각 처리 1μs 라면 일반 진입 : (12 + 실행 + 12복원) 사이클 × 10000 테일체이닝이 걸리면 오버헤드가 크게 준다
② 짧은 ISR을 여러 개 두는 설계가 유리해진다
하나의 큰 ISR에서 여러 원인을 처리하는 것보다, 원인별로 짧은 ISR을 두는 편이 우선순위 제어도 되고 오버헤드도 작다.
③ 그래도 ISR은 짧아야 한다
테일체이닝이 진입 비용을 줄여 주지만, ISR 실행 시간 자체는 줄여 주지 않는다. 긴 ISR은 여전히 다른 인터럽트를 지연시킨다.
확인 방법
오실로스코프로 두 인터럽트의 디버그 핀을 함께 관찰하면 A 종료와 B 시작 사이의 간격이 매우 짧은 것을 볼 수 있다
메인 코드로 복귀하는 흔적 없이 곧바로 이어지면 테일체이닝이 동작한 것이다.
면접에서
"Cortex-M은 예외 진입·복귀를 하드웨어로 처리하기 때문에 테일체이닝, 늦은 도착 같은 최적화가 가능하고, 그 덕에 인터럽트 지연이 12사이클로 짧고 일정합니다. 이 예측 가능성이 실시간 시스템에서 중요합니다."
"짧다"보다 "일정하다"를 강조하는 편이 이해도를 보여 준다.