RTOS #9

홍태준·2026년 1월 23일

RTOS

목록 보기
9/20
post-thumbnail

Week 2 Day 4: Task 삭제와 리소스 관리

학습 목표

  • vTaskDelete()의 동작 원리와 사용법을 이해한다
  • Task가 자기 자신을 삭제하는 방법을 학습한다
  • 메모리 누수를 방지하는 방법을 익힌다
  • 동적 Task 생성/삭제 패턴을 실습한다
  • 리소스 정리의 중요성과 Best Practice를 이해한다

1. vTaskDelete() 이해하기

1.1 vTaskDelete() 함수 개요

함수 원형:

void vTaskDelete(TaskHandle_t xTaskToDelete);

파라미터:

  • xTaskToDelete: 삭제할 Task의 핸들
  • NULL: 현재 실행 중인 Task 자신을 삭제

동작:

  • Task를 즉시 종료하고 리소스를 해제
  • Task는 더 이상 스케줄링되지 않음
  • Task의 TCB(Task Control Block)와 스택 메모리가 회수됨

1.2 vTaskDelete()의 내부 동작

삭제 과정:

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);
}

1.3 주의사항

올바른 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를 삭제하려면 루프를 빠져나올 수 있는 조건이 필요합니다.


2. Task 자기 자신 삭제

2.1 자기 삭제 패턴

패턴 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));
    }
}

2.2 자기 삭제 vs 다른 Task에 의한 삭제

특성자기 삭제 (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);
}

3. 메모리 누수 방지

3.1 메모리 누수의 원인과 해결

메모리 누수는 주로 다음과 같은 상황에서 발생합니다:

원인 1: 동적 할당 후 미해제

  • 반복문 내에서 메모리를 할당하고 해제하지 않는 경우
  • 해결: 사용이 끝난 메모리는 반드시 해제

원인 2: 외부 삭제 시 정리 코드 미실행

  • 다른 Task가 강제로 삭제하면 정리 코드가 실행되지 않음
  • 해결: 자기 삭제 방식 사용하여 정리 후 종료

원인 3: 예외 상황에서 조기 종료

  • 오류 발생 시 리소스 정리 없이 Task 종료
  • 해결: 모든 종료 경로에서 정리 코드 실행

올바른 메모리 관리 예제:

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);
}

3.2 메모리 누수 방지 패턴

패턴 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);
}

3.3 메모리 누수 디버깅

힙 사용량 모니터링:

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));
    }
}

4. 동적 Task 생성/삭제

4.1 동적 생성 패턴

패턴 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));
    }
}

4.2 안전한 삭제 패턴

패턴 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));
        }
    }
}

4.3 Task 수명 주기 관리 (C++)

생성자와 소멸자를 이용한 수명 주기 관리 기법은 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);
}

5. 실습: 동적 Task 시스템 구현

실습 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);
}

실습 2: Task 생명주기 모니터링 시스템

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);
}

실습 3: 리소스 제한 관리자 (C++)

#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);
}

6. Best Practices

6.1 Task 삭제 체크리스트

Task 삭제 전 반드시 확인해야 할 사항들:

  1. 동적 메모리 해제

    /* pvPortMalloc으로 할당한 모든 메모리 해제 */
    if(buffer != NULL) {
        vPortFree(buffer);
        buffer = NULL;  /* 중복 해제 방지를 위해 NULL 설정 */
    }
  2. 파일/하드웨어 리소스 정리

    /* 열려있는 파일 디스크립터 닫기 */
    if(file != NULL) {
        fclose(file);
        file = NULL;
    }
  3. 세마포어/뮤텍스 반환

    /* Task가 획득한 세마포어나 뮤텍스가 있다면 반환
     * 반환하지 않으면 다른 Task가 영원히 대기할 수 있음 */
    if(semaphore != NULL) {
        xSemaphoreGive(semaphore);
    }
  4. 큐에서 제거

    /* 큐에 등록된 데이터가 있다면 정리
     * 필요시 큐를 리셋하여 모든 데이터 제거 */
    xQueueReset(queue);
  5. 다른 Task와의 동기화

    /* 이 Task를 대기하고 있는 다른 Task에게 종료 신호 전송
     * 다른 Task가 무한 대기에 빠지지 않도록 알림 */
    notify_other_tasks_about_deletion();

6.2 안전한 삭제 패턴

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);
}

6.3 메모리 누수 방지 가이드라인

  1. 모든 할당에 대응하는 해제

    /* 할당과 해제는 항상 쌍을 이루어야 함 */
    void *ptr = pvPortMalloc(size);
    /* ... 사용 ... */
    vPortFree(ptr);  /* 반드시 해제 */
  2. 예외 상황 처리

    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 vs C++ 비교 정리

Task 삭제 구현 비교

측면C 방식C++ 방식
리소스 관리수동 정리 필요RAII, 자동 소멸자
메모리 안전성개발자 책임스마트 포인터 활용
코드 복잡도Cleanup 함수 필요캡슐화로 단순화
오류 가능성높음 (누락 위험)낮음 (자동화)
디버깅어려움추적 용이

실전 선택 가이드

C 방식을 선택할 때:

  • 리소스 제약이 심한 환경
  • 단순한 Task 구조
  • 기존 C 코드베이스

C++ 방식을 선택할 때:

  • 복잡한 리소스 관리
  • 여러 Task 간 상호작용
  • 안전성이 중요한 시스템

학습 정리

오늘 배운 핵심 내용

  1. vTaskDelete() 함수

    • Task를 즉시 종료하고 리소스 해제
    • NULL 전달 시 자기 자신 삭제
    • Idle Task에서 실제 메모리 해제 수행
  2. 자기 삭제 패턴

    • 작업 완료 후 자동 종료
    • 조건부 삭제
    • 시간 제한 Task
  3. 메모리 누수 방지

    • 모든 할당에 대응하는 해제
    • Cleanup 함수 패턴
    • RAII (C++)
  4. 동적 Task 관리

    • On-Demand Task 생성
    • Task Pool 패턴
    • 안전한 삭제 메커니즘

핵심 개념

개념설명
vTaskDelete()Task 종료 및 리소스 해제 함수
자기 삭제NULL 파라미터로 현재 Task 종료
메모리 누수할당 후 미해제로 인한 메모리 고갈
리소스 정리Task 종료 전 모든 리소스 반환
Idle Task삭제된 Task의 메모리를 실제 해제

실습 과제

과제 1: 작업 스케줄러

시간별로 Task를 생성하고 자동 삭제하는 스케줄러 구현

요구사항:

  • 특정 시간에 Task 생성
  • 작업 완료 후 자동 삭제
  • 메모리 누수 없이 구현
  • 실행 이력 기록

과제 2: 리소스 풀 관리자

제한된 리소스를 여러 Task가 공유하는 시스템

요구사항:

  • 최대 5개 Task 동시 실행
  • 리소스 대기 큐 구현
  • Task 완료 시 자동 리소스 반환
  • 리소스 사용 통계

과제 3: Task 생명주기 추적기

모든 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);
}

profile
당신의 코딩 메이트

0개의 댓글