인터럽트와 RTOS — ISR Context·Deferred Processing·FromISR API
#한 줄 요약
“ISR은 짧게, 일은 task에 맡깁니다.” Bottom-half 패턴이 RTOS와 OS의 공통 답입니다.
#ISR Context — task와 다른 세계
ISR이 실행 중일 때는 다음 규칙이 적용됩니다.
- 현재 task의 stack을 사용합니다 (또는 별도 ISR stack을 씁니다. Cortex-A나 RISC-V).
- scheduler가 비활성화됩니다 (preemption이 일어나지 않습니다).
- 다른 ISR만 nested로 가능합니다 (priority가 더 높은 경우).
- blocking이 불가능합니다 (semaphore wait 등은 절대 금지입니다).
void TIM1_IRQHandler(void) { // ✓ 가능: 변수 업데이트, FromISR API counter++; xSemaphoreGiveFromISR(sem, &woken);
// ✗ 절대 금지: blocking // xSemaphoreTake(sem, portMAX_DELAY); // CRASH // vTaskDelay(10); // CRASH // printf("..."); // mutex 내부 → block}#Bottom-Half / Top-Half 패턴
Linux 커널의 전통적 용어이고, RTOS에도 동일하게 적용됩니다.
#Top-Half (ISR)
- HW interrupt를 직접 처리합니다.
- 최소 작업만 합니다 (flag 설정, 데이터 캡처, signal task).
- 수 µs 안에 종료합니다.
#Bottom-Half (Task)
- 실제 데이터 처리, 로깅, 전송을 합니다.
- 일반 task이므로 RTOS의 모든 API를 사용할 수 있습니다.
- ms 단위 작업도 괜찮습니다.
#패턴 예 — UART 수신
// Top-Halfvoid USART1_IRQHandler(void) { BaseType_t woken = pdFALSE; uint8_t c = USART1->RDR; xQueueSendFromISR(rxQueue, &c, &woken); portYIELD_FROM_ISR(woken);}
// Bottom-Halfvoid uart_rx_task(void *arg) { uint8_t c; while (1) { xQueueReceive(rxQueue, &c, portMAX_DELAY); process_byte(c); // 복잡한 파싱·검증·로그 }}ISR에서는 1바이트만 큐에 넣고 끝냅니다. 모든 작업은 task에서 처리합니다.
#FromISR API — RTOS의 ISR-safe 변종
| Task용 | ISR용 |
|---|---|
xQueueSend | xQueueSendFromISR |
xQueueReceive | xQueueReceiveFromISR |
xSemaphoreGive | xSemaphoreGiveFromISR |
xSemaphoreTake | (ISR에서 사용 안 함) |
xTaskNotify | xTaskNotifyFromISR |
xEventGroupSetBits | xEventGroupSetBitsFromISR |
FromISR은 차이가 있습니다. blocking이 없고, scheduler를 호출하지 않으며, xHigherPriorityTaskWoken으로 지연 yield 신호를 전달합니다.
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(q, &data, &xHigherPriorityTaskWoken);// 위 호출이 higher-priority task를 ready로 만들면 pdTRUE
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);// pdTRUE면 ISR 끝나면서 task 전환#ISR Priority 설정 — Cortex-M
NVIC에서 IRQ priority를 설정합니다. 중요한 점은 FreeRTOS 호환 영역 안에서만 FromISR API를 사용할 수 있다는 것입니다.
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (5 << (8 - __NVIC_PRIO_BITS))#define configKERNEL_INTERRUPT_PRIORITY (15 << (8 - __NVIC_PRIO_BITS))
// 적용NVIC_SetPriority(USART1_IRQn, 6);// 6은 configMAX_SYSCALL_INTERRUPT_PRIORITY (5)보다 *낮음* (값이 클수록 낮음)// → FromISR API 사용 가능Cortex-M의 priority는 값이 작을수록 높습니다. 처음 만나면 헷갈리는 부분입니다.
#Priority 분리
- High priority (값 0-4): RTOS와 무관한 순수 HW critical 영역입니다 (모터 제어 ISR 등). FromISR API를 사용할 수 없습니다.
- Medium priority (값 5+): FromISR API를 사용할 수 있습니다. 일반 ISR입니다.
configKERNEL_INTERRUPT_PRIORITY(값 15): tick과 PendSV가 여기에 속합니다.
#ISR Latency
ISR이 trigger 되고 실제 ISR 코드 첫 줄이 실행될 때까지의 시간을 말합니다.
| 구간 | 시간 |
|---|---|
| HW interrupt 발생 → CPU 인지 | < 1 cycle |
| Pipeline flush | 2-12 cycle |
| Stack에 context 자동 push | 12 cycle (Cortex-M) |
| Vector table read + branch | 4-6 cycle |
| Total | ~12-25 cycle (~75-150 ns @ 168 MHz) |
Tail-chaining은 ISR끼리 연속 발생 시 context push/pop을 생략해 6-12 cycle을 절약합니다.
#ISR Storm 방지
interrupt가 너무 자주 발생하면 main loop가 굶게 됩니다.
#NAPI 패턴 (Linux 네트워킹)
void NIC_IRQHandler(void) { disable_nic_irq(); // 더 이상 IRQ 안 받음 notify_napi_task(); // poll mode로 전환}
void napi_task(void *arg) { while (1) { while (data_pending()) { read_packet(); // polling } enable_nic_irq(); // 다시 IRQ mode wait_for_signal(); }}IRQ에서 polling으로 전환해 부하 한계 안에서만 IRQ를 사용합니다.
#ISR과 Task 통신 메커니즘 비교
| 방법 | 처리량 | latency | 사용처 |
|---|---|---|---|
| Volatile flag | 수십 µs | 매우 빠름 | 단순 신호 (event 발생) |
| Atomic counter | µs | 빠름 | 이벤트 카운트 |
| Ring buffer | µs | 빠름 | 바이트 스트림 (UART RX) |
| Queue (RTOS) | µs-ms | 보통 | 구조체 메시지 |
| Semaphore Give | µs-ms | 보통 | ”이벤트 발생” 신호 |
| Task Notification | µs | 가장 빠름 (FreeRTOS) | task 직접 signal |
Task Notification이 가장 효율적입니다. queue나 semaphore의 overhead 없이 task의 notification value를 직접 update합니다.
// ISRxTaskNotifyFromISR(rx_task_handle, RX_EVENT, eSetBits, &woken);
// Taskuint32_t notif;xTaskNotifyWait(0, ULONG_MAX, ¬if, portMAX_DELAY);if (notif & RX_EVENT) { /* ... */ }#자주 하는 실수
⚠️ ISR에서 blocking API
xQueueSend()(FromISR이 아닌)를 호출하면 crash나 deadlock이 발생합니다. 항상 FromISR을 써야 합니다.
⚠️ Priority 설정 잘못
Cortex-M priority는 0이 제일 높습니다(값이 작을수록 높음). 다른 시스템(PowerPC, MIPS)은 반대입니다. 데이터시트를 확인해야 합니다.
⚠️ ISR이 너무 김
50 µs 이상 걸리면 다른 ISR이 막힙니다. 데이터 캡처만 하고 나머지는 task로 넘깁니다.
⚠️ ISR 사이 공유 변수 atomic 안 함
32-bit MCU에서 32-bit read와 write는 atomic이지만, 64-bit나 struct는 그렇지 않습니다. interrupt disable이나 atomic API를 사용해야 합니다.
#정리
- ISR은 task가 아니므로 blocking API를 사용할 수 없습니다.
- Top-Half (ISR) + Bottom-Half (task) 패턴이 표준입니다.
- FreeRTOS의 FromISR API,
xHigherPriorityTaskWoken,portYIELD_FROM_ISR()3종 세트를 사용합니다. - Cortex-M priority는 값이 작을수록 높습니다.
configMAX_SYSCALL_INTERRUPT_PRIORITY경계에 주의해야 합니다. - Task Notification이 가장 빠른 ISR과 task 간 통신 방법입니다.
다음 편에서는 동기화 기초를 다룹니다. Critical Section과 Mutual Exclusion을 살펴봅니다.
#관련 항목
Practical RTOS Internals · 6 of 53
- 1Practical RTOS Internals — 실시간 커널 내부 분석 시리즈 소개
- 2RTOS가 필요한 이유 — 일반 OS와의 결정적 차이
- 3Task와 Thread 개념 — TCB·상태 머신·생명 주기 분석
- 4실시간 스케줄링 알고리즘 비교 — RR·Priority·EDF·RMS
- 5Preemption과 Cooperation — 강제 전환 vs 자발 양보
- 6인터럽트와 RTOS — ISR Context·Deferred Processing·FromISR API
- 7동기화 기초 분석 — Critical Section·Mutual Exclusion·Race Condition
- 8Semaphore 개념 분해 — Counting·Binary·P/V 연산
- 9Mutex 개념 분해 — Ownership·Recursive·Priority Inheritance
- 10큐와 메시지 패싱 — Producer-Consumer·Ring Buffer·전달 의미
- 11실시간성 분석 — Latency·Jitter·Deadline·WCET·RMA
- 12Ready List 자료구조 분석 — Linked List·Bitmap·O(1) Scheduler
- 13Blocked List 자료구조 — Timeout 정렬·Delta List·Two-List Scheme
- 14Scheduler 알고리즘 구현 추적 — Next-Task Selection 로직
- 15Context Switch 원리 분석 — 레지스터 저장·복원·Stack Frame
- 16ARM Cortex-M Context Switch — PendSV·MSP/PSP 어셈블리 추적
- 17ARM Cortex-A Context Switch — Mode 전환·SVC·Banked Registers
- 18RISC-V Context Switch 분석 — ECALL·mret·CSR
- 19RTOS Tick과 타이머 — SysTick·Generic Timer·configTICK_RATE_HZ
- 20Tickless 모드 구현 — Idle Tick Suppression·Sleep·Wake 보정
- 21Scheduler Latency 측정 기법 — GPIO Toggle·DWT·ftrace·cyclictest
- 22RTOS Tracing과 Observability — Tracealyzer·SystemView·ITM/ETM
- 23Critical Section 구현 비교 — IRQ Disable·BASEPRI·Spinlock
- 24Semaphore 내부 구현 추적 — Counter·Wait List·ISR-Safe Variant
- 25Mutex 내부 구현 추적 — Owner·Recursion Count·ISR 금지
- 26Priority Inversion 문제 — Mars Pathfinder 사례·Bounded vs Unbounded
- 27Priority Inheritance 구현 — Inherit·Disinherit·Chain
- 28Priority Ceiling Protocol — Immediate vs Original 비교
- 29Queue 내부 구현 추적 — Ring Buffer·2 Wait Lists·Atomic Send/Receive
- 30Event Group 분석 — Bit Flag·AND/OR Wait·Sync Barrier
- 31ISR-Safe API 설계 — FromISR 패턴·Higher Priority Wake·Deferred Work
- 32Deadlock 분석 — 4 조건·Wait-for Graph·Lock Ordering·Timeout
- 33Stream Buffer와 Message Buffer — FreeRTOS 10의 Lock-Free SPSC
- 34실시간 메모리 요구사항 — Determinism·Fragmentation·WCET
- 35FreeRTOS Heap_1~5 분석 — 5종 Allocator의 구조와 트레이드오프
- 36TLSF Allocator 분석 — Two-Level Segregated Fit O(1)
- 37Static Allocation — 컴파일 타임으로 동적 위험 제거하기
- 38Memory Pool — Fixed-Size Block Allocator의 단순함과 강력함
- 39Stack Overflow 탐지 — Canary·MPU·Watermark 3중 방어
- 40SMP RTOS 설계 — Ready List·Affinity·IPI·Load Balancing
- 41SMP Spinlock 구현 — LDREX/STREX·Ticket Lock·MCS·WFE/SEV
- 42Software Timer 분석 — Daemon Task·자료구조·ISR-Safe API
- 43RTOS System Call — SVC·ECALL·User/Kernel 분리·FreeRTOS-MPU
- 44TrustZone과 TF-M — Secure/Non-Secure·NSC Veneer·PSA
- 45AMP와 OpenAMP — Heterogeneous SoC·RPMsg·remoteproc
- 46C++ in RTOS — RAII·std::thread·ETL·Coroutine
- 47FreeRTOS 소스 분석 — tasks.c·queue.c·port.c 추적
- 48Zephyr 커널 분석 — k_thread·k_sem·Driver Model
- 49RT-Thread 분석 — Object 모델·Components·Smart·Studio
- 50RTOS 포팅 가이드 — 새 아키텍처에 옮기는 절차
- 51RTOS 선택 가이드 — Footprint·License·Certification·Ecosystem
- 52Apache NuttX 분석 — POSIX·PX4·NASA Ingenuity
- 53PREEMPT_RT Linux — Mainline 6.12·Xenomai 4·EVL
관련 글
ISR-Safe API 설계 — FromISR 패턴·Higher Priority Wake·Deferred Work
FromISR API 내부 구조와 pxHigherPriorityTaskWoken, yield 결정, deferred work 패턴을 정리합니다.
C++ in RTOS — RAII·std::thread·ETL·Coroutine
RTOS C API를 C++ 객체로 감싸는 패턴을 정리합니다. RAII MutexGuard와 ScopedIRQDisable, std::thread/std::mutex의 한계와 직접 xTaskCreate가 결정성을 갖는 이유, ETL로 STL을 대체하는 법, C++20 coroutine을 RTOS 위에 얹는 방식까지 다룹니다.
Preemption과 Cooperation — 강제 전환 vs 자발 양보
Preemptive는 tick과 IRQ에서 강제로 전환합니다. Cooperative는 yield를 명시해야 합니다. latency와 predictability의 trade-off를 다룹니다.
이 글을 참조하는 글 (5)
- Stack Overflow 탐지 — Canary·MPU·Watermark 3중 방어— Practical RTOS Internals
- ARM Cortex-M Context Switch — PendSV·MSP/PSP 어셈블리 추적— Practical RTOS Internals
- 동기화 기초 분석 — Critical Section·Mutual Exclusion·Race Condition— Practical RTOS Internals
- Preemption과 Cooperation — 강제 전환 vs 자발 양보— Practical RTOS Internals
- Cortex-M 인터럽트 핸들링 — NVIC·Priority·Vector·EXTI— Modern Embedded Recipes