
개요:
Race Condition은 둘 이상의 Task가 공유 자원에 동시에 접근하려 할 때, 실행 순서나 타이밍에 따라 결과가 달라지는 상황을 의미합니다.
태스크 실행 중엔 값의 일관성이 유지돼야 하는데 다른 태스크가 같은 값에 접근에 값을 바꾸게 되면 먼저 실행 중이던 태스크는 엉뚱한 결과를 출력하게 되는 문제입니다.
발생 조건:
Race Condition이 발생하는 시나리오:
| 단계 | Task A | Task B | counter 값 |
|---|---|---|---|
| 1 | 읽기: counter = 0 | - | 0 |
| 2 | - | 읽기: counter = 0 | 0 |
| 3 | 증가: counter = 1 | - | 0 |
| 4 | 쓰기: counter = 1 | - | 1 |
| 5 | - | 증가: counter = 1 | 1 |
| 6 | - | 쓰기: counter = 1 | 1 |
결과: counter = 1 (기대값: 2)
간단한 예제:
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
volatile uint32_t sharedCounter = 0;
void vIncrementTask(void *pvParameters) {
int taskId = (int)pvParameters;
while(1) {
/* Race Condition 발생 구간 */
uint32_t temp = sharedCounter;
temp++;
sharedCounter = temp;
printf("[Task %d] Counter = %lu\n", taskId, sharedCounter);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
int main(void) {
printf("=== Race Condition 기본 예제 ===\n\n");
xTaskCreate(vIncrementTask, "Inc1", 256, (void*)1, 2, NULL);
xTaskCreate(vIncrementTask, "Inc2", 256, (void*)2, 2, NULL);
vTaskStartScheduler();
while(1);
}
예제 1: 은행 계좌 문제
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
typedef struct {
uint32_t balance;
char owner[20];
} BankAccount;
BankAccount account = {1000, "User"};
void withdraw(uint32_t amount) {
/* Race Condition 발생 가능 구간 */
if(account.balance >= amount) {
printf("[출금 시도] 현재 잔액: %lu, 출금액: %lu\n",
account.balance, amount);
/* 컨텍스트 스위칭이 여기서 발생할 수 있음 */
vTaskDelay(pdMS_TO_TICKS(10));
account.balance -= amount;
printf("[출금 완료] 남은 잔액: %lu\n", account.balance);
} else {
printf("[출금 실패] 잔액 부족\n");
}
}
void vWithdrawTask(void *pvParameters) {
int taskId = (int)pvParameters;
while(1) {
printf("\n[Task %d] 출금 작업 시작\n", taskId);
withdraw(600);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void) {
printf("=== 은행 계좌 Race Condition ===\n");
printf("초기 잔액: %lu\n\n", account.balance);
/* 두 Task가 동시에 600원씩 출금 시도 */
xTaskCreate(vWithdrawTask, "Withdraw1", 256, (void*)1, 2, NULL);
xTaskCreate(vWithdrawTask, "Withdraw2", 256, (void*)2, 2, NULL);
vTaskStartScheduler();
while(1);
}
예상 출력 (Race Condition 발생 시):
[Task 1] 출금 작업 시작
[출금 시도] 현재 잔액: 1000, 출금액: 600
[Task 2] 출금 작업 시작
[출금 시도] 현재 잔액: 1000, 출금액: 600
[출금 완료] 남은 잔액: 400
[출금 완료] 남은 잔액: -200 <- 문제 발생!
예제 2: 링 버퍼 문제
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#define BUFFER_SIZE 10
typedef struct {
uint8_t data[BUFFER_SIZE];
volatile uint32_t head;
volatile uint32_t tail;
volatile uint32_t count;
} RingBuffer;
RingBuffer ringBuffer = {0};
BaseType_t buffer_write(uint8_t value) {
/* Race Condition 발생 가능 */
if(ringBuffer.count >= BUFFER_SIZE) {
return pdFALSE;
}
ringBuffer.data[ringBuffer.head] = value;
ringBuffer.head = (ringBuffer.head + 1) % BUFFER_SIZE;
ringBuffer.count++; //여기서 race condition 발생 가능
return pdTRUE;
}
BaseType_t buffer_read(uint8_t *value) {
/* Race Condition 발생 가능 */
if(ringBuffer.count == 0) {
return pdFALSE;
}
*value = ringBuffer.data[ringBuffer.tail];
ringBuffer.tail = (ringBuffer.tail + 1) % BUFFER_SIZE;
ringBuffer.count--; //여기도 race condition 발생 가능
return pdTRUE;
}
void vProducerTask(void *pvParameters) {
int taskId = (int)pvParameters;
uint8_t value = 0;
while(1) {
if(buffer_write(value)) {
printf("[Producer %d] 쓰기 성공: %u (count: %lu)\n",
taskId, value, ringBuffer.count);
} else {
printf("[Producer %d] 버퍼 가득 참\n", taskId);
}
value++;
vTaskDelay(pdMS_TO_TICKS(50));
}
}
void vConsumerTask(void *pvParameters) {
int taskId = (int)pvParameters;
uint8_t value;
while(1) {
if(buffer_read(&value)) {
printf("[Consumer %d] 읽기 성공: %u (count: %lu)\n",
taskId, value, ringBuffer.count);
} else {
printf("[Consumer %d] 버퍼 비어 있음\n", taskId);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
int main(void) {
printf("=== 링 버퍼 Race Condition ===\n\n");
xTaskCreate(vProducerTask, "Producer1", 256, (void*)1, 2, NULL);
xTaskCreate(vProducerTask, "Producer2", 256, (void*)2, 2, NULL);
xTaskCreate(vConsumerTask, "Consumer1", 256, (void*)1, 2, NULL);
vTaskStartScheduler();
while(1);
}
이와 같은 상황을 방지하기 위해 RTOS에선 Critical Section(임계 구역)을 Mutex나 인터럽트 방지로 보호해야 합니다.
컨텍스트 스위칭 타이밍:
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
volatile uint32_t globalData = 0;
void vTaskA(void *pvParameters) {
while(1) {
printf("[Task A] 읽기 전: globalData = %lu\n", globalData);
uint32_t temp = globalData;
printf("[Task A] 읽기 후: temp = %lu\n", temp);
/* 여기서 컨텍스트 스위칭 발생 가능 */
taskYIELD();
temp += 10;
globalData = temp;
printf("[Task A] 쓰기 완료: globalData = %lu\n\n", globalData);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void vTaskB(void *pvParameters) {
while(1) {
printf("[Task B] 읽기 전: globalData = %lu\n", globalData);
uint32_t temp = globalData;
printf("[Task B] 읽기 후: temp = %lu\n", temp);
/* 여기서 컨텍스트 스위칭 발생 가능 */
taskYIELD();
temp += 20;
globalData = temp;
printf("[Task B] 쓰기 완료: globalData = %lu\n\n", globalData);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
int main(void) {
printf("=== 컨텍스트 스위칭과 Race Condition ===\n\n");
xTaskCreate(vTaskA, "TaskA", 256, NULL, 2, NULL);
xTaskCreate(vTaskB, "TaskB", 256, NULL, 2, NULL);
vTaskStartScheduler();
while(1);
}
개요:
Critical Section은 공유 자원에 접근하는 코드 구간으로, 한 번에 하나의 Task만 실행되어야 하는 영역입니다.
Critical Section의 특징:
Critical Section 구조:
| 단계 | 설명 |
|---|---|
| 진입 구간 (Entry Section) | Critical Section 진입 전 준비 |
| Critical Section | 공유 자원 접근 |
| 퇴출 구간 (Exit Section) | Critical Section 종료 후 정리 |
| 나머지 구간 (Remainder Section) | 일반 코드 실행 |
패턴 1: 읽기-수정-쓰기(Read-Modify-Write)
/* Critical Section 필요 */
void increment_counter(void) {
/* 진입 구간 */
/* Critical Section 시작 */
uint32_t temp = sharedCounter; // 읽기
temp++; // 수정
sharedCounter = temp; // 쓰기
/* Critical Section 끝 */
/* 퇴출 구간 */
}
패턴 2: 체크-후-수행(Check-Then-Act)
/* Critical Section 필요 */
void conditional_operation(void) {
/* Critical Section 시작 */
if(resource_available) { // 체크
use_resource(); // 수행
resource_available = false;
}
/* Critical Section 끝 */
}
패턴 3: 복합 연산
typedef struct {
uint32_t x;
uint32_t y;
} Point;
Point sharedPoint = {0, 0};
/* Critical Section 필요 */
void update_point(uint32_t newX, uint32_t newY) {
/* Critical Section 시작 */
sharedPoint.x = newX;
sharedPoint.y = newY;
/* Critical Section 끝 */
/* 두 값이 항상 함께 업데이트되어야 함 */
}
방법 1: 인터럽트 비활성화
void safe_increment(void) {
/* 모든 인터럽트 비활성화 */
taskENTER_CRITICAL();
sharedCounter++;
/* 인터럽트 재활성화 */
taskEXIT_CRITICAL();
}
방법 2: 스케줄러 일시 중단
void safe_operation(void) {
/* 스케줄러 중단 */
vTaskSuspendAll();
/* 공유 자원 접근 */
shared_data.value1 = 100;
shared_data.value2 = 200;
/* 스케줄러 재개 */
xTaskResumeAll();
}
방법 3: 동기화 객체 사용 (다음 강의에서 자세히 다룸)
/* Mutex, Semaphore 등을 사용한 보호 */
void protected_operation(void) {
/* Mutex 획득 */
xSemaphoreTake(mutex, portMAX_DELAY);
/* Critical Section */
access_shared_resource();
/* Mutex 반환 */
xSemaphoreGive(mutex);
}
문제 시나리오:
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
volatile uint32_t sharedValue = 0;
volatile uint32_t expectedValue = 0;
void vIncrementTask(void *pvParameters) {
const char *name = (const char*)pvParameters;
for(int i = 0; i < 1000; i++) {
/* Race Condition 발생 구간 */
sharedValue++;
expectedValue++;
/* 간헐적으로 양보하여 Race Condition 유발 */
if(i % 10 == 0) {
taskYIELD();
}
}
printf("[%s] 작업 완료\n", name);
vTaskDelete(NULL);
}
void vMonitorTask(void *pvParameters) {
vTaskDelay(pdMS_TO_TICKS(2000));
printf("\n=== 결과 분석 ===\n");
printf("실제 값: %lu\n", sharedValue);
printf("기대 값: %lu\n", expectedValue);
printf("손실된 증가: %lu\n", expectedValue - sharedValue);
if(sharedValue != expectedValue) {
printf("상태: Race Condition 발생!\n");
} else {
printf("상태: 정상 (운이 좋았음)\n");
}
vTaskDelete(NULL);
}
int main(void) {
printf("=== 전역 변수 경쟁 문제 ===\n\n");
xTaskCreate(vIncrementTask, "Inc1", 256, (void*)"Task 1", 2, NULL);
xTaskCreate(vIncrementTask, "Inc2", 256, (void*)"Task 2", 2, NULL);
xTaskCreate(vMonitorTask, "Monitor", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
구조체 멤버를 하나씩 채우는 과정에서 여러 개의 명령어가 전달되고, 아래와 같이 vSensorUpdateTask와 vSensorReadTask가 동시에 실행될 때 Context Switch하는 과정에서 구조체 멤버 값이 꼬이는 상황이 발생할 수 있습니다. 이를 방지하기 위해 앞서 배운 Critical Section을 활용합니다.
taskENTER_CRITICAL(); sensorReading.timestamp = ...; //모든 멤버 업데이트 sensorReading.checksum = ...; taskEXIT_CRITICAL();또는 Mutex 사용을 통해 구조체 멤버에 접근하는 순서를 제한할 수 있습니다
if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE){ //데이터 읽기 또는 쓰기 xSemaphoreGive(xMutex); }
문제 시나리오:
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
typedef struct {
uint32_t timestamp;
int16_t temperature;
uint16_t humidity;
uint8_t checksum;
} SensorData;
SensorData sensorReading = {0};
uint8_t calculate_checksum(SensorData *data) {
return (data->timestamp + data->temperature + data->humidity) & 0xFF;
}
void vSensorUpdateTask(void *pvParameters) {
uint32_t counter = 0;
while(1) {
/* Race Condition 발생 가능 구간 */
sensorReading.timestamp = xTaskGetTickCount();
sensorReading.temperature = 20 + (counter % 10);
/* 여기서 컨텍스트 스위칭 가능 */
taskYIELD();
sensorReading.humidity = 50 + (counter % 20);
sensorReading.checksum = calculate_checksum(&sensorReading);
counter++;
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void vSensorReadTask(void *pvParameters) {
while(1) {
/* Race Condition 발생 가능 구간 */
SensorData localCopy;
localCopy.timestamp = sensorReading.timestamp;
/* 여기서 컨텍스트 스위칭 가능 */
taskYIELD();
localCopy.temperature = sensorReading.temperature;
localCopy.humidity = sensorReading.humidity;
localCopy.checksum = sensorReading.checksum;
/* 체크섬 검증 */
uint8_t calculatedChecksum = calculate_checksum(&localCopy);
if(calculatedChecksum != localCopy.checksum) {
printf("[Reader] 데이터 불일치 감지!\n");
printf(" 시간: %lu, 온도: %d, 습도: %u\n",
localCopy.timestamp, localCopy.temperature, localCopy.humidity);
printf(" 체크섬: 저장=%u, 계산=%u\n",
localCopy.checksum, calculatedChecksum);
}
vTaskDelay(pdMS_TO_TICKS(150));
}
}
int main(void) {
printf("=== 구조체 일관성 문제 ===\n\n");
xTaskCreate(vSensorUpdateTask, "Update", 256, NULL, 2, NULL);
xTaskCreate(vSensorReadTask, "Read", 256, NULL, 2, NULL);
vTaskStartScheduler();
while(1);
}
문제 시나리오:
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include <string.h>
#define MESSAGE_SIZE 64
char sharedBuffer[MESSAGE_SIZE];
volatile BaseType_t bufferInUse = pdFALSE;
void vWriterTask(void *pvParameters) {
int taskId = (int)pvParameters;
char message[MESSAGE_SIZE];
while(1) {
snprintf(message, MESSAGE_SIZE, "Message from Task %d", taskId);
/* Race Condition 발생 구간 */
if(!bufferInUse) {
bufferInUse = pdTRUE;
/* 메시지를 한 글자씩 복사 (느린 작업 시뮬레이션) */
for(int i = 0; message[i] != '\0'; i++) {
sharedBuffer[i] = message[i];
/* 간헐적으로 양보 */
if(i % 5 == 0) {
taskYIELD();
}
}
sharedBuffer[strlen(message)] = '\0';
printf("[Writer %d] 버퍼에 쓰기: %s\n", taskId, sharedBuffer);
bufferInUse = pdFALSE;
}
vTaskDelay(pdMS_TO_TICKS(200));
}
}
void vReaderTask(void *pvParameters) {
char localBuffer[MESSAGE_SIZE];
while(1) {
/* Race Condition 발생 구간 */
if(bufferInUse) {
strcpy(localBuffer, sharedBuffer);
printf("[Reader] 버퍼 읽기: %s\n", localBuffer);
}
vTaskDelay(pdMS_TO_TICKS(300));
}
}
int main(void) {
printf("=== 공유 버퍼 충돌 문제 ===\n\n");
xTaskCreate(vWriterTask, "Writer1", 256, (void*)1, 2, NULL);
xTaskCreate(vWriterTask, "Writer2", 256, (void*)2, 2, NULL);
xTaskCreate(vReaderTask, "Reader", 256, NULL, 2, NULL);
vTaskStartScheduler();
while(1);
}
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#define NUM_INCREMENTS 10000
#define NUM_TASKS 3
volatile uint32_t sharedCounter = 0;
volatile uint32_t taskCompletionCount = 0;
void vCounterTask(void *pvParameters) {
int taskId = (int)pvParameters;
printf("[Task %d] 증가 시작\n", taskId);
for(int i = 0; i < NUM_INCREMENTS; i++) {
/* 읽기-수정-쓰기 패턴 (Race Condition) */
uint32_t temp = sharedCounter;
temp = temp + 1;
sharedCounter = temp;
/* 주기적으로 양보하여 Race Condition 발생 유도 */
if(i % 100 == 0) {
taskYIELD();
}
}
printf("[Task %d] 증가 완료\n", taskId);
taskCompletionCount++;
vTaskDelete(NULL);
}
void vResultTask(void *pvParameters) {
while(1) {
if(taskCompletionCount >= NUM_TASKS) {
uint32_t expectedValue = NUM_INCREMENTS * NUM_TASKS;
uint32_t actualValue = sharedCounter;
uint32_t lostIncrements = expectedValue - actualValue;
printf("\n=== 테스트 결과 ===\n");
printf("기대값: %lu\n", expectedValue);
printf("실제값: %lu\n", actualValue);
printf("손실된 증가: %lu\n", lostIncrements);
printf("정확도: %.2f%%\n",
(actualValue * 100.0) / expectedValue);
if(lostIncrements == 0) {
printf("상태: 통과 (운이 좋았거나 시스템이 느림)\n");
} else {
printf("상태: Race Condition 발생 확인!\n");
}
vTaskDelete(NULL);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
int main(void) {
printf("=== 카운터 경쟁 문제 재현 ===\n");
printf("각 Task가 %d번씩 카운터 증가\n", NUM_INCREMENTS);
printf("총 %d개 Task 실행\n\n", NUM_TASKS);
for(int i = 0; i < NUM_TASKS; i++) {
char taskName[16];
snprintf(taskName, sizeof(taskName), "Counter%d", i + 1);
xTaskCreate(vCounterTask, taskName, 256, (void*)(i + 1), 2, NULL);
}
xTaskCreate(vResultTask, "Result", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
Node *listHead = NULL;
volatile uint32_t listSize = 0;
void list_add(int value) {
Node *newNode = (Node*)pvPortMalloc(sizeof(Node));
if(newNode != NULL) {
newNode->data = value;
/* Race Condition 발생 구간 */
newNode->next = listHead;
/* 여기서 컨텍스트 스위칭 발생 가능 */
taskYIELD();
listHead = newNode;
listSize++;
}
}
void list_print(void) {
printf("[리스트] ");
Node *current = listHead;
uint32_t count = 0;
while(current != NULL && count < 20) {
printf("%d -> ", current->data);
current = current->next;
count++;
}
printf("NULL (크기: %lu)\n", listSize);
}
BaseType_t list_verify(void) {
Node *current = listHead;
uint32_t actualCount = 0;
while(current != NULL) {
actualCount++;
if(actualCount > 1000) {
printf("[검증] 순환 링크 감지!\n");
return pdFALSE;
}
current = current->next;
}
if(actualCount != listSize) {
printf("[검증] 크기 불일치! 실제: %lu, 저장된 크기: %lu\n",
actualCount, listSize);
return pdFALSE;
}
return pdTRUE;
}
void vAdderTask(void *pvParameters) {
int taskId = (int)pvParameters;
for(int i = 0; i < 10; i++) {
int value = taskId * 100 + i;
list_add(value);
printf("[Task %d] 추가: %d\n", taskId, value);
vTaskDelay(pdMS_TO_TICKS(50));
}
printf("[Task %d] 완료\n", taskId);
vTaskDelete(NULL);
}
void vVerifyTask(void *pvParameters) {
vTaskDelay(pdMS_TO_TICKS(1000));
printf("\n=== 리스트 검증 ===\n");
list_print();
if(list_verify()) {
printf("[검증] 리스트 정상\n");
} else {
printf("[검증] 리스트 손상 감지!\n");
}
vTaskDelete(NULL);
}
int main(void) {
printf("=== 링크드 리스트 충돌 문제 ===\n\n");
xTaskCreate(vAdderTask, "Adder1", 512, (void*)1, 2, NULL);
xTaskCreate(vAdderTask, "Adder2", 512, (void*)2, 2, NULL);
xTaskCreate(vAdderTask, "Adder3", 512, (void*)3, 2, NULL);
xTaskCreate(vVerifyTask, "Verify", 512, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
typedef struct {
char name[20];
BaseType_t inUse;
uint32_t useCount;
} Resource;
Resource resources[3] = {
{"리소스 A", pdFALSE, 0},
{"리소스 B", pdFALSE, 0},
{"리소스 C", pdFALSE, 0}
};
BaseType_t acquire_resource(int resourceId) {
/* Race Condition 발생 구간 */
if(!resources[resourceId].inUse) {
/* 체크와 설정 사이에 컨텍스트 스위칭 가능 */
taskYIELD();
resources[resourceId].inUse = pdTRUE;
resources[resourceId].useCount++;
return pdTRUE;
}
return pdFALSE;
}
void release_resource(int resourceId) {
resources[resourceId].inUse = pdFALSE;
}
void vWorkerTask(void *pvParameters) {
int taskId = (int)pvParameters;
int attempts = 0;
int successes = 0;
for(int i = 0; i < 20; i++) {
for(int resId = 0; resId < 3; resId++) {
attempts++;
if(acquire_resource(resId)) {
successes++;
printf("[Task %d] %s 획득 성공\n",
taskId, resources[resId].name);
/* 리소스 사용 시뮬레이션 */
vTaskDelay(pdMS_TO_TICKS(10));
release_resource(resId);
} else {
printf("[Task %d] %s 획득 실패 (이미 사용 중)\n",
taskId, resources[resId].name);
}
vTaskDelay(pdMS_TO_TICKS(20));
}
}
printf("\n[Task %d] 통계: 성공 %d / 시도 %d\n",
taskId, successes, attempts);
vTaskDelete(NULL);
}
void vStatisticsTask(void *pvParameters) {
vTaskDelay(pdMS_TO_TICKS(3000));
printf("\n=== 리소스 사용 통계 ===\n");
uint32_t totalUse = 0;
for(int i = 0; i < 3; i++) {
printf("%s: %lu회 사용\n",
resources[i].name, resources[i].useCount);
totalUse += resources[i].useCount;
}
printf("총 사용 횟수: %lu\n", totalUse);
printf("\n주의: Race Condition으로 인해 실제 획득 횟수와\n");
printf(" 기록된 사용 횟수가 다를 수 있습니다.\n");
vTaskDelete(NULL);
}
int main(void) {
printf("=== 멀티 리소스 경쟁 문제 ===\n\n");
xTaskCreate(vWorkerTask, "Worker1", 256, (void*)1, 2, NULL);
xTaskCreate(vWorkerTask, "Worker2", 256, (void*)2, 2, NULL);
xTaskCreate(vWorkerTask, "Worker3", 256, (void*)3, 2, NULL);
xTaskCreate(vStatisticsTask, "Stats", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#define NUM_PRODUCTS 5
typedef struct {
char name[20];
volatile int32_t stock;
volatile uint32_t totalSales;
volatile uint32_t failedOrders;
} Product;
Product inventory[NUM_PRODUCTS] = {
{"상품 A", 100, 0, 0},
{"상품 B", 100, 0, 0},
{"상품 C", 100, 0, 0},
{"상품 D", 100, 0, 0},
{"상품 E", 100, 0, 0}
};
BaseType_t process_order(int productId, int quantity) {
/* Race Condition 발생 구간 */
if(inventory[productId].stock >= quantity) {
printf(" [주문] %s 재고 확인: %ld개 (주문: %d개)\n",
inventory[productId].name,
inventory[productId].stock,
quantity);
/* 재고 확인과 차감 사이에 컨텍스트 스위칭 */
taskYIELD();
inventory[productId].stock -= quantity;
inventory[productId].totalSales += quantity;
printf(" [완료] %s 출고 성공, 남은 재고: %ld\n",
inventory[productId].name,
inventory[productId].stock);
return pdTRUE;
} else {
inventory[productId].failedOrders++;
printf(" [실패] %s 재고 부족 (재고: %ld, 주문: %d)\n",
inventory[productId].name,
inventory[productId].stock,
quantity);
return pdFALSE;
}
}
void vOrderTask(void *pvParameters) {
int taskId = (int)pvParameters;
printf("[주문 Task %d] 시작\n", taskId);
for(int i = 0; i < 30; i++) {
int productId = i % NUM_PRODUCTS;
int quantity = 5 + (taskId * 2);
printf("\n[주문 Task %d] 주문 시도 #%d\n", taskId, i + 1);
process_order(productId, quantity);
vTaskDelay(pdMS_TO_TICKS(100));
}
printf("\n[주문 Task %d] 완료\n", taskId);
vTaskDelete(NULL);
}
void vInventoryCheckTask(void *pvParameters) {
vTaskDelay(pdMS_TO_TICKS(4000));
printf("\n");
printf("===============================================\n");
printf(" 재고 관리 최종 보고서 \n");
printf("===============================================\n\n");
int32_t totalStock = 0;
uint32_t totalSales = 0;
uint32_t totalFailed = 0;
BaseType_t hasNegative = pdFALSE;
for(int i = 0; i < NUM_PRODUCTS; i++) {
printf("%-10s | 재고: %4ld | 판매: %4lu | 실패: %4lu\n",
inventory[i].name,
inventory[i].stock,
inventory[i].totalSales,
inventory[i].failedOrders);
totalStock += inventory[i].stock;
totalSales += inventory[i].totalSales;
totalFailed += inventory[i].failedOrders;
if(inventory[i].stock < 0) {
hasNegative = pdTRUE;
printf(" 경고: 음수 재고 발생! (Race Condition)\n");
}
}
printf("\n-----------------------------------------------\n");
printf("총 재고: %ld\n", totalStock);
printf("총 판매: %lu\n", totalSales);
printf("총 실패: %lu\n", totalFailed);
printf("-----------------------------------------------\n");
int32_t expectedStock = (NUM_PRODUCTS * 100) - totalSales;
if(totalStock != expectedStock || hasNegative) {
printf("\n상태: Race Condition 감지!\n");
printf(" 기대 재고: %ld\n", expectedStock);
printf(" 실제 재고: %ld\n", totalStock);
printf(" 차이: %ld\n", expectedStock - totalStock);
} else {
printf("\n상태: 정상 (드문 경우)\n");
}
printf("===============================================\n");
vTaskDelete(NULL);
}
int main(void) {
printf("=== 재고 관리 시스템 Race Condition ===\n");
printf("초기 재고: 각 상품 100개\n\n");
xTaskCreate(vOrderTask, "Order1", 512, (void*)1, 2, NULL);
xTaskCreate(vOrderTask, "Order2", 512, (void*)2, 2, NULL);
xTaskCreate(vOrderTask, "Order3", 512, (void*)3, 2, NULL);
xTaskCreate(vInventoryCheckTask, "Check", 512, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
방법 1: 카운터 불일치 검사
typedef struct {
volatile uint32_t writeCount;
volatile uint32_t readCount;
volatile uint32_t data;
} MonitoredData;
MonitoredData monData = {0};
void write_data(uint32_t value) {
monData.writeCount++;
monData.data = value;
}
void read_data(void) {
uint32_t value = monData.data;
monData.readCount++;
if(monData.writeCount != monData.readCount) {
printf("[경고] 읽기/쓰기 불일치 감지!\n");
}
}
방법 2: 체크섬 검증
typedef struct {
uint32_t value1;
uint32_t value2;
uint32_t checksum;
} CheckedData;
CheckedData checkedData = {0};
void update_data(uint32_t v1, uint32_t v2) {
checkedData.value1 = v1;
checkedData.value2 = v2;
checkedData.checksum = v1 + v2;
}
BaseType_t verify_data(void) {
uint32_t calculated = checkedData.value1 + checkedData.value2;
if(calculated != checkedData.checksum) {
printf("[경고] 데이터 무결성 오류!\n");
return pdFALSE;
}
return pdTRUE;
}
#define LOG_SIZE 100
typedef struct {
TickType_t timestamp;
const char *taskName;
const char *operation;
uint32_t value;
} LogEntry;
LogEntry eventLog[LOG_SIZE];
volatile uint32_t logIndex = 0;
void add_log(const char *operation, uint32_t value) {
if(logIndex < LOG_SIZE) {
eventLog[logIndex].timestamp = xTaskGetTickCount();
eventLog[logIndex].taskName = pcTaskGetName(NULL);
eventLog[logIndex].operation = operation;
eventLog[logIndex].value = value;
logIndex++;
}
}
void print_log(void) {
printf("\n=== 이벤트 로그 ===\n");
printf("%-8s %-15s %-20s %s\n",
"시간", "Task", "작업", "값");
printf("--------------------------------------------------------\n");
for(uint32_t i = 0; i < logIndex; i++) {
printf("%-8lu %-15s %-20s %lu\n",
eventLog[i].timestamp,
eventLog[i].taskName,
eventLog[i].operation,
eventLog[i].value);
}
}
Race Condition
Critical Section
공유 자원 문제
동기화 필요성
| 개념 | 설명 |
|---|---|
| Race Condition | 여러 Task의 실행 순서에 따라 결과가 달라지는 상황 |
| Critical Section | 공유 자원에 접근하는 코드 구간 |
| 상호 배제 | 동시에 하나의 Task만 Critical Section 진입 |
| 원자적 연산 | 중단 없이 완전히 실행되는 연산 |
| 데이터 일관성 | 공유 데이터의 정확성과 무결성 |
여러 Task가 공유 배열에 동시에 접근하는 시스템을 작성하고, Race Condition이 발생하는 시나리오를 재현하세요.
요구사항:
체크섬을 사용하여 공유 데이터의 무결성을 검증하는 시스템을 구현하세요.
요구사항:
여러 Task가 동시에 계좌에 입출금하는 시뮬레이션을 작성하고, Race Condition으로 인한 잔액 오류를 재현하세요.
요구사항:
/* taskYIELD()로 강제 컨텍스트 스위칭 유도 */
void increment(void) {
uint32_t temp = counter;
taskYIELD(); /* 컨텍스트 스위칭 강제 */
counter = temp + 1;
}
/* 더 많은 Task와 더 긴 Critical Section 사용 */
#define NUM_TASKS 5
void vTestTask(void *pvParameters) {
for(int i = 0; i < 10000; i++) {
/* 긴 Critical Section */
uint32_t temp = shared_data;
for(volatile int j = 0; j < 100; j++);
shared_data = temp + 1;
}
}
/* 검증 함수 추가 */
BaseType_t verify_integrity(void) {
if(data.checksum != calculate_checksum(&data)) {
printf("무결성 오류 발생!\n");
return pdFALSE;
}
return pdTRUE;
}
/* 주기적 검증 */
void vVerifyTask(void *pvParameters) {
while(1) {
verify_integrity();
vTaskDelay(pdMS_TO_TICKS(100));
}
}