임베디드 면접 용어 사전
RTOS뮤텍스 · 세마포어

뮤텍스 vs 세마포어 (RTOS)

뮤텍스는 소유권과 우선순위 상속이 있어 상호 배제용, 세마포어는 카운팅·시그널링용이다.

비슷해 보이지만 목적과 소유권이 다른 두 동기화 도구.

한 줄 요약

  • 뮤텍스 — : "내가 쓰는 동안 아무도 못 들어와" → 상호 배제 (자원 보호)
  • 세마포어 : "준비됐어" / "동시에 N개까지" — → 신호 전달 / 개수 제한

결정적 차이 — 소유권

뮤텍스세마포어
소유자있다 (잠근 태스크)없다
해제 주체잠근 태스크만누구나
우선순위 상속지원미지원
재귀 획득가능(재귀 뮤텍스)불가
ISR에서 give✗ 금지○ 가능
초기 상태사용 가능(1)지정 가능(0부터)

"ISR에서 뮤텍스를 주면 안 된다" 는 것이 실무에서 특히 중요하다. ISR은 태스크가 아니라 소유자가 될 수 없고, 우선순위 상속 대상도 아니기 때문이다.

용도별 선택

① 공유 자원 보호 → 뮤텍스

xSemaphoreTake(i2c_mutex, portMAX_DELAY);
i2c_read(addr, buf, len);            // 여러 태스크가 같은 I2C 버스를 쓴다
xSemaphoreGive(i2c_mutex);

우선순위 상속이 필요하므로 반드시 뮤텍스여야 한다.

② ISR → 태스크 신호 → 이진 세마포어

// ISR
void USART1_IRQHandler(void) {
    BaseType_t woken = pdFALSE;
    xSemaphoreGiveFromISR(rx_sem, &woken);   // 소유권이 없으니 가능
    portYIELD_FROM_ISR(woken);
}

// 태스크
void rx_task(void *p) {
    for (;;) {
        xSemaphoreTake(rx_sem, portMAX_DELAY);   // 신호 올 때까지 잠듦
        process();
    }
}

deferred 처리의 표준 구현이다. ISR은 즉시 끝나고 실제 작업은 우선순위를 가진 태스크에서 한다.

③ 자원 개수 제한 → 카운팅 세마포어

SemaphoreHandle_t pool = xSemaphoreCreateCounting(4, 4);   // 버퍼 4개
xSemaphoreTake(pool, portMAX_DELAY);   // 하나 확보
// … 사용 …
xSemaphoreGive(pool);

④ 여러 태스크 동시 시작 → 카운팅 세마포어 또는 이벤트 그룹

재귀 뮤텍스

SemaphoreHandle_t rmtx = xSemaphoreCreateRecursiveMutex();

void outer(void) {
    xSemaphoreTakeRecursive(rmtx, portMAX_DELAY);
    inner();                                     // 같은 태스크가 또 잡는다
    xSemaphoreGiveRecursive(rmtx);
}
void inner(void) {
    xSemaphoreTakeRecursive(rmtx, portMAX_DELAY);   // 소유자가 자신이므로 통과
    xSemaphoreGiveRecursive(rmtx);
}

일반 뮤텍스면 자기 자신과 데드락에 빠진다. 다만 재귀 뮤텍스가 필요하다는 것은 설계가 얽혀 있다는 신호이기도 하다.

흔한 실수

① 세마포어로 상호 배제

xSemaphoreTake(sem);    // 세마포어를 락처럼 사용
critical();
xSemaphoreGive(sem);

동작은 하지만 우선순위 역전을 막지 못한다. Pathfinder 사고와 같은 문제가 재현될 수 있다.

② 해제를 빠뜨림

xSemaphoreTake(mtx, portMAX_DELAY);
if (error) return;               // ✗ 뮤텍스를 쥔 채 나간다 → 영구 데드락
xSemaphoreGive(mtx);

모든 탈출 경로에서 해제해야 한다. goto cleanup 패턴이 유용하다.

③ 무한 대기

xSemaphoreTake(mtx, portMAX_DELAY);   // 영원히 기다릴 수 있다
if (xSemaphoreTake(mtx, pdMS_TO_TICKS(100)) == pdTRUE) { … }
else { /* 타임아웃 — 오류 처리 */ }

타임아웃을 두면 문제를 감지할 수 있다. 진단 가능한 시스템의 조건이다.

④ ISR에서 일반 API 사용

xSemaphoreGive(sem);           // ✗ ISR에서 호출 금지
xSemaphoreGiveFromISR(...);    // ✓

큐가 더 나은 경우가 많다

xQueueSendFromISR(q, &data, &woken);   // 신호 + 데이터를 함께

세마포어는 "무슨 일이 일어났다"만 알려 주고 데이터는 별도 공유 변수가 필요하다. 큐를 쓰면 데이터가 함께 전달되어 경쟁 조건이 줄어든다.

FreeRTOS의 세마포어는 내부적으로 크기 0인 큐로 구현되어 있어 성능 차이도 거의 없다. 의심스러우면 큐를 쓰는 것이 실무 조언이다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — RTOS — 스케줄링·뮤텍스/세마포어·우선순위 역전·데드락·스택