
FreeRTOS에서 Critical Section은 taskENTER_CRITICAL()과 taskEXIT_CRITICAL() 매크로를 통해 구현됩니다. 이 매크로 쌍은 내부적으로 인터럽트를 비활성화하여 공유 자원에 대한 접근을 원자적으로 보장합니다.
말 그대로 taskENTER/taskEXIT이 실행되는 구간에선 인터럽트를 차단하는 방식입니다. 인터럽트가 발생하지 않으니 컨텍스트 스위칭도 따라서 발생하지 않는 방식입니다.
동작 원리:
| 단계 | 동작 |
|---|---|
| taskENTER_CRITICAL() 호출 | configMAX_SYSCALL_INTERRUPT_PRIORITY 이하의 인터럽트 비활성화 |
| Critical Section 실행 | 공유 자원 접근 (컨텍스트 스위칭 불가) |
| taskEXIT_CRITICAL() 호출 | 인터럽트 재활성화, 이전 인터럽트 상태 복원 |
기본 사용 구조:
#include "FreeRTOS.h"
#include "task.h"
volatile uint32_t sharedCounter = 0;
void safe_increment(void) {
taskENTER_CRITICAL();
/* 이 구간은 인터럽트와 컨텍스트 스위칭으로부터 보호됨 */
uint32_t temp = sharedCounter;
temp++;
sharedCounter = temp;
taskEXIT_CRITICAL();
}
FreeRTOS의 Critical Section은 중첩 호출을 지원합니다. 내부적으로 중첩 카운터를 유지하며, taskEXIT_CRITICAL()은 카운터가 0이 될 때만 인터럽트를 실제로 재활성화합니다.
void outer_function(void) {
taskENTER_CRITICAL(); /* 중첩 카운터: 1, 인터럽트 비활성화 */
inner_function(); /* 내부에서 추가 중첩 호출 */
taskEXIT_CRITICAL(); /* 중첩 카운터: 0, 인터럽트 재활성화 */
}
void inner_function(void) {
taskENTER_CRITICAL(); /* 중첩 카운터: 2, 이미 비활성화 상태 유지 */
/* 공유 자원 접근 */
sharedCounter++;
taskEXIT_CRITICAL(); /* 중첩 카운터: 1, 아직 인터럽트 비활성화 유지 */
}
중첩 호출 시
taskENTER_CRITICAL()과taskEXIT_CRITICAL()의 호출 횟수는 반드시 일치해야 합니다. 불일치가 발생하면 인터럽트가 영구적으로 비활성화되거나 조기에 재활성화되는 문제가 발생합니다.
FreeRTOS는 configMAX_SYSCALL_INTERRUPT_PRIORITY를 기준으로 인터럽트를 분류합니다.
높은 우선순위
|
| [ 우선순위 레벨 1 ] <-- taskENTER_CRITICAL()로 차단되지 않음
| [ 우선순위 레벨 2 ] <-- FreeRTOS API 호출 불가 ISR
| ── configMAX_SYSCALL_INTERRUPT_PRIORITY ──
| [ 우선순위 레벨 3 ] <-- taskENTER_CRITICAL()로 차단됨
| [ 우선순위 레벨 4 ] <-- FreeRTOS API 호출 가능 ISR
낮은 우선순위
configMAX_SYSCALL_INTERRUPT_PRIORITY보다 높은 우선순위의 인터럽트는 Critical Section 내에서도 실행될 수 있습니다. 따라서 이 구간에서는 FreeRTOS API를 절대 호출해서는 안 됩니다.
/* FreeRTOSConfig.h 예시 */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( 5 << (8 - configPRIO_BITS) )
일반 Task에서 사용하는 taskENTER_CRITICAL() / taskEXIT_CRITICAL()은 ISR(Interrupt Service Routine)에서 사용할 수 없습니다. ISR 내부에서는 별도의 매크로를 사용해야 합니다.
| 환경 | 진입 매크로 | 퇴출 매크로 |
|---|---|---|
| Task | taskENTER_CRITICAL() | taskEXIT_CRITICAL() |
| ISR | taskENTER_CRITICAL_FROM_ISR() | taskEXIT_CRITICAL_FROM_ISR() |
ISR에서의 사용 구조:
void UART_IRQHandler(void) {
/* 이전 인터럽트 마스크 상태를 저장 */
UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
/* Critical Section */
if(rxBuffer.count < BUFFER_SIZE) {
rxBuffer.data[rxBuffer.head] = UART->DR;
rxBuffer.head = (rxBuffer.head + 1) % BUFFER_SIZE;
rxBuffer.count++;
}
/* 저장된 상태로 복원 */
taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus);
}
taskENTER_CRITICAL_FROM_ISR()은 반환값으로 이전 인터럽트 마스크 상태를 반환합니다. 이 값을taskEXIT_CRITICAL_FROM_ISR()에 전달해야 올바르게 이전 상태로 복원됩니다.
ISR과 Task가 동일한 자원을 공유할 때, 양쪽 모두 적절한 보호 매크로를 사용해야 합니다.
#include "FreeRTOS.h"
#include "task.h"
#define BUFFER_SIZE 32
typedef struct {
volatile uint8_t data[BUFFER_SIZE];
volatile uint32_t head;
volatile uint32_t tail;
volatile uint32_t count;
} SharedBuffer;
SharedBuffer uartRxBuffer = {0};
/* ISR에서 데이터 쓰기 */
void UART_RX_IRQHandler(void) {
UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
if(uartRxBuffer.count < BUFFER_SIZE) {
uartRxBuffer.data[uartRxBuffer.head] = UART->DR;
uartRxBuffer.head = (uartRxBuffer.head + 1) % BUFFER_SIZE;
uartRxBuffer.count++;
}
taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus);
}
/* Task에서 데이터 읽기 */
void vUartProcessTask(void *pvParameters) {
uint8_t byte;
while(1) {
taskENTER_CRITICAL();
if(uartRxBuffer.count > 0) {
byte = uartRxBuffer.data[uartRxBuffer.tail];
uartRxBuffer.tail = (uartRxBuffer.tail + 1) % BUFFER_SIZE;
uartRxBuffer.count--;
}
taskEXIT_CRITICAL();
/* 읽어온 데이터 처리 */
process_byte(byte);
vTaskDelay(pdMS_TO_TICKS(10));
}
}
taskENTER_CRITICAL_FROM_ISR()은 이미 ISR 내부에서 실행 중이므로, 호출 시 추가적인 인터럽트 마스킹 레벨 조정만 수행하고 스케줄러 중단은 포함하지 않습니다. 이는 Task용 Critical Section과의 핵심적인 차이입니다.
/* Task용: 스케줄러 + 인터럽트 차단 */
taskENTER_CRITICAL();
/* ... */
taskEXIT_CRITICAL();
/* ISR용: 추가 인터럽트 레벨 차단만 수행 */
UBaseType_t uxSaved = taskENTER_CRITICAL_FROM_ISR();
/* ... */
taskEXIT_CRITICAL_FROM_ISR(uxSaved);
Critical Section은 인터럽트를 비활성화하므로, 내부에서 시스템 응답성에 영향을 주는 동작을 수행해서는 안 됩니다.
void wrong_usage(void) {
taskENTER_CRITICAL();
/* 금지: vTaskDelay() 호출 - 스케줄러 사용 불가 */
vTaskDelay(pdMS_TO_TICKS(100));
/* 금지: xSemaphoreTake() 등 블로킹 API 호출 */
xSemaphoreTake(xMutex, portMAX_DELAY);
/* 금지: printf() 등 긴 수행 시간이 소요되는 함수 */
printf("Critical Section 내부\n");
taskEXIT_CRITICAL();
}
void correct_usage(void) {
taskENTER_CRITICAL();
/* 허용: 단순 변수 접근 및 산술 연산 */
sharedCounter++;
sharedFlag = pdTRUE;
/* 허용: 구조체 멤버 읽기/쓰기 */
sensorData.temperature = read_sensor();
taskEXIT_CRITICAL();
}
Critical Section은 가능한 한 짧게 유지해야 합니다. 긴 Critical Section은 인터럽트 응답 지연(Interrupt Latency)을 증가시켜 실시간 성능을 저하시킵니다.
/* 잘못된 예: 불필요하게 긴 Critical Section */
void bad_example(void) {
taskENTER_CRITICAL();
uint32_t result = 0;
for(int i = 0; i < 1000; i++) {
result += heavy_computation(i); /* 긴 연산 */
}
sharedResult = result;
taskEXIT_CRITICAL();
}
/* 올바른 예: 공유 자원 접근만 보호 */
void good_example(void) {
uint32_t result = 0;
/* 공유 자원 접근 없는 연산은 Critical Section 외부에서 수행 */
for(int i = 0; i < 1000; i++) {
result += heavy_computation(i);
}
/* 공유 자원 접근 구간만 보호 */
taskENTER_CRITICAL();
sharedResult = result;
taskEXIT_CRITICAL();
}
인터럽트 비활성화 없이 Task 간 경쟁만 방지하려면 스케줄러 일시 중단을 사용할 수 있습니다. 단, 이 방법은 ISR로부터는 보호되지 않습니다.
/* 방법 1: Critical Section (인터럽트 + Task 모두 차단) */
void method_critical(void) {
taskENTER_CRITICAL();
shared_data++;
taskEXIT_CRITICAL();
}
/* 방법 2: 스케줄러 일시 중단 (Task만 차단, ISR은 계속 실행) */
void method_scheduler(void) {
vTaskSuspendAll();
shared_data++; /* ISR로부터는 보호되지 않음 */
xTaskResumeAll();
}
| 특성 | taskENTER_CRITICAL() | vTaskSuspendAll() |
|---|---|---|
| Task 간 보호 | 가능 | 가능 |
| ISR로부터 보호 | 가능 | 불가능 |
| 인터럽트 응답 | 차단됨 | 유지됨 |
| 적합한 상황 | 짧은 원자적 연산 | ISR과 공유하지 않는 긴 연산 |
portDISABLE_INTERRUPTS() / portENABLE_INTERRUPTS()는 FreeRTOS 스케줄러와 독립적으로 모든 인터럽트를 제어합니다. 중첩 카운터를 유지하지 않으므로 일반적으로는 taskENTER_CRITICAL() 사용이 권장됩니다.
일반적으론 인터럽트를 하드웨어적으로 차단하는 방식보다 크리티컬 섹션에 진입/탈출 하는 방식이 더 안전하므로 이 방식은 그냥 있다 정도로만 알아두시기 권장합니다.
/* portDISABLE_INTERRUPTS(): 중첩 추적 없음, 주의해서 사용 */
portDISABLE_INTERRUPTS();
sharedCounter++;
portENABLE_INTERRUPTS();
/* taskENTER_CRITICAL(): 중첩 추적 포함, 일반적으로 권장 */
taskENTER_CRITICAL();
sharedCounter++;
taskEXIT_CRITICAL();
Race Condition이 발생하는 카운터 예제에 Critical Section을 적용하여 데이터 일관성을 보장합니다.
#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 vSafeCounterTask(void *pvParameters) {
int taskId = (int)pvParameters;
printf("[Task %d] 증가 시작\n", taskId);
for(int i = 0; i < NUM_INCREMENTS; i++) {
taskENTER_CRITICAL();
sharedCounter++;
taskEXIT_CRITICAL();
if(i % 1000 == 0) {
taskYIELD();
}
}
printf("[Task %d] 증가 완료\n", taskId);
taskENTER_CRITICAL();
taskCompletionCount++;
taskEXIT_CRITICAL();
vTaskDelete(NULL);
}
void vResultTask(void *pvParameters) {
while(1) {
uint32_t count;
taskENTER_CRITICAL();
count = taskCompletionCount;
taskEXIT_CRITICAL();
if(count >= NUM_TASKS) {
uint32_t expectedValue = NUM_INCREMENTS * NUM_TASKS;
uint32_t actualValue = sharedCounter;
printf("\n=== 테스트 결과 ===\n");
printf("기대값: %lu\n", expectedValue);
printf("실제값: %lu\n", actualValue);
if(actualValue == expectedValue) {
printf("상태: 정상 - Critical Section 보호 성공\n");
} else {
printf("상태: 오류 - 예상치 못한 불일치 발생\n");
}
vTaskDelete(NULL);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
int main(void) {
printf("=== Critical Section 카운터 보호 ===\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(vSafeCounterTask, taskName, 256, (void*)(i + 1), 2, NULL);
}
xTaskCreate(vResultTask, "Result", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
여러 멤버로 구성된 구조체에 대해 Critical Section을 적용하여 일관된 읽기/쓰기를 보장합니다.
#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 (uint8_t)((data->timestamp + data->temperature + data->humidity) & 0xFF);
}
void vSensorUpdateTask(void *pvParameters) {
uint32_t counter = 0;
while(1) {
SensorData newData;
newData.timestamp = xTaskGetTickCount();
newData.temperature = (int16_t)(20 + (counter % 10));
newData.humidity = (uint16_t)(50 + (counter % 20));
newData.checksum = calculate_checksum(&newData);
/* 구조체 전체를 Critical Section 내에서 원자적으로 업데이트 */
taskENTER_CRITICAL();
sensorReading = newData;
taskEXIT_CRITICAL();
counter++;
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void vSensorReadTask(void *pvParameters) {
uint32_t errorCount = 0;
uint32_t successCount = 0;
while(1) {
SensorData localCopy;
/* 구조체 전체를 Critical Section 내에서 원자적으로 복사 */
taskENTER_CRITICAL();
localCopy = sensorReading;
taskEXIT_CRITICAL();
uint8_t calculatedChecksum = calculate_checksum(&localCopy);
if(calculatedChecksum != localCopy.checksum) {
errorCount++;
printf("[Reader] 체크섬 오류 감지 #%lu!\n", errorCount);
printf(" 저장 체크섬: %u, 계산 체크섬: %u\n",
localCopy.checksum, calculatedChecksum);
} else {
successCount++;
}
vTaskDelay(pdMS_TO_TICKS(150));
}
}
void vStatusTask(void *pvParameters) {
while(1) {
vTaskDelay(pdMS_TO_TICKS(3000));
printf("\n=== 센서 데이터 상태 ===\n");
taskENTER_CRITICAL();
printf("시간: %lu, 온도: %d, 습도: %u\n",
sensorReading.timestamp,
sensorReading.temperature,
sensorReading.humidity);
taskEXIT_CRITICAL();
}
}
int main(void) {
printf("=== 구조체 일관성 보호 실습 ===\n\n");
xTaskCreate(vSensorUpdateTask, "Update", 256, NULL, 2, NULL);
xTaskCreate(vSensorReadTask, "Read", 256, NULL, 2, NULL);
xTaskCreate(vStatusTask, "Status", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
ISR에서 데이터를 수신하고 Task에서 처리하는 구조에서 각 환경에 맞는 Critical Section을 적용합니다.
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include <string.h>
#define RX_BUFFER_SIZE 64
typedef struct {
uint8_t data[RX_BUFFER_SIZE];
volatile uint32_t head;
volatile uint32_t tail;
volatile uint32_t count;
} RxBuffer;
RxBuffer rxBuffer = {0};
/* ISR 시뮬레이션: 실제 환경에서는 UART IRQ Handler에 해당 */
void simulate_isr_receive(uint8_t byte) {
UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
if(rxBuffer.count < RX_BUFFER_SIZE) {
rxBuffer.data[rxBuffer.head] = byte;
rxBuffer.head = (rxBuffer.head + 1) % RX_BUFFER_SIZE;
rxBuffer.count++;
}
taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus);
}
BaseType_t buffer_read(uint8_t *outByte) {
BaseType_t result = pdFALSE;
taskENTER_CRITICAL();
if(rxBuffer.count > 0) {
*outByte = rxBuffer.data[rxBuffer.tail];
rxBuffer.tail = (rxBuffer.tail + 1) % RX_BUFFER_SIZE;
rxBuffer.count--;
result = pdTRUE;
}
taskEXIT_CRITICAL();
return result;
}
/* ISR 시뮬레이션을 위한 Task */
void vSimulatedIsrTask(void *pvParameters) {
uint8_t byteToSend = 0;
while(1) {
simulate_isr_receive(byteToSend);
byteToSend++;
vTaskDelay(pdMS_TO_TICKS(20));
}
}
void vProcessTask(void *pvParameters) {
uint8_t receivedByte;
uint32_t totalReceived = 0;
while(1) {
if(buffer_read(&receivedByte)) {
totalReceived++;
printf("[Process] 수신: 0x%02X, 총 수신 횟수: %lu\n",
receivedByte, totalReceived);
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}
void vBufferStatusTask(void *pvParameters) {
while(1) {
vTaskDelay(pdMS_TO_TICKS(1000));
taskENTER_CRITICAL();
uint32_t currentCount = rxBuffer.count;
taskEXIT_CRITICAL();
printf("[Status] 버퍼 사용량: %lu / %d\n",
currentCount, RX_BUFFER_SIZE);
}
}
int main(void) {
printf("=== ISR-Task 공유 버퍼 보호 실습 ===\n\n");
xTaskCreate(vSimulatedIsrTask, "ISR_Sim", 256, NULL, 3, NULL);
xTaskCreate(vProcessTask, "Process", 256, NULL, 2, NULL);
xTaskCreate(vBufferStatusTask, "BufStatus", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
Day 12의 Race Condition 예제였던 은행 계좌 문제에 Critical Section을 적용하여 올바른 잔액 관리를 구현합니다.
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
typedef struct {
volatile int32_t balance;
volatile uint32_t depositCount;
volatile uint32_t withdrawCount;
volatile uint32_t failedCount;
char owner[20];
} BankAccount;
BankAccount account = {1000, 0, 0, 0, "User"};
BaseType_t safe_withdraw(uint32_t amount) {
BaseType_t result = pdFALSE;
taskENTER_CRITICAL();
if(account.balance >= (int32_t)amount) {
account.balance -= (int32_t)amount;
account.withdrawCount++;
result = pdTRUE;
} else {
account.failedCount++;
}
taskEXIT_CRITICAL();
return result;
}
BaseType_t safe_deposit(uint32_t amount) {
taskENTER_CRITICAL();
account.balance += (int32_t)amount;
account.depositCount++;
taskEXIT_CRITICAL();
return pdTRUE;
}
void vWithdrawTask(void *pvParameters) {
int taskId = (int)pvParameters;
for(int i = 0; i < 10; i++) {
if(safe_withdraw(150)) {
printf("[Task %d] 출금 성공: 150원, 잔액: %ld\n",
taskId, account.balance);
} else {
printf("[Task %d] 출금 실패: 잔액 부족 (%ld원)\n",
taskId, account.balance);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
vTaskDelete(NULL);
}
void vDepositTask(void *pvParameters) {
for(int i = 0; i < 5; i++) {
safe_deposit(300);
printf("[Deposit] 입금 성공: 300원, 잔액: %ld\n", account.balance);
vTaskDelay(pdMS_TO_TICKS(200));
}
vTaskDelete(NULL);
}
void vAccountReportTask(void *pvParameters) {
vTaskDelay(pdMS_TO_TICKS(3000));
printf("\n");
printf("=======================================\n");
printf(" 계좌 최종 보고서 \n");
printf("=======================================\n");
taskENTER_CRITICAL();
int32_t finalBalance = account.balance;
uint32_t depositCount = account.depositCount;
uint32_t withdrawCount = account.withdrawCount;
uint32_t failedCount = account.failedCount;
taskEXIT_CRITICAL();
int32_t expectedBalance = 1000
+ (int32_t)(depositCount * 300)
- (int32_t)(withdrawCount * 150);
printf("초기 잔액: 1000원\n");
printf("입금 횟수: %lu회 (+%ld원)\n", depositCount, (int32_t)(depositCount * 300));
printf("출금 횟수: %lu회 (-%ld원)\n", withdrawCount, (int32_t)(withdrawCount * 150));
printf("실패 횟수: %lu회\n", failedCount);
printf("---------------------------------------\n");
printf("기대 잔액: %ld원\n", expectedBalance);
printf("실제 잔액: %ld원\n", finalBalance);
if(finalBalance == expectedBalance && finalBalance >= 0) {
printf("상태: 정상 - Critical Section 보호 성공\n");
} else {
printf("상태: 오류 - 잔액 불일치 또는 음수 잔액\n");
}
printf("=======================================\n");
vTaskDelete(NULL);
}
int main(void) {
printf("=== 은행 계좌 Critical Section 보호 ===\n");
printf("초기 잔액: %ld원\n\n", account.balance);
xTaskCreate(vWithdrawTask, "Withdraw1", 256, (void*)1, 2, NULL);
xTaskCreate(vWithdrawTask, "Withdraw2", 256, (void*)2, 2, NULL);
xTaskCreate(vWithdrawTask, "Withdraw3", 256, (void*)3, 2, NULL);
xTaskCreate(vDepositTask, "Deposit", 256, NULL, 2, NULL);
xTaskCreate(vAccountReportTask,"Report", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
Critical Section의 길이를 검증하기 위해 진입/퇴출 시점의 타임스탬프를 기록합니다.
typedef struct {
TickType_t enterTime;
TickType_t exitTime;
TickType_t duration;
} CriticalSectionStats;
CriticalSectionStats csStats = {0};
void instrumented_critical_section(void) {
csStats.enterTime = xTaskGetTickCount();
taskENTER_CRITICAL();
/* 보호가 필요한 연산 */
sharedCounter++;
taskEXIT_CRITICAL();
csStats.exitTime = xTaskGetTickCount();
csStats.duration = csStats.exitTime - csStats.enterTime;
if(csStats.duration > pdMS_TO_TICKS(1)) {
printf("[경고] Critical Section 길이 초과: %lu ticks\n",
csStats.duration);
}
}
taskENTER_CRITICAL()과 taskEXIT_CRITICAL()의 호출 횟수가 맞지 않을 경우를 감지하기 위한 래퍼 함수를 활용합니다.
volatile int32_t csNestCount = 0;
void debug_enter_critical(void) {
taskENTER_CRITICAL();
csNestCount++;
if(csNestCount > 3) {
printf("[경고] Critical Section 중첩 깊이 이상: %ld\n", csNestCount);
}
}
void debug_exit_critical(void) {
csNestCount--;
if(csNestCount < 0) {
printf("[오류] Critical Section 불균형 퇴출 감지!\n");
csNestCount = 0;
}
taskEXIT_CRITICAL();
}
taskENTER_CRITICAL() / taskEXIT_CRITICAL()
configMAX_SYSCALL_INTERRUPT_PRIORITY 이하의 인터럽트만 차단ISR에서의 Critical Section
taskENTER_CRITICAL_FROM_ISR() / taskEXIT_CRITICAL_FROM_ISR() 사용사용 시 주의사항
스케줄러 일시 중단과의 차이
| 개념 | 설명 |
|---|---|
| taskENTER_CRITICAL() | 인터럽트 비활성화, Task용 Critical Section 진입 |
| taskEXIT_CRITICAL() | 인터럽트 재활성화, Task용 Critical Section 퇴출 |
| taskENTER_CRITICAL_FROM_ISR() | ISR 내에서의 Critical Section 진입, 이전 마스크 반환 |
| taskEXIT_CRITICAL_FROM_ISR() | ISR 내에서의 Critical Section 퇴출, 이전 마스크 복원 |
| 중첩 카운터 | 중첩 Critical Section의 깊이를 추적하는 내부 카운터 |
| Interrupt Latency | Critical Section으로 인한 인터럽트 응답 지연 시간 |
Day 12 이전 강의에서 작성한 Race Condition이 발생하는 링 버퍼 예제에 Critical Section을 적용하여 안전한 버퍼 구현을 완성하세요.
요구사항:
하나의 함수가 다른 Critical Section 보호 함수를 내부적으로 호출하는 중첩 구조를 설계하고, 올바르게 동작하는지 검증하세요.
요구사항:
타이머 ISR에서 주기적으로 데이터를 생성하고, Task에서 처리하는 시스템을 구현하세요.
요구사항:
/* 원인: Critical Section 내부에서 블로킹 API 호출 */
taskENTER_CRITICAL();
vTaskDelay(pdMS_TO_TICKS(100)); /* 오류: 스케줄러 비활성화 상태에서 대기 불가 */
taskEXIT_CRITICAL();
/* 해결: 블로킹 호출을 Critical Section 외부로 이동 */
taskENTER_CRITICAL();
uint32_t value = sharedData;
taskEXIT_CRITICAL();
vTaskDelay(pdMS_TO_TICKS(100)); /* Critical Section 외부에서 대기 */
/* 오류: ISR에서 Task용 매크로 사용 */
void UART_IRQHandler(void) {
taskENTER_CRITICAL(); /* ISR에서 사용 금지 */
rxBuffer.count++;
taskEXIT_CRITICAL();
}
/* 해결: ISR 전용 매크로 사용 */
void UART_IRQHandler(void) {
UBaseType_t uxSaved = taskENTER_CRITICAL_FROM_ISR();
rxBuffer.count++;
taskEXIT_CRITICAL_FROM_ISR(uxSaved);
}
/* 오류: 조건부 퇴출로 인한 불균형 */
void bad_conditional(void) {
taskENTER_CRITICAL();
if(condition) {
return; /* 퇴출 없이 반환: 인터럽트 영구 비활성화 */
}
sharedCounter++;
taskEXIT_CRITICAL();
}
/* 해결: 모든 경로에서 반드시 퇴출 */
void good_conditional(void) {
taskENTER_CRITICAL();
if(!condition) {
sharedCounter++;
}
taskEXIT_CRITICAL(); /* 조건과 무관하게 항상 퇴출 */
}