
xSemaphoreCreateMutex()를 통한 Mutex 생성 방법을 학습한다xSemaphoreTake() / xSemaphoreGive()의 동작 원리를 파악한다Mutex(Mutual Exclusion)는 공유 자원에 대한 접근을 하나의 Task만 허용하도록 보장하는 동기화 기법입니다. Critical Section이 인터럽트를 비활성화하는 방식인 것과 달리, Mutex는 Task 간 소유권(Ownership) 개념을 통해 공유 자원을 보호합니다.
Critical Section은 인터럽트 자체를 차단하는 반면, Mutex는 자원에 대한 접근 권한을 토큰처럼 관리합니다. 자원을 사용하려는 Task는 반드시 토큰을 획득해야 하며, 사용이 끝나면 반납합니다. 토큰이 없는 Task는 반납될 때까지 대기 상태로 전환됩니다.
Mutex 동작 흐름:
| 단계 | 동작 |
|---|---|
| xSemaphoreTake() 호출 | Mutex 획득 시도, 이미 사용 중이면 대기(Blocked) 상태 진입 |
| 공유 자원 접근 | Mutex를 보유한 Task만 접근 가능 |
| xSemaphoreGive() 호출 | Mutex 반납, 대기 중인 Task가 있으면 Unblocked 상태로 전환 |
기본 사용 구조:
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
SemaphoreHandle_t xMutex;
void vTaskA(void *pvParameters) {
while(1) {
/* Mutex 획득 시도: 최대 100ms 대기 */
if(xSemaphoreTake(xMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
/* 공유 자원 접근 */
shared_resource_access();
/* Mutex 반납 */
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
Mutex와 Binary Semaphore는 구조는 유사하지만 사용 목적과 동작 방식에서 중요한 차이가 있습니다.
| 특성 | Mutex | Binary Semaphore |
|---|---|---|
| 주요 목적 | 공유 자원 보호 (상호배제) | Task 간 동기화 (이벤트 알림) |
| 소유권 개념 | 있음 (Take한 Task만 Give 가능) | 없음 (어느 Task든 Give 가능) |
| Priority Inheritance | 지원 | 미지원 |
| ISR에서 Give | 불가능 | 가능 |
Mutex의 가장 중요한 특징은 소유권입니다. Mutex를
xSemaphoreTake()로 획득한 Task만xSemaphoreGive()로 반납할 수 있습니다. Binary Semaphore는 이 제약이 없으므로, 공유 자원 보호 목적에는 반드시 Mutex를 사용해야 합니다.
FreeRTOS의 Mutex는 Priority Inheritance 기법을 지원합니다. 이는 Priority Inversion(우선순위 역전) 문제를 완화하기 위한 메커니즘입니다.
Priority Inversion 문제 시나리오:
1. Low Priority Task (L)가 Mutex 보유 중
2. High Priority Task (H)가 Mutex 획득 시도 -> 대기 상태
3. Mid Priority Task (M)이 실행되며 L의 실행을 지연
4. 결과: H가 M보다 낮은 우선순위로 사실상 동작
Priority Inheritance 동작:
1. Low Priority Task (L)가 Mutex 보유 중
2. High Priority Task (H)가 Mutex 획득 시도 -> 대기 상태
3. FreeRTOS가 L의 우선순위를 H 수준으로 일시 상승
4. L이 빠르게 실행을 완료하고 Mutex 반납
5. H가 Mutex 획득 후 실행, L은 원래 우선순위로 복원
/*
* Priority Inheritance는 Mutex에 자동으로 적용됩니다.
* 별도 설정 없이 xSemaphoreCreateMutex()로 생성하면 활성화됩니다.
*
* configUSE_MUTEXES가 1로 설정되어 있어야 합니다.
*/
/* FreeRTOSConfig.h */
#define configUSE_MUTEXES 1
Priority Inheritance는 Priority Inversion을 완전히 해결하지는 않으며, 완화하는 기법입니다. 완전한 해결을 위해서는 Priority Ceiling Protocol 등의 별도 기법이 필요합니다.
PCP(Priority Ceiling Protocol) 동작:
1. 각 자원(Mutex) 마다 우선순위 천장값 부여
2. 태스크 T가 자원을 획득하기 위해 현재 다른 태스크에 의해 잠긴 모든 자원들의 천장 값 중 가장 높은값 검색
3. 태스크 T의 우선순위가 이 최고 천장값보다 높은 경우 자원(Mutex) 획득 가능
SemaphoreHandle_t xSemaphoreCreateMutex(void);
Mutex를 동적으로 생성하고 핸들을 반환합니다. 내부적으로 FreeRTOS 힙에서 메모리를 할당하며, 생성 직후 Mutex는 반납(available) 상태입니다.
반환값:
| 반환값 | 의미 |
|---|---|
| NULL이 아닌 값 | Mutex 생성 성공, SemaphoreHandle_t 핸들 반환 |
| NULL | 메모리 부족으로 생성 실패 |
생성 예시:
#include "FreeRTOS.h"
#include "semphr.h"
SemaphoreHandle_t xUartMutex;
void vApplicationInit(void) {
/* Mutex 생성 */
xUartMutex = xSemaphoreCreateMutex();
if(xUartMutex == NULL) {
/* 생성 실패 처리: 시스템 초기화 중단 등 */
configASSERT(pdFALSE);
}
}
NULL이 아닌 값을 반환했다고 해도 두 개 이상의 태스크가 서로의 자원을 기다리며 무한 대기(Deadlock)에 빠질 수 있으니 코드를 작성할 때 다음과 같이 주의해야 합니다.
//1. 무한 대기
// 위험: 자원을 얻을 때까지 영원히 기다립니다. (데드락의 원인)
xSemaphoreTake(xMutex, portMAX_DELAY);
// 권장: 일정 시간(예: 100ms)만 기다리고 실패하면 다른 처리를 합니다.
if (xSemaphoreTake(xMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
// 자원 사용
xSemaphoreGive(xMutex);
} else {
// 자원을 못 얻었을 때의 예외 처리 (데드락 회피)
}
//2. 중복 Take 금지(Self-Deadlock)
xSemaphoreTake(xMutex, portMAX_DELAY);
// ... 작업 중 ...
xSemaphoreTake(xMutex, portMAX_DELAY); // 여기서 영원히 멈춤
동적 메모리 할당을 피해야 하는 환경에서는 xSemaphoreCreateMutexStatic()을 사용합니다.
#include "FreeRTOS.h"
#include "semphr.h"
static StaticSemaphore_t xMutexBuffer;
SemaphoreHandle_t xMutex;
void vInit(void) {
/* 정적 메모리를 사용하여 Mutex 생성 */
xMutex = xSemaphoreCreateMutexStatic(&xMutexBuffer);
/* 정적 생성은 메모리 부족으로 실패하지 않음 */
configASSERT(xMutex != NULL);
}
임베디드 환경에서는 동적 메모리 할당의 단편화(Fragmentation) 문제를 방지하기 위해 정적 할당 방식이 권장되는 경우가 많습니다. 안전성이 중요한 시스템에서는
configSUPPORT_STATIC_ALLOCATION을 1로 설정하고 정적 방식을 사용하십시오.
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
Mutex 획득을 시도합니다. Mutex가 이미 다른 Task에 의해 보유 중이면, xTicksToWait로 지정된 시간 동안 Blocked 상태로 대기합니다.
매개변수:
| 매개변수 | 설명 |
|---|---|
| xSemaphore | xSemaphoreCreateMutex()로 생성한 핸들 |
| xTicksToWait | 최대 대기 시간 (Tick 단위), portMAX_DELAY로 무한 대기 가능 |
반환값:
| 반환값 | 의미 |
|---|---|
| pdTRUE | Mutex 획득 성공 |
| pdFALSE | 타임아웃 내에 획득 실패 |
void vTask(void *pvParameters) {
while(1) {
/* 방법 1: 타임아웃 지정 */
if(xSemaphoreTake(xMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
/* 자원 접근 */
xSemaphoreGive(xMutex);
} else {
/* 타임아웃 처리 */
printf("[경고] Mutex 획득 타임아웃\n");
}
/* 방법 2: 무한 대기 */
xSemaphoreTake(xMutex, portMAX_DELAY);
/* 자원 접근 (반드시 Give 호출 필요) */
xSemaphoreGive(xMutex);
}
}
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
보유 중인 Mutex를 반납합니다. xSemaphoreTake()로 Mutex를 획득한 Task만 호출할 수 있습니다.
반환값:
| 반환값 | 의미 |
|---|---|
| pdTRUE | Mutex 반납 성공 |
| pdFALSE | 반납 실패 (Mutex를 보유하지 않은 Task가 호출하는 경우 등) |
void vTask(void *pvParameters) {
while(1) {
if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
/* 공유 자원 접근 */
shared_resource_write(data);
/* 반드시 Give 호출 */
BaseType_t result = xSemaphoreGive(xMutex);
if(result != pdTRUE) {
/* 반납 실패: 소유권 없는 Task가 Give를 호출한 경우 */
printf("[오류] Mutex Give 실패\n");
}
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
xSemaphoreTake()와 xSemaphoreGive()는 반드시 대칭을 이루어야 합니다. 조건 분기가 있는 코드에서는 모든 실행 경로에서 Give가 호출되는지 검토해야 합니다.
/* 잘못된 예: 조건에 따라 Give가 누락됨 */
void bad_example(void) {
xSemaphoreTake(xMutex, portMAX_DELAY);
if(error_condition) {
return; /* Give 없이 반환: Mutex가 영구적으로 잠김 */
}
shared_data++;
xSemaphoreGive(xMutex);
}
/* 올바른 예: 모든 경로에서 Give 보장 */
void good_example(void) {
xSemaphoreTake(xMutex, portMAX_DELAY);
if(!error_condition) {
shared_data++;
}
xSemaphoreGive(xMutex); /* 조건과 무관하게 항상 반납 */
}
Mutex는 ISR에서 사용할 수 없습니다. Priority Inheritance 메커니즘이 ISR 환경에서는 동작하지 않기 때문입니다. ISR에서 Task를 깨워야 하는 경우에는 Binary Semaphore 또는 Queue를 사용해야 합니다.
/* 오류: ISR에서 Mutex Take/Give 금지 */
void UART_IRQHandler(void) {
xSemaphoreTake(xMutex, portMAX_DELAY); /* ISR에서 사용 불가 */
rxBuffer.count++;
xSemaphoreGive(xMutex);
}
/* 올바른 방법: ISR에서는 Binary Semaphore Give만 사용 */
void UART_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* Binary Semaphore로 Task에 이벤트 알림 */
xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
여러 Task가 UART를 동시에 사용할 때 출력이 뒤섞이는 문제를 Mutex로 해결합니다.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
SemaphoreHandle_t xUartMutex;
/* Mutex로 보호되는 UART 출력 함수 */
void uart_print_safe(const char *message) {
if(xSemaphoreTake(xUartMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
/* UART 출력 (Critical Section 없이도 단독 접근 보장) */
printf("%s", message);
xSemaphoreGive(xUartMutex);
} else {
/* 타임아웃: 필요에 따라 재시도 또는 오류 처리 */
}
}
void vTask1(void *pvParameters) {
char buffer[64];
while(1) {
snprintf(buffer, sizeof(buffer), "[Task1] 실행 중, Tick: %lu\n",
xTaskGetTickCount());
uart_print_safe(buffer);
vTaskDelay(pdMS_TO_TICKS(300));
}
}
void vTask2(void *pvParameters) {
char buffer[64];
while(1) {
snprintf(buffer, sizeof(buffer), "[Task2] 실행 중, Tick: %lu\n",
xTaskGetTickCount());
uart_print_safe(buffer);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void vTask3(void *pvParameters) {
char buffer[64];
while(1) {
snprintf(buffer, sizeof(buffer), "[Task3] 실행 중, Tick: %lu\n",
xTaskGetTickCount());
uart_print_safe(buffer);
vTaskDelay(pdMS_TO_TICKS(700));
}
}
int main(void) {
xUartMutex = xSemaphoreCreateMutex();
configASSERT(xUartMutex != NULL);
xTaskCreate(vTask1, "Task1", 256, NULL, 2, NULL);
xTaskCreate(vTask2, "Task2", 256, NULL, 2, NULL);
xTaskCreate(vTask3, "Task3", 256, NULL, 2, NULL);
vTaskStartScheduler();
while(1);
}
실제 임베디드 환경에서 사용하는 구조처럼 UART 드라이버를 Mutex로 감싸는 레이어를 구현합니다.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#define UART_TX_TIMEOUT_MS 50
typedef struct {
SemaphoreHandle_t xMutex;
uint32_t txCount;
uint32_t timeoutCount;
} UartDriver;
static UartDriver uartDrv = {0};
BaseType_t uart_driver_init(void) {
uartDrv.xMutex = xSemaphoreCreateMutex();
if(uartDrv.xMutex == NULL) {
return pdFALSE;
}
uartDrv.txCount = 0;
uartDrv.timeoutCount = 0;
return pdTRUE;
}
BaseType_t uart_send(const char *data, uint32_t length) {
if(xSemaphoreTake(uartDrv.xMutex, pdMS_TO_TICKS(UART_TX_TIMEOUT_MS)) != pdTRUE) {
uartDrv.timeoutCount++;
return pdFALSE;
}
/* HAL_UART_Transmit() 또는 직접 레지스터 접근으로 대체 */
for(uint32_t i = 0; i < length; i++) {
putchar(data[i]);
}
uartDrv.txCount++;
xSemaphoreGive(uartDrv.xMutex);
return pdTRUE;
}
BaseType_t uart_printf(const char *format, ...) {
char buffer[128];
int length;
va_list args;
va_start(args, format);
length = vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
if(length <= 0) {
return pdFALSE;
}
return uart_send(buffer, (uint32_t)length);
}
void uart_get_stats(uint32_t *txCount, uint32_t *timeoutCount) {
if(xSemaphoreTake(uartDrv.xMutex, pdMS_TO_TICKS(UART_TX_TIMEOUT_MS)) == pdTRUE) {
*txCount = uartDrv.txCount;
*timeoutCount = uartDrv.timeoutCount;
xSemaphoreGive(uartDrv.xMutex);
}
}
void vSensorTask(void *pvParameters) {
int16_t temperature = 25;
while(1) {
temperature += (int16_t)((xTaskGetTickCount() % 3) - 1);
uart_printf("[Sensor] 온도: %d C\n", temperature);
vTaskDelay(pdMS_TO_TICKS(400));
}
}
void vControlTask(void *pvParameters) {
while(1) {
uart_printf("[Control] 제어 루프 실행, Tick: %lu\n",
xTaskGetTickCount());
vTaskDelay(pdMS_TO_TICKS(600));
}
}
void vMonitorTask(void *pvParameters) {
while(1) {
vTaskDelay(pdMS_TO_TICKS(3000));
uint32_t txCount, timeoutCount;
uart_get_stats(&txCount, &timeoutCount);
uart_printf("\n--- UART 통계 ---\n");
uart_printf("송신 횟수: %lu\n", txCount);
uart_printf("타임아웃 횟수: %lu\n", timeoutCount);
uart_printf("-----------------\n\n");
}
}
int main(void) {
if(uart_driver_init() != pdTRUE) {
while(1); /* 드라이버 초기화 실패 */
}
uart_printf("=== UART Mutex 보호 실습 ===\n\n");
xTaskCreate(vSensorTask, "Sensor", 256, NULL, 2, NULL);
xTaskCreate(vControlTask, "Control", 256, NULL, 2, NULL);
xTaskCreate(vMonitorTask, "Monitor", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
낮은 우선순위 Task가 Mutex를 보유한 상태에서 높은 우선순위 Task가 Mutex를 요청할 때 우선순위 상속이 발생하는 과정을 확인합니다.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
SemaphoreHandle_t xMutex;
/* 낮은 우선순위 Task: Mutex 보유 후 긴 작업 수행 */
void vLowPriorityTask(void *pvParameters) {
while(1) {
printf("[Low] Mutex 획득 시도\n");
xSemaphoreTake(xMutex, portMAX_DELAY);
printf("[Low] Mutex 획득 완료\n");
printf("[Low] 현재 우선순위: %lu\n",
(uint32_t)uxTaskPriorityGet(NULL));
/* 긴 작업 시뮬레이션 */
vTaskDelay(pdMS_TO_TICKS(500));
printf("[Low] 작업 완료, Mutex 반납\n");
printf("[Low] 반납 전 우선순위: %lu\n",
(uint32_t)uxTaskPriorityGet(NULL));
xSemaphoreGive(xMutex);
printf("[Low] 반납 후 우선순위: %lu (복원됨)\n",
(uint32_t)uxTaskPriorityGet(NULL));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
/* 중간 우선순위 Task: Mutex와 무관하게 실행 */
void vMidPriorityTask(void *pvParameters) {
while(1) {
printf("[Mid] 실행 중 (Mutex 불필요)\n");
vTaskDelay(pdMS_TO_TICKS(200));
}
}
/* 높은 우선순위 Task: Mutex 요청 -> Low Task 우선순위 상속 유발 */
void vHighPriorityTask(void *pvParameters) {
/* Low Task가 Mutex를 획득할 시간을 줌 */
vTaskDelay(pdMS_TO_TICKS(100));
while(1) {
printf("[High] Mutex 획득 시도 -> Low Task 우선순위 상승 예상\n");
xSemaphoreTake(xMutex, portMAX_DELAY);
printf("[High] Mutex 획득 성공\n");
/* 공유 자원 접근 */
vTaskDelay(pdMS_TO_TICKS(100));
xSemaphoreGive(xMutex);
printf("[High] Mutex 반납\n");
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
int main(void) {
printf("=== Priority Inheritance 동작 확인 ===\n\n");
xMutex = xSemaphoreCreateMutex();
configASSERT(xMutex != NULL);
/* 우선순위: Low=1, Mid=2, High=3 */
xTaskCreate(vLowPriorityTask, "Low", 256, NULL, 1, NULL);
xTaskCreate(vMidPriorityTask, "Mid", 256, NULL, 2, NULL);
xTaskCreate(vHighPriorityTask, "High", 256, NULL, 3, NULL);
vTaskStartScheduler();
while(1);
}
여러 공유 자원(UART, 센서 데이터, 상태 레지스터)을 각각의 Mutex로 독립적으로 보호하는 시스템을 구현합니다.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
/* 공유 자원별 Mutex */
SemaphoreHandle_t xUartMutex;
SemaphoreHandle_t xSensorMutex;
SemaphoreHandle_t xStateMutex;
typedef struct {
int16_t temperature;
uint16_t humidity;
uint32_t lastUpdate;
} SensorData;
typedef enum {
STATE_IDLE = 0,
STATE_RUNNING,
STATE_ERROR
} SystemState;
SensorData gSensorData = {0};
SystemState gSystemState = STATE_IDLE;
void safe_uart_print(const char *msg) {
if(xSemaphoreTake(xUartMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
printf("%s", msg);
xSemaphoreGive(xUartMutex);
}
}
void update_sensor_data(int16_t temp, uint16_t hum) {
if(xSemaphoreTake(xSensorMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
gSensorData.temperature = temp;
gSensorData.humidity = hum;
gSensorData.lastUpdate = xTaskGetTickCount();
xSemaphoreGive(xSensorMutex);
}
}
SensorData read_sensor_data(void) {
SensorData local = {0};
if(xSemaphoreTake(xSensorMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
local = gSensorData;
xSemaphoreGive(xSensorMutex);
}
return local;
}
void set_system_state(SystemState newState) {
if(xSemaphoreTake(xStateMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
gSystemState = newState;
xSemaphoreGive(xStateMutex);
}
}
SystemState get_system_state(void) {
SystemState state = STATE_IDLE;
if(xSemaphoreTake(xStateMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
state = gSystemState;
xSemaphoreGive(xStateMutex);
}
return state;
}
void vSensorWriteTask(void *pvParameters) {
int16_t temp = 20;
uint16_t hum = 50;
set_system_state(STATE_RUNNING);
safe_uart_print("[SensorWrite] 시스템 상태: RUNNING\n");
while(1) {
temp += (int16_t)((xTaskGetTickCount() % 3) - 1);
hum += (uint16_t)((xTaskGetTickCount() % 5) - 2);
update_sensor_data(temp, hum);
char msg[64];
snprintf(msg, sizeof(msg),
"[SensorWrite] 온도: %d, 습도: %u 업데이트\n", temp, hum);
safe_uart_print(msg);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void vSensorReadTask(void *pvParameters) {
while(1) {
vTaskDelay(pdMS_TO_TICKS(800));
SensorData data = read_sensor_data();
SystemState state = get_system_state();
char msg[128];
snprintf(msg, sizeof(msg),
"[SensorRead] 온도: %d, 습도: %u, 갱신시각: %lu, 상태: %d\n",
data.temperature, data.humidity, data.lastUpdate, (int)state);
safe_uart_print(msg);
}
}
void vWatchdogTask(void *pvParameters) {
while(1) {
vTaskDelay(pdMS_TO_TICKS(2000));
SensorData data = read_sensor_data();
SystemState state = get_system_state();
if(data.temperature > 40 || data.temperature < 0) {
set_system_state(STATE_ERROR);
safe_uart_print("[Watchdog] 온도 범위 초과: STATE_ERROR 설정\n");
} else if(state == STATE_ERROR) {
set_system_state(STATE_RUNNING);
safe_uart_print("[Watchdog] 온도 정상 복귀: STATE_RUNNING 복원\n");
}
}
}
int main(void) {
xUartMutex = xSemaphoreCreateMutex();
xSensorMutex = xSemaphoreCreateMutex();
xStateMutex = xSemaphoreCreateMutex();
configASSERT(xUartMutex != NULL);
configASSERT(xSensorMutex != NULL);
configASSERT(xStateMutex != NULL);
safe_uart_print("=== 다중 공유 자원 Mutex 보호 ===\n\n");
xTaskCreate(vSensorWriteTask, "SensorWrite", 256, NULL, 3, NULL);
xTaskCreate(vSensorReadTask, "SensorRead", 256, NULL, 2, NULL);
xTaskCreate(vWatchdogTask, "Watchdog", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
두 Task가 서로 상대방이 보유한 Mutex를 기다리는 상황을 Deadlock이라 합니다.
/*
* Deadlock 시나리오:
*
* Task A: xMutex1 보유 -> xMutex2 대기
* Task B: xMutex2 보유 -> xMutex1 대기
* 결과: 두 Task 모두 영구 대기 상태
*/
/* 잘못된 구조 */
void vTaskA(void *pvParameters) {
xSemaphoreTake(xMutex1, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(10)); /* 이 사이에 Task B가 Mutex2 획득 */
xSemaphoreTake(xMutex2, portMAX_DELAY); /* Deadlock 발생 */
xSemaphoreGive(xMutex2);
xSemaphoreGive(xMutex1);
}
void vTaskB(void *pvParameters) {
xSemaphoreTake(xMutex2, portMAX_DELAY);
xSemaphoreTake(xMutex1, portMAX_DELAY); /* Deadlock 발생 */
xSemaphoreGive(xMutex1);
xSemaphoreGive(xMutex2);
}
/* 올바른 구조: 획득 순서를 통일 */
void vTaskA_fixed(void *pvParameters) {
xSemaphoreTake(xMutex1, portMAX_DELAY); /* 항상 Mutex1 먼저 */
xSemaphoreTake(xMutex2, portMAX_DELAY);
xSemaphoreGive(xMutex2);
xSemaphoreGive(xMutex1);
}
void vTaskB_fixed(void *pvParameters) {
xSemaphoreTake(xMutex1, portMAX_DELAY); /* 항상 Mutex1 먼저 */
xSemaphoreTake(xMutex2, portMAX_DELAY);
xSemaphoreGive(xMutex2);
xSemaphoreGive(xMutex1);
}
/* portMAX_DELAY 대신 타임아웃을 설정하여 Deadlock 상황을 감지 */
void vSafeTask(void *pvParameters) {
while(1) {
if(xSemaphoreTake(xMutex1, pdMS_TO_TICKS(500)) != pdTRUE) {
printf("[오류] Mutex1 획득 타임아웃: Deadlock 의심\n");
continue;
}
if(xSemaphoreTake(xMutex2, pdMS_TO_TICKS(500)) != pdTRUE) {
printf("[오류] Mutex2 획득 타임아웃: Deadlock 의심\n");
xSemaphoreGive(xMutex1); /* 보유 중인 Mutex 반납 후 재시도 */
continue;
}
/* 두 Mutex 모두 획득 성공 */
shared_operation();
xSemaphoreGive(xMutex2);
xSemaphoreGive(xMutex1);
vTaskDelay(pdMS_TO_TICKS(10));
}
}
/* 문제: 예외 경로에서 Mutex 미반납 */
void bad_resource_access(void) {
xSemaphoreTake(xMutex, portMAX_DELAY);
if(invalid_state()) {
return; /* Mutex 반납 누락 */
}
process_data();
xSemaphoreGive(xMutex);
}
/* 해결: 단일 퇴출 지점 패턴 사용 */
void good_resource_access(void) {
xSemaphoreTake(xMutex, portMAX_DELAY);
if(!invalid_state()) {
process_data();
}
xSemaphoreGive(xMutex); /* 항상 도달하는 단일 반납 지점 */
}
Mutex의 개념
xSemaphoreCreateMutex()
xSemaphoreCreateMutexStatic() 사용xSemaphoreTake() / xSemaphoreGive()
실전 설계 원칙
| 개념 | 설명 |
|---|---|
| xSemaphoreCreateMutex() | Mutex 생성, Priority Inheritance 자동 적용 |
| xSemaphoreTake() | Mutex 획득, 실패 시 Blocked 상태로 대기 |
| xSemaphoreGive() | Mutex 반납, 대기 Task 중 Unblocked 처리 |
| Priority Inheritance | Mutex 보유 Task의 우선순위를 요청자 수준으로 일시 상승 |
| Priority Inversion | 낮은 우선순위 Task가 높은 우선순위 Task를 간접적으로 차단하는 현상 |
| Deadlock | 두 Task가 서로의 Mutex를 무한 대기하는 교착 상태 |
| 소유권 | Mutex를 Take한 Task만 Give 가능하다는 제약 |
4개 이상의 Task가 동시에 UART를 통해 로그를 출력하는 시스템을 Mutex로 보호하여 출력이 섞이지 않도록 구현하십시오.
요구사항:
동일한 공유 자원 보호 작업에 대해 Mutex와 Critical Section의 동작 차이를 측정하고 분석하십시오.
요구사항:
3개의 공유 자원(A, B, C)을 각각의 Mutex로 보호하고, 여러 Task가 2개 이상의 자원을 동시에 사용해야 하는 시나리오에서 Deadlock 없이 동작하는 시스템을 구현하십시오.
요구사항:
/* 원인: Mutex 초기화 전에 Task에서 사용 시도 */
SemaphoreHandle_t xMutex;
void vTask(void *pvParameters) {
/* Mutex가 아직 NULL일 수 있음 */
xSemaphoreTake(xMutex, portMAX_DELAY); /* Crash 위험 */
}
int main(void) {
/* 해결: Task 생성 전에 반드시 Mutex 먼저 생성 */
xMutex = xSemaphoreCreateMutex();
configASSERT(xMutex != NULL);
xTaskCreate(vTask, "Task", 256, NULL, 2, NULL);
vTaskStartScheduler();
}
/* 원인: 재귀 호출 또는 중첩 호출로 인한 이중 Take */
void function_a(void) {
xSemaphoreTake(xMutex, portMAX_DELAY);
function_b(); /* function_b 내부에서도 Take 시도 */
xSemaphoreGive(xMutex);
}
void function_b(void) {
xSemaphoreTake(xMutex, portMAX_DELAY); /* 자기 자신이 보유한 Mutex를 다시 Take -> Deadlock */
xSemaphoreGive(xMutex);
}
/*
* 해결 방법 1: Recursive Mutex 사용 (xSemaphoreCreateRecursiveMutex)
* 해결 방법 2: 내부 함수가 Mutex를 직접 사용하지 않도록 설계 분리
*/
/* 오류: ISR에서 Mutex Take/Give 사용 */
void TIM2_IRQHandler(void) {
xSemaphoreTake(xMutex, 0); /* ISR에서 사용 금지 */
timerCount++;
xSemaphoreGive(xMutex);
}
/* 해결: ISR에서는 taskENTER_CRITICAL_FROM_ISR() 사용 */
void TIM2_IRQHandler(void) {
UBaseType_t uxSaved = taskENTER_CRITICAL_FROM_ISR();
timerCount++;
taskEXIT_CRITICAL_FROM_ISR(uxSaved);
}