Single-Core & ISR Synchronization

Table of contents

핵심 요약

단일 코어는 한 순간에 하나의 명령 흐름을 실행해도 task 선점과 ISR 진입 때문에 Shared State가 손상될 수 있다. counter++는 보통 읽기·증가·쓰기이며 중간에 선점되면 증가분을 잃는다.

Lost Update: Task Preempted by ISR

sequenceDiagram
 participant T as Task
 participant S as Shared counter
 participant I as ISR
 T->>S: read counter = 0
 Note over T,I: IRQ 발생: Task 선점
 I->>S: read 0, increment, write 1
 Note over T,I: ISR 종료: Task 재개
 T->>S: 이전 값 0에 1을 더해 write 1
 Note over S: 두 번 증가했지만 결과는 1

단일 코어에서도 read-modify-write는 중간에 선점될 수 있다. 해당 플랫폼의 짧은 Critical Section 또는 ISR-safe임을 확인한 Atomic operation을 사용한다. volatile만으로 해결되지 않는다.

초기 counter=0
Task: counter 읽음 → 0
ISR : counter 읽음 → 0, +1, counter=1
Task: 이전에 읽은 0에 +1, counter=1
두 번 증가했지만 결과는 1

Critical Section 사용 원칙

플랫폼이 제공하는 interrupt state 저장·복원 API를 사용한다. 진입 전 IRQ가 이미 비활성이었다면 종료 후 무조건 enable하면 안 된다. FreeRTOS의 taskENTER_CRITICAL 등과 ISR용 API는 역할·허용 문맥이 다르므로 설치 포트의 규칙을 따른다.

  • 잠금 안에서는 공유 값 복사·작은 queue 인덱스 변경처럼 짧고 상한이 분명한 작업만 수행한다.
  • delay·blocking 전송·printf·동적 할당·다른 lock 대기·ISR 완료 대기를 Critical Section에 넣지 않는다. 필요한 IRQ를 막은 채 완료를 기다리면 진행할 수 없다.
  • 일부 RTOS는 특정 우선순위 범위의 IRQ만 mask한다. 더 높은 우선순위 IRQ, NMI 등은 계속 접근할 수 있으므로 실제 보호 범위를 확인한다.
  • scheduler lock은 task 전환을 막을 수 있지만 IRQ를 막는 것과 같지 않다. 단일 코어의 interrupt mask는 다른 코어나 DMA도 막지 않는다.

ISR에서 task로 넘기기

ISR에서는 장치 상태·필요한 데이터 확보, 드라이버 규정에 따른 flag 처리, 짧은 이벤트 전달을 수행한다. 해석·재시도 정책·사용자 logging은 task에서 처리한다. FreeRTOS에서는 해당 IRQ 우선순위가 FromISR API 호출을 허용하는지 configMAX_SYSCALL_INTERRUPT_PRIORITY와 포트의 우선순위 표현을 확인한다.

/* FreeRTOS 개념 예제: done은 미리 생성한 binary semaphore.
 * 장치 IRQ 처리·flag clear는 실제 드라이버 handler가 수행한다.
 */
void completion_callback_from_isr(void) {
    BaseType_t wake = pdFALSE;
    xSemaphoreGiveFromISR(done, &wake);
    portYIELD_FROM_ISR(wake);
}

/* task: timeout 값과 실패 시 복구 정책을 정의한다. */
if (xSemaphoreTake(done, pdMS_TO_TICKS(100)) == pdTRUE) {
    /* 완료 상태와 수신 길이 확인 */
} else {
    /* 실제 전송 정지/늦은 callback 처리 후 buffer 재사용 */
}

binary semaphore는 중복 이벤트가 합쳐질 수 있다. 모든 수신 이벤트를 보존하려면 데이터 queue 또는 counting 형태를 검토한다. timeout은 하드웨어 전송이 정지했다는 뜻이 아니므로 버퍼를 즉시 재사용하지 않는다.

검증

높은 빈도의 IRQ와 task 부하로 강제 선점하고 카운터·queue 항목·완료 알림의 손실을 확인한다. Critical Section 최대 시간과 interrupt latency를 GPIO toggle·trace로 측정한다. 정상 실행 빈도만으로 race가 없다고 판단하지 않는다.