지노랩 /JinoLab

인터럽트 안에서 …FromISR() 버전을 꼭 써야 하는 이유 본문

임베디드 시스템/RTOS

인터럽트 안에서 …FromISR() 버전을 꼭 써야 하는 이유

지노랩/JinoLab 2025. 6. 27. 11:14

FreeRTOS의 “ISR-Safe API” 완전 정복 가이드


1. 두 개의 세계, 두 개의 규칙  

구분 Task Context (Thread Mode) Interrupt Context (Handler Mode)
실행 시점 스케줄러가 선택한 뒤 CPU에서 실행 하드웨어 이벤트가 즉시 CPU를 점유
가능 동작 스케줄러에게 “나 잠시 Block 시켜” 요청 가능 (예: vTaskDelay(), xQueueReceive()) Block 금지 — 인터럽트 루틴은 짧고 빠르게 끝나야 함
API 이름 일반 FreeRTOS API …FromISR() 접미사 API

핵심: 인터럽트 안에서는 “Block 동작”이 불가능하다.
따라서 동일 API를 공용으로 쓰면, 내부에서 “지금 Task냐 ISR이냐” 를 매번 분기해야 하고, 일부 파라미터가 무용지물이 된다.


2. …FromISR() 버전이 존재하는 3가지 이유 

이유 상세 설명
① 구현 단순화 Task 모드 전용 API는 “Block 또는 스케줄링” 로직만 포함ISR 전용 API는 “큐/세마포어 조작 + 컨텍스트 전환 요청”만 담당
② 불필요한 인자 제거 xSemaphoreTake()는 time-out 파라미터 필요 → ISR에서는 쓸모없음xSemaphoreTakeFromISR()는 해당 인자 제거로 API 시그니처 간결
③ 아키텍처 의존성 제거 어떤 MCU는 “현재 모드가 ISR인지 확인” 기능이 없음 → API를 분리하면 포팅이 쉬워진다

3. 직접 써 보면 더 선명해지는 차이

(1) Task 전용 버전

void vSenderTask(void *arg)
{
    for (;;)
    {
        xQueueSend(xLogQ, &data, portMAX_DELAY);  // 큐가 꽉 차면 Block
    }
}

(2) ISR 전용 버전

void USART1_IRQHandler(void)
{
    BaseType_t xHigherWoken = pdFALSE;
    uint8_t ch = USART1->DR;                 // 수신 바이트

    xQueueSendFromISR(xLogQ, &ch, &xHigherWoken);

    // 필요하다면 PendSV 트리거
    portYIELD_FROM_ISR(xHigherWoken);
}
  • portMAX_DELAY 같은 인자는 ISR 버전엔 존재하지 않는다.
  • BaseType_t *pxHigherPriorityTaskWoken 인자는 ISR 종료 직전 컨텍스트 스위치 필요 여부를 알린다.

4. “3rd-party 코드가 API를 호출해 버리는데요?”

외부 라이브러리 함수가 일반 FreeRTOS API를 내부에서 사용한다면 ISR 안에서 직접 호출하면 안 된다.

해결 패턴

  1. ISR은 데이터만 큐/버퍼에 넣고 즉시 반환
  2. 전용 Task가 깨어나 라이브러리 함수를 호출해 후처리
void ADC_IRQHandler(void)
{
    BaseType_t xHigherWoken = pdFALSE;
    uint16_t raw = ADC1->DR;

    xQueueSendFromISR(xAdcQ, &raw, &xHigherWoken);
    portYIELD_FROM_ISR(xHigherWoken);
}

void vAdcTask(void *arg)
{
    uint16_t raw;
    for (;;)
    {
        xQueueReceive(xAdcQ, &raw, portMAX_DELAY); // Block 대기
        ThirdParty_Filter(raw);                    // ISR-safe 아님 → 여기서 호출
    }
}

5. 사용 시 체크리스트 ✅

  1. ISR에서 일반 API 호출 금지 (configASSERT()가 잡아낼 수도, HardFault가 날 수도!)
  2. BaseType_t xHigherPriorityTaskWoken 파라미터 NULL로 두지 말 것 – 컨텍스트 스위치 누락 위험
  3. configMAX_SYSCALL_INTERRUPT_PRIORITY 보다 우선순위가 낮은(숫자가 큰) ISR 에서만 호출

6. 결론

“인터럽트 안에서는 무조건 …FromISR() 버전만!”

이렇게 규칙을 분리하면 코드가 가독성·안정성·이식성 모두 좋아집니다.
ISR은 가볍게, Task는 여유 있게 — FreeRTOS가 의도한 ‘RTOS다운’ 구조를 그대로 살려보세요!