본문으로 건너뛰기
Practical RTOS Internals · 5/53

인터럽트와 RTOS — ISR Context·Deferred Processing·FromISR API

· Hawk · 5분 읽기

#한 줄 요약

“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 패턴

Top-Half (ISR) + Bottom-Half (Task) pattern

Linux 커널의 전통적 용어이고, RTOS에도 동일하게 적용됩니다.

#Top-Half (ISR)

  • HW interrupt를 직접 처리합니다.
  • 최소 작업만 합니다 (flag 설정, 데이터 캡처, signal task).
  • 수 µs 안에 종료합니다.

#Bottom-Half (Task)

  • 실제 데이터 처리, 로깅, 전송을 합니다.
  • 일반 task이므로 RTOS의 모든 API를 사용할 수 있습니다.
  • ms 단위 작업도 괜찮습니다.

#패턴 예 — UART 수신

// Top-Half
void USART1_IRQHandler(void) {
BaseType_t woken = pdFALSE;
uint8_t c = USART1->RDR;
xQueueSendFromISR(rxQueue, &c, &woken);
portYIELD_FROM_ISR(woken);
}
// Bottom-Half
void 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용
xQueueSendxQueueSendFromISR
xQueueReceivexQueueReceiveFromISR
xSemaphoreGivexSemaphoreGiveFromISR
xSemaphoreTake(ISR에서 사용 안 함)
xTaskNotifyxTaskNotifyFromISR
xEventGroupSetBitsxEventGroupSetBitsFromISR

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를 사용할 수 있다는 것입니다.

FreeRTOSConfig.h
#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 flush2-12 cycle
Stack에 context 자동 push12 cycle (Cortex-M)
Vector table read + branch4-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합니다.

// ISR
xTaskNotifyFromISR(rx_task_handle, RX_EVENT, eSetBits, &woken);
// Task
uint32_t notif;
xTaskNotifyWait(0, ULONG_MAX, &notif, 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

  1. 1Practical RTOS Internals — 실시간 커널 내부 분석 시리즈 소개
  2. 2RTOS가 필요한 이유 — 일반 OS와의 결정적 차이
  3. 3Task와 Thread 개념 — TCB·상태 머신·생명 주기 분석
  4. 4실시간 스케줄링 알고리즘 비교 — RR·Priority·EDF·RMS
  5. 5Preemption과 Cooperation — 강제 전환 vs 자발 양보
  6. 6인터럽트와 RTOS — ISR Context·Deferred Processing·FromISR API
  7. 7동기화 기초 분석 — Critical Section·Mutual Exclusion·Race Condition
  8. 8Semaphore 개념 분해 — Counting·Binary·P/V 연산
  9. 9Mutex 개념 분해 — Ownership·Recursive·Priority Inheritance
  10. 10큐와 메시지 패싱 — Producer-Consumer·Ring Buffer·전달 의미
  11. 11실시간성 분석 — Latency·Jitter·Deadline·WCET·RMA
  12. 12Ready List 자료구조 분석 — Linked List·Bitmap·O(1) Scheduler
  13. 13Blocked List 자료구조 — Timeout 정렬·Delta List·Two-List Scheme
  14. 14Scheduler 알고리즘 구현 추적 — Next-Task Selection 로직
  15. 15Context Switch 원리 분석 — 레지스터 저장·복원·Stack Frame
  16. 16ARM Cortex-M Context Switch — PendSV·MSP/PSP 어셈블리 추적
  17. 17ARM Cortex-A Context Switch — Mode 전환·SVC·Banked Registers
  18. 18RISC-V Context Switch 분석 — ECALL·mret·CSR
  19. 19RTOS Tick과 타이머 — SysTick·Generic Timer·configTICK_RATE_HZ
  20. 20Tickless 모드 구현 — Idle Tick Suppression·Sleep·Wake 보정
  21. 21Scheduler Latency 측정 기법 — GPIO Toggle·DWT·ftrace·cyclictest
  22. 22RTOS Tracing과 Observability — Tracealyzer·SystemView·ITM/ETM
  23. 23Critical Section 구현 비교 — IRQ Disable·BASEPRI·Spinlock
  24. 24Semaphore 내부 구현 추적 — Counter·Wait List·ISR-Safe Variant
  25. 25Mutex 내부 구현 추적 — Owner·Recursion Count·ISR 금지
  26. 26Priority Inversion 문제 — Mars Pathfinder 사례·Bounded vs Unbounded
  27. 27Priority Inheritance 구현 — Inherit·Disinherit·Chain
  28. 28Priority Ceiling Protocol — Immediate vs Original 비교
  29. 29Queue 내부 구현 추적 — Ring Buffer·2 Wait Lists·Atomic Send/Receive
  30. 30Event Group 분석 — Bit Flag·AND/OR Wait·Sync Barrier
  31. 31ISR-Safe API 설계 — FromISR 패턴·Higher Priority Wake·Deferred Work
  32. 32Deadlock 분석 — 4 조건·Wait-for Graph·Lock Ordering·Timeout
  33. 33Stream Buffer와 Message Buffer — FreeRTOS 10의 Lock-Free SPSC
  34. 34실시간 메모리 요구사항 — Determinism·Fragmentation·WCET
  35. 35FreeRTOS Heap_1~5 분석 — 5종 Allocator의 구조와 트레이드오프
  36. 36TLSF Allocator 분석 — Two-Level Segregated Fit O(1)
  37. 37Static Allocation — 컴파일 타임으로 동적 위험 제거하기
  38. 38Memory Pool — Fixed-Size Block Allocator의 단순함과 강력함
  39. 39Stack Overflow 탐지 — Canary·MPU·Watermark 3중 방어
  40. 40SMP RTOS 설계 — Ready List·Affinity·IPI·Load Balancing
  41. 41SMP Spinlock 구현 — LDREX/STREX·Ticket Lock·MCS·WFE/SEV
  42. 42Software Timer 분석 — Daemon Task·자료구조·ISR-Safe API
  43. 43RTOS System Call — SVC·ECALL·User/Kernel 분리·FreeRTOS-MPU
  44. 44TrustZone과 TF-M — Secure/Non-Secure·NSC Veneer·PSA
  45. 45AMP와 OpenAMP — Heterogeneous SoC·RPMsg·remoteproc
  46. 46C++ in RTOS — RAII·std::thread·ETL·Coroutine
  47. 47FreeRTOS 소스 분석 — tasks.c·queue.c·port.c 추적
  48. 48Zephyr 커널 분석 — k_thread·k_sem·Driver Model
  49. 49RT-Thread 분석 — Object 모델·Components·Smart·Studio
  50. 50RTOS 포팅 가이드 — 새 아키텍처에 옮기는 절차
  51. 51RTOS 선택 가이드 — Footprint·License·Certification·Ecosystem
  52. 52Apache NuttX 분석 — POSIX·PX4·NASA Ingenuity
  53. 53PREEMPT_RT Linux — Mainline 6.12·Xenomai 4·EVL