
함수 원형:
void vTaskDelete(TaskHandle_t xTaskToDelete);
파라미터:
xTaskToDelete: 삭제할 Task의 핸들NULL: 현재 실행 중인 Task 자신을 삭제동작:
삭제 과정:
1. Task 상태를 "삭제됨"으로 변경
↓
2. Ready List에서 제거
↓
3. TCB를 삭제 대기 리스트로 이동
↓
4. Idle Task가 실제 메모리 해제 수행
↓
5. 스택과 TCB 메모리 반환
중요: 실제 메모리 해제는 Idle Task에서 수행됩니다. 따라서 Idle Task가 실행될 수 있는 시간이 필요합니다.
예제 코드:
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
TaskHandle_t xTaskToDeleteHandle;
void vTaskToDelete(void *pvParameters) {
int count = 0;
/* 작업을 5회만 반복 후 종료 */
for(count = 0; count < 5; count++) {
printf("[삭제될 Task] 카운트: %d\n", count);
vTaskDelay(pdMS_TO_TICKS(500));
}
printf("[삭제될 Task] 5회 실행 완료, 종료합니다.\n");
/* 작업이 완료되면 자기 자신을 삭제
* NULL을 전달하면 현재 실행 중인 Task(자기 자신)가 삭제됨 */
vTaskDelete(NULL);
}
void vControlTask(void *pvParameters) {
vTaskDelay(pdMS_TO_TICKS(3000));
printf("[제어 Task] 다른 Task 삭제 시도\n");
if(xTaskToDeleteHandle != NULL) {
/* 다른 Task의 핸들을 사용하여 해당 Task를 삭제 */
vTaskDelete(xTaskToDeleteHandle);
printf("[제어 Task] Task 삭제 완료\n");
/* 삭제 후 핸들을 NULL로 설정하여 중복 삭제 방지 */
xTaskToDeleteHandle = NULL;
}
vTaskDelete(NULL);
}
int main(void) {
xTaskCreate(vTaskToDelete, "ToDelete", 256, NULL, 2, &xTaskToDeleteHandle);
/* 외부에서 Task를 삭제하려면 아래 주석을 해제 */
// xTaskCreate(vControlTask, "Control", 256, NULL, 3, NULL);
vTaskStartScheduler();
while(1);
}
올바른 Task 삭제 패턴:
void vGoodTask(void *pvParameters) {
int maxIterations = 10;
/* for 루프를 사용하여 정해진 횟수만큼 작업 수행 */
for(int i = 0; i < maxIterations; i++) {
printf("작업 수행: %d/%d\n", i+1, maxIterations);
vTaskDelay(pdMS_TO_TICKS(1000));
}
printf("작업 완료, Task 삭제\n");
/* for 루프가 종료된 후 vTaskDelete 호출
* 이 위치에서 호출하면 정상적으로 실행됨 */
vTaskDelete(NULL);
}
주의: while(1) 무한 루프 안에 vTaskDelete()를 넣으면 해당 코드는 절대 실행되지 않습니다. Task를 삭제하려면 루프를 빠져나올 수 있는 조건이 필요합니다.
패턴 1: 작업 완료 후 삭제
void vOneTimeTask(void *pvParameters) {
printf("[일회성 Task] 초기화 시작\n");
// 초기화 작업 수행
initialize_hardware();
configure_peripherals();
printf("[일회성 Task] 초기화 완료\n");
// 작업 완료, 자기 자신 삭제
vTaskDelete(NULL);
// 이 이후 코드는 실행되지 않음
}
int main(void) {
xTaskCreate(vOneTimeTask, "OneTime", 256, NULL, 2, NULL);
vTaskStartScheduler();
while(1);
}
패턴 2: 조건부 삭제
void vConditionalTask(void *pvParameters) {
int errorCount = 0;
const int maxErrors = 5;
while(1) {
if(process_data() != SUCCESS) {
errorCount++;
printf("오류 발생 (%d/%d)\n", errorCount, maxErrors);
/* 오류 횟수가 최대 허용치를 초과하면 Task 종료
* vTaskDelete() 호출 후의 코드는 실행되지 않음 */
if(errorCount >= maxErrors) {
printf("최대 오류 횟수 초과, Task 종료\n");
vTaskDelete(NULL);
}
} else {
/* 정상 처리 시 오류 카운터 초기화 */
errorCount = 0;
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
패턴 3: 시간 제한 Task
void vTimeLimitedTask(void *pvParameters) {
TickType_t startTime = xTaskGetTickCount();
const TickType_t maxRunTime = pdMS_TO_TICKS(10000); // 10초
while(1) {
TickType_t currentTime = xTaskGetTickCount();
TickType_t elapsedTime = currentTime - startTime;
if(elapsedTime >= maxRunTime) {
printf("[제한 Task] 실행 시간 초과 (10초), 종료\n");
vTaskDelete(NULL);
}
printf("[제한 Task] 실행 중... (%lu ms / %lu ms)\n",
elapsedTime, maxRunTime);
perform_work();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
| 특성 | 자기 삭제 (NULL) | 다른 Task 삭제 |
|---|---|---|
| 안전성 | 안전 (자신의 상태 파악) | 주의 필요 (동기화) |
| 리소스 정리 | 정리 후 삭제 가능 | 강제 종료 |
| 사용 시점 | 작업 완료, 자발적 종료 | 외부 제어, 강제 종료 |
| 제어 흐름 | 예측 가능 | 예측 어려움 |
자기 삭제 예제:
void vSelfDeletingTask(void *pvParameters) {
/* 동적 메모리 할당 */
uint8_t *buffer = pvPortMalloc(1024);
/* 메모리 할당 실패 시 Task 즉시 종료 */
if(buffer == NULL) {
printf("메모리 할당 실패\n");
vTaskDelete(NULL);
return;
}
/* 작업 수행 */
for(int i = 0; i < 10; i++) {
printf("작업 진행: %d/10\n", i+1);
process_buffer(buffer);
vTaskDelay(pdMS_TO_TICKS(500));
}
/* 리소스 정리 - Task 삭제 전 반드시 할당된 메모리 해제 */
vPortFree(buffer);
printf("리소스 정리 완료\n");
/* 자기 자신 삭제 */
vTaskDelete(NULL);
}
메모리 누수는 주로 다음과 같은 상황에서 발생합니다:
원인 1: 동적 할당 후 미해제
원인 2: 외부 삭제 시 정리 코드 미실행
원인 3: 예외 상황에서 조기 종료
올바른 메모리 관리 예제:
void vProperMemoryManagement(void *pvParameters) {
uint8_t *buffer = NULL;
/* 메모리 할당 */
buffer = pvPortMalloc(256);
if(buffer == NULL) {
printf("메모리 할당 실패\n");
vTaskDelete(NULL);
return;
}
/* 작업 수행 */
for(int i = 0; i < 10; i++) {
process_data(buffer);
vTaskDelay(pdMS_TO_TICKS(1000));
}
/* 메모리 해제 - 종료 전 반드시 실행 */
vPortFree(buffer);
buffer = NULL;
vTaskDelete(NULL);
}
패턴 1: RAII(Resource Acquisition Is Initialization) 스타일 (C++)
자원의 획득은 초기화와 동시에 이루어져야 한다는 뜻으로 자원(메모리, 파일, 뮤텍스 등)의 수명을 객체의 수명과 일치시키는 기법입니다. RAII는 생성자와 소멸자를 이용하며 다음과 같이 동작합니다.
생성자에서 자원 획득: 객체가 생성될 때 메모리 할당이나 파일을 엽니다. 소멸자에서 자원 해제: 객체가 스코프{}를 벗어나 사라질 때 자동으로 소멸자가 호출되며 자원을 반납합니다.
#include "FreeRTOS.h"
#include "task.h"
#include <memory>
#include <iostream>
class Resource {
private:
uint8_t *buffer;
size_t size;
public:
Resource(size_t s) : size(s) {
buffer = static_cast<uint8_t*>(pvPortMalloc(s));
if(buffer) {
std::cout << "리소스 할당: " << s << " bytes" << std::endl;
}
}
/* 소멸자에서 자동으로 메모리 해제
* 객체가 스코프를 벗어나면 자동 호출됨 */
~Resource() {
if(buffer) {
vPortFree(buffer);
std::cout << "리소스 해제" << std::endl;
}
}
uint8_t* get() { return buffer; }
bool isValid() const { return buffer != nullptr; }
};
class SafeTask : public Task {
public:
SafeTask() : Task("Safe", 512, 2) {}
void run() override {
/* Resource 객체 생성 - 자동으로 메모리 관리됨 */
Resource res(1024);
if(!res.isValid()) {
std::cout << "리소스 할당 실패" << std::endl;
/* return 시 res의 소멸자가 자동으로 호출되어 정리됨 */
return;
}
for(int i = 0; i < 10; i++) {
std::cout << "작업 " << i << std::endl;
process_buffer(res.get());
vTaskDelay(pdMS_TO_TICKS(500));
}
/* 함수 종료 시 res의 소멸자가 자동으로 호출되어 메모리 해제 */
}
};
패턴 2: Cleanup 함수 (C)
typedef struct {
uint8_t *buffer;
FILE *file;
bool initialized;
} TaskResources;
/* 모든 리소스를 안전하게 해제하는 함수
* NULL 포인터 체크를 통해 부분 할당된 리소스도 안전하게 처리 */
void cleanup_resources(TaskResources *res) {
if(res == NULL) return;
if(res->buffer != NULL) {
vPortFree(res->buffer);
res->buffer = NULL;
printf("버퍼 해제\n");
}
if(res->file != NULL) {
fclose(res->file);
res->file = NULL;
printf("파일 닫기\n");
}
res->initialized = false;
}
void vSafeTask(void *pvParameters) {
/* 구조체 초기화 - 모든 포인터를 NULL로 설정 */
TaskResources res = {0};
/* 리소스 할당 */
res.buffer = pvPortMalloc(512);
res.file = fopen("log.txt", "w");
/* 할당 실패 시 cleanup_resources 호출
* 부분적으로 할당된 리소스도 안전하게 해제됨 */
if(res.buffer == NULL || res.file == NULL) {
printf("리소스 할당 실패\n");
cleanup_resources(&res);
vTaskDelete(NULL);
return;
}
res.initialized = true;
/* 작업 수행 */
for(int i = 0; i < 10; i++) {
if(process_data(res.buffer, res.file) != SUCCESS) {
printf("처리 오류 발생\n");
break;
}
vTaskDelay(pdMS_TO_TICKS(500));
}
/* 모든 종료 경로에서 cleanup_resources 호출
* 정상 종료든 오류 종료든 리소스가 안전하게 해제됨 */
cleanup_resources(&res);
vTaskDelete(NULL);
}
패턴 3: Goto를 이용한 정리 (C)
void vTaskWithCleanup(void *pvParameters) {
uint8_t *buffer1 = NULL;
uint8_t *buffer2 = NULL;
FILE *file = NULL;
int result = -1;
/* 리소스 순차 할당 및 오류 처리
* 각 할당 실패 시 goto cleanup으로 점프하여 정리 코드 실행 */
buffer1 = pvPortMalloc(256);
if(buffer1 == NULL) {
goto cleanup;
}
buffer2 = pvPortMalloc(512);
if(buffer2 == NULL) {
goto cleanup;
}
file = fopen("data.txt", "r");
if(file == NULL) {
goto cleanup;
}
/* 모든 리소스 할당 성공 시 작업 수행 */
result = process_everything(buffer1, buffer2, file);
cleanup:
/* 모든 종료 경로가 이곳으로 모임
* NULL 체크를 통해 할당된 리소스만 해제 */
if(buffer1) vPortFree(buffer1);
if(buffer2) vPortFree(buffer2);
if(file) fclose(file);
printf("정리 완료 (결과: %d)\n", result);
vTaskDelete(NULL);
}
힙 사용량 모니터링:
void vMemoryMonitorTask(void *pvParameters) {
while(1) {
size_t freeHeap = xPortGetFreeHeapSize();
size_t minEverFree = xPortGetMinimumEverFreeHeapSize();
printf("\n=== 메모리 상태 ===\n");
printf("현재 여유 Heap: %u bytes\n", freeHeap);
printf("최소 여유 Heap: %u bytes\n", minEverFree);
printf("사용 중: %u bytes\n",
configTOTAL_HEAP_SIZE - freeHeap);
/* 여유 메모리가 1KB 미만이면 경고 출력
* 메모리 부족 상황을 조기에 감지 */
if(freeHeap < 1024) {
printf("경고: Heap 부족!\n");
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
Task별 스택 사용량 체크:
/* 개별 Task의 스택 여유 공간 확인
* High Water Mark가 낮으면 스택 오버플로우 위험 */
void check_task_stack(TaskHandle_t handle, const char *name) {
UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(handle);
printf("[%s] 스택 여유: %u words\n", name, stackHighWaterMark);
/* 여유 스택이 50 words 미만이면 경고
* 스택 크기 증가 필요 */
if(stackHighWaterMark < 50) {
printf("경고: [%s] 스택 부족 위험!\n", name);
}
}
void vTaskStackMonitor(void *pvParameters) {
TaskHandle_t taskHandles[5];
const char *taskNames[] = {"Task1", "Task2", "Task3", "Task4", "Task5"};
/* 실제 구현에서는 Task 생성 시 핸들을 저장해야 함 */
while(1) {
printf("\n=== Task 스택 상태 ===\n");
for(int i = 0; i < 5; i++) {
if(taskHandles[i] != NULL) {
check_task_stack(taskHandles[i], taskNames[i]);
}
}
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
패턴 1: On-Demand Task
On Demand Task는 특정 이벤트나 요청이 있을 때만 동적으로 생성되어 실행되고, 할 일이 끝나면 스스로 삭제되는 태스크를 의미합니다. On Demand Task는 보통 다음과 같은 라이프 사이클을 가집니다.
Trigger: 인터럽트(ISR), 특정 메시지 수신, 혹은 사용자 버튼 클릭 등의 이벤트 발생. Creation: 메인 제어 태스크나 스케줄러가 xTaskCreate()(FreeRTOS 기준) 등을 호출하여 해당 태스크를 메모리에 올림. Execution: 부여된 작업을 수행 (예: 대용량 로그 파일 저장, 네트워크 패킷 전송). Self-Termination: 작업이 완료되면 vTaskDelete(NULL) 등을 호출하여 자신의 리소스를 반납하고 종료.
TaskHandle_t xDynamicTaskHandle = NULL;
void vDynamicWorker(void *pvParameters) {
int *workId = (int*)pvParameters;
printf("[동적 Worker %d] 시작\n", *workId);
/* 5회 작업 수행 */
for(int i = 0; i < 5; i++) {
printf("[동적 Worker %d] 작업 진행: %d/5\n", *workId, i+1);
vTaskDelay(pdMS_TO_TICKS(500));
}
printf("[동적 Worker %d] 완료, 자동 삭제\n", *workId);
/* 파라미터로 전달받은 메모리 해제
* Task 생성 시 동적 할당한 메모리 정리 */
vPortFree(pvParameters);
/* 핸들 초기화하여 다음 Task 생성 가능하도록 함 */
xDynamicTaskHandle = NULL;
vTaskDelete(NULL);
}
void vManagerTask(void *pvParameters) {
int workCount = 0;
while(1) {
printf("\n[Manager] 새 작업 생성 요청...\n");
/* 이전 Worker가 완료되었는지 확인 */
if(xDynamicTaskHandle == NULL) {
/* Work ID를 동적 할당하여 Task에 전달 */
int *workId = pvPortMalloc(sizeof(int));
*workId = ++workCount;
BaseType_t result = xTaskCreate(
vDynamicWorker,
"DynWorker",
256,
workId,
2,
&xDynamicTaskHandle
);
if(result == pdPASS) {
printf("[Manager] Worker %d 생성 완료\n", *workId);
} else {
printf("[Manager] Task 생성 실패\n");
/* 생성 실패 시 할당한 메모리 해제 */
vPortFree(workId);
}
} else {
printf("[Manager] 이전 Worker 실행 중, 대기...\n");
}
vTaskDelay(pdMS_TO_TICKS(3000));
}
}
int main(void) {
xTaskCreate(vManagerTask, "Manager", 512, NULL, 3, NULL);
vTaskStartScheduler();
while(1);
}
패턴 2: Task Pool
Task Pool은 On Demand Task의 유연성과 Static Task의 안정성을 합친 개념으로, 미리 일정 개수의 태스크를 만들어 두고, 작업(Job)이 들어올 때마다 하나씩 할당해주는 방식입니다. 태스크 풀의 라이프 사이클은 다음과 같습니다.
Pool 생성: 시스템 초기화 시점에 스택을 미리 할당받은 태스크(Worker)들을 여러 개 생성합니다. 이들은 처음엔 '대기(Blocked)' 상태입니다. Job Queue: 처리해야 할 작업들을 담는 큐(Queue)를 운영합니다. Dispatch: 새로운 작업이 들어오면, 쉬고 있는 태스크 중 하나를 깨워 작업을 맡깁니다. Recycle: 태스크는 작업을 마치면 스스로 삭제되지 않고, 다시 큐를 바라보며 다음 작업을 기다리는 대기 상태로 돌아갑니다.
#define MAX_WORKERS 5
typedef struct {
TaskHandle_t handle;
bool active;
int workId;
} WorkerInfo;
WorkerInfo workerPool[MAX_WORKERS] = {0};
void vPoolWorker(void *pvParameters) {
WorkerInfo *info = (WorkerInfo*)pvParameters;
printf("[Pool Worker %d] 작업 시작\n", info->workId);
// 작업 수행
for(int i = 0; i < 10; i++) {
printf("[Pool Worker %d] 진행률: %d%%\n",
info->workId, (i+1)*10);
vTaskDelay(pdMS_TO_TICKS(300));
}
printf("[Pool Worker %d] 완료\n", info->workId);
// Pool에서 제거
info->active = false;
info->handle = NULL;
vTaskDelete(NULL);
}
bool create_worker(int workId) {
// 빈 슬롯 찾기
for(int i = 0; i < MAX_WORKERS; i++) {
if(!workerPool[i].active) {
workerPool[i].workId = workId;
workerPool[i].active = true;
BaseType_t result = xTaskCreate(
vPoolWorker,
"PoolWorker",
256,
&workerPool[i],
2,
&workerPool[i].handle
);
if(result == pdPASS) {
printf("Worker %d 생성 (슬롯 %d)\n", workId, i);
return true;
} else {
workerPool[i].active = false;
return false;
}
}
}
printf("Pool 가득 찼습니다\n");
return false;
}
int get_active_workers() {
int count = 0;
for(int i = 0; i < MAX_WORKERS; i++) {
if(workerPool[i].active) count++;
}
return count;
}
void vPoolManagerTask(void *pvParameters) {
int workId = 0;
while(1) {
printf("\n[Pool Manager] 활성 Worker: %d/%d\n",
get_active_workers(), MAX_WORKERS);
if(get_active_workers() < MAX_WORKERS) {
create_worker(++workId);
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
패턴 1: 삭제 요청 플래그
typedef struct {
TaskHandle_t handle;
volatile bool deleteRequested; /* 삭제 요청 플래그 (다른 Task에서 설정) */
volatile bool isRunning; /* Task 실행 상태 */
} ControlledTask;
void vControlledWorker(void *pvParameters) {
ControlledTask *ctrl = (ControlledTask*)pvParameters;
ctrl->isRunning = true;
/* deleteRequested 플래그를 주기적으로 확인하며 작업 수행
* 다른 Task에서 플래그를 true로 설정하면 루프 종료 */
while(!ctrl->deleteRequested) {
printf("[제어 Worker] 작업 수행 중...\n");
perform_work();
vTaskDelay(pdMS_TO_TICKS(500));
}
printf("[제어 Worker] 삭제 요청 받음, 정리 중...\n");
/* 리소스 정리 수행 */
cleanup_resources();
/* 실행 상태 플래그를 false로 설정하여 종료 완료 알림 */
ctrl->isRunning = false;
vTaskDelete(NULL);
}
/* 안전하게 Task 삭제를 요청하는 함수 */
void request_task_deletion(ControlledTask *ctrl) {
if(ctrl->isRunning) {
printf("Task 삭제 요청...\n");
/* 삭제 요청 플래그 설정 */
ctrl->deleteRequested = true;
/* Task가 종료될 때까지 대기 (폴링 방식)
* isRunning이 false가 되면 Task가 안전하게 종료된 것 */
while(ctrl->isRunning) {
vTaskDelay(pdMS_TO_TICKS(10));
}
printf("Task 안전하게 종료됨\n");
}
}
패턴 2: 동기화를 이용한 삭제
#include "semphr.h"
typedef struct {
TaskHandle_t handle;
SemaphoreHandle_t deleteSemaphore; /* 종료 신호용 세마포어 */
volatile bool running;
} SyncTask;
void vSyncWorker(void *pvParameters) {
SyncTask *task = (SyncTask*)pvParameters;
task->running = true;
while(1) {
/* 세마포어를 100ms 타임아웃으로 대기
* 세마포어를 받으면(종료 신호) 루프 종료
* 타임아웃되면 정상 작업 계속 수행 */
if(xSemaphoreTake(task->deleteSemaphore, pdMS_TO_TICKS(100)) == pdTRUE) {
printf("[Sync Worker] 종료 신호 받음\n");
break;
}
/* 정상 작업 수행 */
printf("[Sync Worker] 작업 중...\n");
perform_work();
}
/* 리소스 정리 */
cleanup_resources();
task->running = false;
/* 세마포어 삭제 */
vSemaphoreDelete(task->deleteSemaphore);
vTaskDelete(NULL);
}
void create_sync_task(SyncTask *task) {
/* 바이너리 세마포어 생성 (초기값 0) */
task->deleteSemaphore = xSemaphoreCreateBinary();
xTaskCreate(
vSyncWorker,
"SyncWorker",
256,
task,
2,
&task->handle
);
}
void delete_sync_task(SyncTask *task) {
if(task->running) {
/* 세마포어 Give로 종료 신호 전송
* Worker Task의 xSemaphoreTake가 성공하여 종료 시작 */
xSemaphoreGive(task->deleteSemaphore);
/* Task가 완전히 종료될 때까지 대기 */
while(task->running) {
vTaskDelay(pdMS_TO_TICKS(10));
}
}
}
생성자와 소멸자를 이용한 수명 주기 관리 기법은 C에서는 사용할 수 없습니다. 대신 C언어에선 앞서 설명한 goto를 이용한 cleanup label을 이용하거나 wrapper 함수를 이용한 생명 주기 제어를 이용합니다. 다음은 c++에서 사용 가능한 생성자와 소멸자를 이용해 태스크가 스코프를 벗어날 때 자동으로 종료되는 예제 코드입니다.
#include "FreeRTOS.h"
#include "task.h"
#include <iostream>
#include <memory>
#include <vector>
class ManagedTask {
protected:
TaskHandle_t taskHandle;
const char* taskName;
uint16_t stackSize;
UBaseType_t priority;
volatile bool stopRequested;
volatile bool isRunning;
virtual void run() = 0;
static void taskEntry(void* pvParameters) {
ManagedTask* task = static_cast<ManagedTask*>(pvParameters);
task->isRunning = true;
task->run();
task->isRunning = false;
vTaskDelete(nullptr);
}
public:
ManagedTask(const char* name, uint16_t stack, UBaseType_t prio)
: taskName(name), stackSize(stack), priority(prio),
taskHandle(nullptr), stopRequested(false), isRunning(false) {}
bool start() {
BaseType_t result = xTaskCreate(
taskEntry,
taskName,
stackSize,
this,
priority,
&taskHandle
);
if(result == pdPASS) {
std::cout << "Task [" << taskName << "] 생성 완료" << std::endl;
return true;
} else {
std::cout << "Task [" << taskName << "] 생성 실패" << std::endl;
return false;
}
}
void requestStop() {
stopRequested = true;
}
void waitForStop() {
while(isRunning) {
vTaskDelay(pdMS_TO_TICKS(10));
}
std::cout << "Task [" << taskName << "] 종료 완료" << std::endl;
}
bool isStopped() const { return !isRunning; }
virtual ~ManagedTask() {
if(isRunning) {
requestStop();
waitForStop();
}
}
};
class WorkerTask : public ManagedTask {
private:
int workerId;
int maxIterations;
public:
WorkerTask(int id, int iterations)
: ManagedTask("Worker", 512, 2),
workerId(id), maxIterations(iterations) {}
void run() override {
std::cout << "[Worker " << workerId << "] 시작" << std::endl;
for(int i = 0; i < maxIterations && !stopRequested; i++) {
std::cout << "[Worker " << workerId << "] 진행: "
<< i+1 << "/" << maxIterations << std::endl;
vTaskDelay(pdMS_TO_TICKS(500));
}
if(stopRequested) {
std::cout << "[Worker " << workerId << "] 중단 요청으로 종료" << std::endl;
} else {
std::cout << "[Worker " << workerId << "] 작업 완료" << std::endl;
}
}
};
class TaskManager {
private:
std::vector<std::unique_ptr<ManagedTask>> tasks;
public:
void addTask(std::unique_ptr<ManagedTask> task) {
if(task->start()) {
tasks.push_back(std::move(task));
}
}
void stopAllTasks() {
std::cout << "\n모든 Task 중단 요청..." << std::endl;
for(auto& task : tasks) {
task->requestStop();
}
for(auto& task : tasks) {
task->waitForStop();
}
std::cout << "모든 Task 종료 완료" << std::endl;
}
int getActiveTaskCount() const {
int count = 0;
for(const auto& task : tasks) {
if(!task->isStopped()) count++;
}
return count;
}
~TaskManager() {
stopAllTasks();
}
};
// 사용 예제
class ManagerTask : public ManagedTask {
private:
TaskManager& manager;
public:
ManagerTask(TaskManager& mgr)
: ManagedTask("Manager", 512, 3), manager(mgr) {}
void run() override {
int workerId = 0;
while(!stopRequested) {
std::cout << "\n[Manager] 새 Worker 생성..." << std::endl;
auto worker = std::make_unique<WorkerTask>(++workerId, 10);
manager.addTask(std::move(worker));
std::cout << "[Manager] 활성 Task: "
<< manager.getActiveTaskCount() << std::endl;
vTaskDelay(pdMS_TO_TICKS(3000));
}
}
};
int main(void) {
TaskManager taskManager;
auto manager = std::make_unique<ManagerTask>(taskManager);
manager->start();
// 15초 후 모든 Task 중단
vTaskDelay(pdMS_TO_TICKS(15000));
std::cout << "\n프로그램 종료 중..." << std::endl;
manager->requestStop();
manager->waitForStop();
taskManager.stopAllTasks();
vTaskStartScheduler();
while(1);
}
C 버전:
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdio.h>
#define MAX_WORKERS 3
typedef struct {
int jobId;
int duration; // 작업 시간 (ms)
} Job;
typedef struct {
TaskHandle_t handle;
bool active;
int jobId;
} Worker;
QueueHandle_t jobQueue;
Worker workers[MAX_WORKERS] = {0};
void vWorkerTask(void *pvParameters) {
Worker *worker = (Worker*)pvParameters;
printf("[Worker %p] 시작\n", worker);
printf("[Worker %p] Job %d 실행 중...\n", worker, worker->jobId);
// 작업 시뮬레이션
vTaskDelay(pdMS_TO_TICKS(worker->jobId * 1000));
printf("[Worker %p] Job %d 완료\n", worker, worker->jobId);
// Worker를 Pool에 반환
worker->active = false;
worker->handle = NULL;
vTaskDelete(NULL);
}
bool assign_job(Job *job) {
// 사용 가능한 Worker 찾기
for(int i = 0; i < MAX_WORKERS; i++) {
if(!workers[i].active) {
workers[i].jobId = job->jobId;
workers[i].active = true;
BaseType_t result = xTaskCreate(
vWorkerTask,
"Worker",
256,
&workers[i],
2,
&workers[i].handle
);
if(result == pdPASS) {
printf("Job %d → Worker %d 할당\n", job->jobId, i);
return true;
} else {
workers[i].active = false;
return false;
}
}
}
return false; // 모든 Worker 사용 중
}
int get_active_workers() {
int count = 0;
for(int i = 0; i < MAX_WORKERS; i++) {
if(workers[i].active) count++;
}
return count;
}
void vJobDispatcherTask(void *pvParameters) {
Job job;
while(1) {
if(xQueueReceive(jobQueue, &job, pdMS_TO_TICKS(100)) == pdPASS) {
printf("\n[Dispatcher] Job %d 수신 (지속시간: %ds)\n",
job.jobId, job.duration);
// Worker 할당 시도
while(!assign_job(&job)) {
printf("[Dispatcher] Worker 대기 중... (활성: %d/%d)\n",
get_active_workers(), MAX_WORKERS);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// 상태 출력
printf("[Dispatcher] 활성 Worker: %d/%d\n",
get_active_workers(), MAX_WORKERS);
}
}
void vJobCreatorTask(void *pvParameters) {
int jobId = 0;
while(1) {
Job newJob;
newJob.jobId = ++jobId;
newJob.duration = (jobId % 5) + 1; // 1~5초
if(xQueueSend(jobQueue, &newJob, pdMS_TO_TICKS(1000)) == pdPASS) {
printf("[Creator] Job %d 생성 (큐 전송)\n", jobId);
} else {
printf("[Creator] 큐 가득 참\n");
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
int main(void) {
// 작업 큐 생성
jobQueue = xQueueCreate(10, sizeof(Job));
if(jobQueue == NULL) {
printf("큐 생성 실패\n");
return -1;
}
// Task 생성
xTaskCreate(vJobCreatorTask, "Creator", 256, NULL, 3, NULL);
xTaskCreate(vJobDispatcherTask, "Dispatcher", 512, NULL, 3, NULL);
printf("=== 동적 작업 큐 시스템 시작 ===\n");
printf("Worker Pool 크기: %d\n\n", MAX_WORKERS);
vTaskStartScheduler();
while(1);
}
C 버전:
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include <string.h>
#define MAX_TASKS 10
typedef struct {
TaskHandle_t handle;
char name[16];
TickType_t createdTime;
TickType_t deletedTime;
bool active;
} TaskInfo;
TaskInfo taskRegistry[MAX_TASKS] = {0};
int taskCount = 0;
int register_task(TaskHandle_t handle, const char *name) {
for(int i = 0; i < MAX_TASKS; i++) {
if(!taskRegistry[i].active) {
taskRegistry[i].handle = handle;
strncpy(taskRegistry[i].name, name, 15);
taskRegistry[i].createdTime = xTaskGetTickCount();
taskRegistry[i].active = true;
taskCount++;
printf("Task 등록: %s (ID: %d, 시각: %lu ms)\n",
name, i, taskRegistry[i].createdTime);
return i;
}
}
return -1;
}
void unregister_task(int taskId) {
if(taskId >= 0 && taskId < MAX_TASKS && taskRegistry[taskId].active) {
taskRegistry[taskId].deletedTime = xTaskGetTickCount();
taskRegistry[taskId].active = false;
taskCount--;
TickType_t lifetime = taskRegistry[taskId].deletedTime -
taskRegistry[taskId].createdTime;
printf("Task 삭제: %s (수명: %lu ms)\n",
taskRegistry[taskId].name, lifetime);
}
}
void print_task_registry() {
printf("\n=== Task 레지스트리 ===\n");
printf("총 등록 Task: %d\n", taskCount);
for(int i = 0; i < MAX_TASKS; i++) {
if(taskRegistry[i].active) {
TickType_t currentLifetime = xTaskGetTickCount() -
taskRegistry[i].createdTime;
printf(" [%d] %s - 실행 시간: %lu ms\n",
i, taskRegistry[i].name, currentLifetime);
}
}
printf("==================\n\n");
}
// 모니터링되는 Worker Task
void vMonitoredWorker(void *pvParameters) {
int *taskIdPtr = (int*)pvParameters;
int taskId = *taskIdPtr;
vPortFree(taskIdPtr);
printf("[Worker %d] 작업 시작\n", taskId);
for(int i = 0; i < 5; i++) {
printf("[Worker %d] 진행: %d/5\n", taskId, i+1);
vTaskDelay(pdMS_TO_TICKS(1000));
}
printf("[Worker %d] 작업 완료\n", taskId);
// 등록 해제
unregister_task(taskId);
vTaskDelete(NULL);
}
void vMonitoringTask(void *pvParameters) {
while(1) {
print_task_registry();
vTaskDelay(pdMS_TO_TICKS(3000));
}
}
void vTaskSpawner(void *pvParameters) {
int workerCount = 0;
while(workerCount < 5) {
printf("\n[Spawner] 새 Worker 생성...\n");
TaskHandle_t handle;
char taskName[16];
snprintf(taskName, sizeof(taskName), "Worker%d", workerCount);
int *taskIdPtr = pvPortMalloc(sizeof(int));
BaseType_t result = xTaskCreate(
vMonitoredWorker,
taskName,
256,
NULL,
2,
&handle
);
if(result == pdPASS) {
*taskIdPtr = register_task(handle, taskName);
// Task에 ID 전달 (재생성)
vTaskDelete(handle);
xTaskCreate(
vMonitoredWorker,
taskName,
256,
taskIdPtr,
2,
&handle
);
workerCount++;
} else {
vPortFree(taskIdPtr);
printf("Task 생성 실패\n");
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
printf("\n[Spawner] 모든 Worker 생성 완료, 종료\n");
vTaskDelete(NULL);
}
int main(void) {
xTaskCreate(vTaskSpawner, "Spawner", 512, NULL, 3, NULL);
xTaskCreate(vMonitoringTask, "Monitor", 512, NULL, 1, NULL);
printf("=== Task 생명주기 모니터링 시스템 ===\n\n");
vTaskStartScheduler();
while(1);
}
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <iostream>
#include <memory>
#include <vector>
class ResourceLimiter {
private:
SemaphoreHandle_t semaphore;
int maxResources;
int currentUsage;
public:
ResourceLimiter(int max) : maxResources(max), currentUsage(0) {
semaphore = xSemaphoreCreateCounting(max, max);
}
bool acquire(TickType_t timeout = portMAX_DELAY) {
if(xSemaphoreTake(semaphore, timeout) == pdTRUE) {
currentUsage++;
std::cout << "리소스 획득 (" << currentUsage
<< "/" << maxResources << ")" << std::endl;
return true;
}
return false;
}
void release() {
xSemaphoreGive(semaphore);
currentUsage--;
std::cout << "⬅️ 리소스 반환 (" << currentUsage
<< "/" << maxResources << ")" << std::endl;
}
int getUsage() const { return currentUsage; }
int getMax() const { return maxResources; }
~ResourceLimiter() {
vSemaphoreDelete(semaphore);
}
};
class Task {
protected:
TaskHandle_t taskHandle;
const char* taskName;
uint16_t stackSize;
UBaseType_t priority;
virtual void run() = 0;
static void taskEntry(void* pvParameters) {
Task* task = static_cast<Task*>(pvParameters);
task->run();
vTaskDelete(nullptr);
}
public:
Task(const char* name, uint16_t stack, UBaseType_t prio)
: taskName(name), stackSize(stack), priority(prio), taskHandle(nullptr) {}
bool create() {
BaseType_t result = xTaskCreate(
taskEntry, taskName, stackSize, this, priority, &taskHandle
);
return (result == pdPASS);
}
virtual ~Task() {}
};
class LimitedWorker : public Task {
private:
ResourceLimiter& limiter;
int workerId;
int workDuration;
public:
LimitedWorker(ResourceLimiter& lim, int id, int duration)
: Task("LimitedWorker", 512, 2),
limiter(lim), workerId(id), workDuration(duration) {}
void run() override {
std::cout << "[Worker " << workerId << "] 리소스 요청..." << std::endl;
if(limiter.acquire(pdMS_TO_TICKS(5000))) {
std::cout << "[Worker " << workerId << "] 작업 시작 ("
<< workDuration << "ms)" << std::endl;
vTaskDelay(pdMS_TO_TICKS(workDuration));
std::cout << "[Worker " << workerId << "] 작업 완료" << std::endl;
limiter.release();
} else {
std::cout << "[Worker " << workerId
<< "] 리소스 획득 실패 (타임아웃)" << std::endl;
}
}
};
class WorkerSpawner : public Task {
private:
ResourceLimiter& limiter;
public:
WorkerSpawner(ResourceLimiter& lim)
: Task("Spawner", 512, 3), limiter(lim) {}
void run() override {
int workerId = 0;
while(workerId < 10) {
std::cout << "\n[Spawner] Worker " << workerId
<< " 생성..." << std::endl;
int duration = (workerId % 3 + 1) * 1000; // 1~3초
auto worker = new LimitedWorker(limiter, workerId, duration);
if(worker->create()) {
std::cout << "[Spawner] Worker " << workerId
<< " 생성 완료" << std::endl;
} else {
std::cout << "[Spawner] Worker 생성 실패" << std::endl;
delete worker;
}
workerId++;
vTaskDelay(pdMS_TO_TICKS(500));
}
std::cout << "\n[Spawner] 모든 Worker 생성 완료" << std::endl;
}
};
class MonitorTask : public Task {
private:
ResourceLimiter& limiter;
public:
MonitorTask(ResourceLimiter& lim)
: Task("Monitor", 512, 1), limiter(lim) {}
void run() override {
while(1) {
std::cout << "\n리소스 사용: " << limiter.getUsage()
<< "/" << limiter.getMax() << std::endl;
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
};
int main(void) {
ResourceLimiter limiter(3); // 최대 3개 동시 실행
WorkerSpawner spawner(limiter);
MonitorTask monitor(limiter);
std::cout << "=== 리소스 제한 관리 시스템 ===" << std::endl;
std::cout << "최대 동시 실행: " << limiter.getMax() << std::endl << std::endl;
spawner.create();
monitor.create();
vTaskStartScheduler();
while(1);
}
Task 삭제 전 반드시 확인해야 할 사항들:
동적 메모리 해제
/* pvPortMalloc으로 할당한 모든 메모리 해제 */
if(buffer != NULL) {
vPortFree(buffer);
buffer = NULL; /* 중복 해제 방지를 위해 NULL 설정 */
}
파일/하드웨어 리소스 정리
/* 열려있는 파일 디스크립터 닫기 */
if(file != NULL) {
fclose(file);
file = NULL;
}
세마포어/뮤텍스 반환
/* Task가 획득한 세마포어나 뮤텍스가 있다면 반환
* 반환하지 않으면 다른 Task가 영원히 대기할 수 있음 */
if(semaphore != NULL) {
xSemaphoreGive(semaphore);
}
큐에서 제거
/* 큐에 등록된 데이터가 있다면 정리
* 필요시 큐를 리셋하여 모든 데이터 제거 */
xQueueReset(queue);
다른 Task와의 동기화
/* 이 Task를 대기하고 있는 다른 Task에게 종료 신호 전송
* 다른 Task가 무한 대기에 빠지지 않도록 알림 */
notify_other_tasks_about_deletion();
typedef enum {
TASK_STATE_INIT,
TASK_STATE_RUNNING,
TASK_STATE_STOPPING,
TASK_STATE_STOPPED
} TaskState;
typedef struct {
TaskHandle_t handle;
TaskState state;
void *resources;
} SafeTask;
/* Task를 안전하게 삭제하는 함수
* 상태 전이를 확인하며 리소스를 정리 */
void safe_task_delete(SafeTask *task) {
if(task->state == TASK_STATE_RUNNING) {
/* 1. 종료 신호 전송 - Task에게 종료 요청 */
task->state = TASK_STATE_STOPPING;
/* 2. Task가 STOPPED 상태로 전이될 때까지 대기
* Task 함수 내에서 상태를 확인하고 정리 후 STOPPED로 설정함 */
while(task->state != TASK_STATE_STOPPED) {
vTaskDelay(pdMS_TO_TICKS(10));
}
/* 3. 외부 리소스 정리
* Task 자체 리소스는 Task 내에서 정리되고
* 외부에서 관리하는 리소스는 여기서 정리 */
if(task->resources != NULL) {
vPortFree(task->resources);
task->resources = NULL;
}
/* 4. Task 핸들 무효화 */
task->handle = NULL;
printf("Task 안전하게 삭제됨\n");
}
}
void vSafeTaskFunction(void *pvParameters) {
SafeTask *task = (SafeTask*)pvParameters;
task->state = TASK_STATE_RUNNING;
/* RUNNING 상태인 동안 작업 수행
* 외부에서 state를 STOPPING으로 변경하면 루프 종료 */
while(task->state == TASK_STATE_RUNNING) {
perform_work();
vTaskDelay(pdMS_TO_TICKS(100));
}
/* STOPPING 신호를 받으면 정리 시작 */
printf("Task 종료 준비 중...\n");
/* Task 내부 리소스 정리 */
cleanup_task_resources(task);
/* STOPPED 상태로 전이하여 외부에 종료 완료 알림 */
task->state = TASK_STATE_STOPPED;
vTaskDelete(NULL);
}
모든 할당에 대응하는 해제
/* 할당과 해제는 항상 쌍을 이루어야 함 */
void *ptr = pvPortMalloc(size);
/* ... 사용 ... */
vPortFree(ptr); /* 반드시 해제 */
예외 상황 처리
void *ptr1 = NULL, *ptr2 = NULL;
ptr1 = pvPortMalloc(100);
if(ptr1 == NULL) goto cleanup;
ptr2 = pvPortMalloc(200);
if(ptr2 == NULL) goto cleanup;
/* ... 작업 ... */
cleanup:
/ goto를 사용하여 모든 종료 경로에서 정리 보장
NULL 체크로 부분 할당된 경우도 안전하게 처리 */
if(ptr1) vPortFree(ptr1);
if(ptr2) vPortFree(ptr2);
3. **정기적인 메모리 모니터링**
```c
/* 메모리 상태를 주기적으로 확인하여 누수 조기 발견 */
void check_memory_health() {
size_t free = xPortGetFreeHeapSize();
size_t minEver = xPortGetMinimumEverFreeHeapSize();
if(free < 1000) {
printf("메모리 부족 경고!\n");
}
}
| 측면 | C 방식 | C++ 방식 |
|---|---|---|
| 리소스 관리 | 수동 정리 필요 | RAII, 자동 소멸자 |
| 메모리 안전성 | 개발자 책임 | 스마트 포인터 활용 |
| 코드 복잡도 | Cleanup 함수 필요 | 캡슐화로 단순화 |
| 오류 가능성 | 높음 (누락 위험) | 낮음 (자동화) |
| 디버깅 | 어려움 | 추적 용이 |
C 방식을 선택할 때:
C++ 방식을 선택할 때:
vTaskDelete() 함수
자기 삭제 패턴
메모리 누수 방지
동적 Task 관리
| 개념 | 설명 |
|---|---|
| vTaskDelete() | Task 종료 및 리소스 해제 함수 |
| 자기 삭제 | NULL 파라미터로 현재 Task 종료 |
| 메모리 누수 | 할당 후 미해제로 인한 메모리 고갈 |
| 리소스 정리 | Task 종료 전 모든 리소스 반환 |
| Idle Task | 삭제된 Task의 메모리를 실제 해제 |
시간별로 Task를 생성하고 자동 삭제하는 스케줄러 구현
요구사항:
제한된 리소스를 여러 Task가 공유하는 시스템
요구사항:
모든 Task의 생성/삭제를 추적하고 통계를 제공
요구사항:
문제 1: Task가 삭제되지 않음
무한 루프 안에 삭제 코드를 넣으면 해당 코드는 절대 실행되지 않습니다.
/* 올바른 방법: 루프를 종료할 수 있는 조건 사용 */
void vCorrectTask(void *pvParameters) {
for(int i = 0; i < 10; i++) {
work();
vTaskDelay(pdMS_TO_TICKS(100));
}
/* for 루프가 종료된 후 vTaskDelete 호출 */
vTaskDelete(NULL);
}
문제 2: 메모리 누수 탐지
/* 메모리 사용량 변화를 모니터링하여 누수 감지 */
void monitor_heap() {
static size_t lastFree = 0;
size_t currentFree = xPortGetFreeHeapSize();
if(lastFree > 0) {
long diff = (long)currentFree - (long)lastFree;
/* 100 bytes 이상 감소했다면 누수 의심 */
if(diff < -100) {
printf("메모리 누수 의심: %ld bytes\n", -diff);
}
}
/* 다음 비교를 위해 현재 값 저장 */
lastFree = currentFree;
}
문제 3: 삭제 후 핸들 사용
/* Task 삭제 후 핸들을 NULL로 설정하여 중복 삭제 방지 */
TaskHandle_t handle;
vTaskDelete(handle);
handle = NULL; /* 반드시 NULL로 설정 */
/* 이후 사용 시 NULL 체크로 안전성 확보 */
if(handle != NULL) {
vTaskDelete(handle);
}