Week 2 Day 5: Idle Task와 시스템 모니터링

학습 목표

  • Idle Task의 역할과 동작 원리를 이해한다
  • vApplicationIdleHook()을 활용하여 시스템 유휴 시간에 작업을 수행한다
  • CPU 사용률 측정 방법을 학습한다
  • 시스템 상태 모니터링 기법을 익힌다
  • 런타임 통계를 활용한 성능 분석 방법을 이해한다

1. Idle Task 이해하기

1.1 Idle Task란?

개요:
Idle Task는 FreeRTOS가 자동으로 생성하는 특수한 Task로, 다른 모든 Task가 Blocked 또는 Suspended 상태일 때 실행됩니다.

주요 특징:

  • 우선순위: 0 (가장 낮음)
  • 항상 Ready 상태 유지
  • 스케줄러 시작 시 자동 생성
  • 절대 Blocked 상태로 전환되지 않음

Idle Task의 주요 역할:

  1. 삭제된 Task의 메모리 정리

    • vTaskDelete()로 삭제된 Task의 TCB(Task Control Block)와 스택 메모리 해제
    • Idle Task가 실행되어야만 메모리가 실제로 반환됨
  2. 시스템이 완전히 멈추지 않도록 보장

    • 실행 가능한 Task가 없을 때도 시스템 동작 유지
    • CPU가 항상 실행할 Task를 갖도록 보장
  3. 저전력 모드 진입

    • CPU 유휴 시간에 저전력 모드로 전환 가능
    • 전력 소비 최소화

Idle Task 동작 흐름:

모든 Task가 Blocked/Suspended
          ↓
   Idle Task 실행
          ↓
삭제 대기 중인 Task 메모리 해제
          ↓
Idle Hook 함수 호출 (설정된 경우)
          ↓
저전력 모드 진입 (설정된 경우)
          ↓
다른 Task가 Ready 되면 즉시 양보

1.2 Idle Task 내부 구조

FreeRTOS 내부 Idle Task 함수 (개념):

/* FreeRTOS 내부 Idle Task 구현 (단순화된 버전) */
static void prvIdleTask(void *pvParameters) {
    /* Idle Task는 무한 루프로 실행됨 */
    for(;;) {
        /* 1. 삭제 대기 중인 Task의 메모리 정리 */
        prvCheckTasksWaitingTermination();
        
        /* 2. Idle Hook 함수 호출 (저전력 모드 전환/시스템 상태 모니터링/백그라운드 정리 작업 용도) */
        #if (configUSE_IDLE_HOOK == 1)
        {
            extern void vApplicationIdleHook(void);
            vApplicationIdleHook();
        }
        #endif
        
        /* 3. 다른 Task에게 양보할 기회 제공 */
        #if (configIDLE_SHOULD_YIELD == 1)
        {
            /* 같은 우선순위의 다른 Task가 있으면 양보 */
            taskYIELD();
        }
        #endif
        
        /* 4. 저전력 모드 진입 (Tickless Idle) */
        #if (configUSE_TICKLESS_IDLE != 0)
        {
            portSUPPRESS_TICKS_AND_SLEEP(xExpectedIdleTime);
        }
        #endif
    }
}

1.3 Idle Task 관련 설정

FreeRTOSConfig.h 설정:

/* Idle Hook 함수 사용 여부 */
#define configUSE_IDLE_HOOK                1

/* Idle Task가 다른 Task에게 양보할지 여부 
 * 1: 같은 우선순위(0)의 다른 Task가 있으면 양보
 * 0: 선점될 때까지 계속 실행 */
#define configIDLE_SHOULD_YIELD            1

/* Tickless Idle 모드 사용 여부 (저전력) */
#define configUSE_TICKLESS_IDLE            0

/* Idle Task 스택 크기 설정 */
#define configMINIMAL_STACK_SIZE           128

Idle Task 동작 확인 예제:

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

volatile uint32_t idleCounter = 0;

void vApplicationIdleHook(void) {
    /* Idle Task가 실행될 때마다 카운터 증가
     * 이 함수는 Idle Task의 매 루프마다 호출됨 */
    idleCounter++;
}

void vTask1(void *pvParameters) {
    while(1) {
        printf("[Task1] 실행\n");
        vTaskDelay(pdMS_TO_TICKS(1000));  /* 1초 대기 */
    }
}

void vTask2(void *pvParameters) {
    while(1) {
        printf("[Task2] 실행\n");
        vTaskDelay(pdMS_TO_TICKS(1500));  /* 1.5초 대기 */
    }
}

void vMonitorTask(void *pvParameters) {
    uint32_t lastCounter = 0;
    
    while(1) {
        uint32_t currentCounter = idleCounter;
        uint32_t idleExecutions = currentCounter - lastCounter;
        
        printf("\n[모니터] Idle Task 실행 횟수: %lu\n", idleExecutions);
        printf("        (높을수록 시스템이 유휴 상태)\n");
        
        lastCounter = currentCounter;
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

int main(void) {
    printf("=== Idle Task 동작 확인 ===\n\n");
    
    xTaskCreate(vTask1, "Task1", 256, NULL, 2, NULL);
    xTaskCreate(vTask2, "Task2", 256, NULL, 2, NULL);
    xTaskCreate(vMonitorTask, "Monitor", 512, NULL, 1, NULL);
    
    vTaskStartScheduler();
    while(1);
}

2. vApplicationIdleHook() 활용

2.1 Idle Hook 함수 개요

앞서 사용한 개발자 정의 Hook 함수와는 달리 VApplicationIdleHook()는 정의(Define)만 해두면 호출(Call)은 OS가 알아서 실행하는 콜백(Callback) 메커니즘을 이용합니다.

함수 원형:

void vApplicationIdleHook(void);

특징:

  • Idle Task의 매 루프마다 호출됨
  • 우선순위 0에서 실행
  • 절대 Blocked 상태로 전환하면 안 됨
  • vTaskDelay(), vTaskDelayUntil() 등 사용 금지
  • Queue나 Semaphore를 무한 대기하면 안 됨

주의사항:

void vApplicationIdleHook(void) {
    /* 주의사항 및 금지 항목 */
    /* vTaskDelay(pdMS_TO_TICKS(100)); */
    /* xQueueReceive(queue, &data, portMAX_DELAY); */
    /* while(1) {}; */
}

2.2 Idle Hook 활용 패턴

패턴 1: 백그라운드 작업

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

/* 체크섬 계산 상태 */
typedef struct {
    uint8_t *data;
    uint32_t length;
    uint32_t currentIndex;
    uint32_t checksum;
    bool complete;
} ChecksumTask;

ChecksumTask checksumState = {0};

/* 대용량 데이터의 체크섬을 백그라운드에서 계산 */
void vApplicationIdleHook(void) {
    if(!checksumState.complete && checksumState.data != NULL) {
        /* 한 번에 소량만 처리 (Idle Hook은 빨리 리턴해야 함) */
        for(int i = 0; i < 10 && checksumState.currentIndex < checksumState.length; i++) {
            checksumState.checksum += checksumState.data[checksumState.currentIndex];
            checksumState.currentIndex++;
        }
        
        /* 완료 확인 */
        if(checksumState.currentIndex >= checksumState.length) {
            checksumState.complete = true;
            printf("[Idle Hook] 체크섬 계산 완료: 0x%08lX\n", checksumState.checksum);
        }
    }
}

void start_checksum_calculation(uint8_t *data, uint32_t length) {
    checksumState.data = data;
    checksumState.length = length;
    checksumState.currentIndex = 0;
    checksumState.checksum = 0;
    checksumState.complete = false;
    
    printf("[메인] 백그라운드 체크섬 계산 시작 (%lu bytes)\n", length);
}

void vMainTask(void *pvParameters) {
    uint8_t testData[1000];
    
    /* 테스트 데이터 초기화 */
    for(int i = 0; i < 1000; i++) {
        testData[i] = i & 0xFF;
    }
    
    /* 백그라운드 체크섬 계산 시작 */
    start_checksum_calculation(testData, 1000);
    
    while(1) {
        printf("[메인 Task] 다른 작업 수행 중...\n");
        
        if(checksumState.complete) {
            printf("[메인 Task] 체크섬 결과 확인: 0x%08lX\n", checksumState.checksum);
            
            /* 새로운 계산 시작 */
            checksumState.complete = false;
            start_checksum_calculation(testData, 1000);
        }
        
        vTaskDelay(pdMS_TO_TICKS(2000)); //여기서 vApplicationIdleHook()가 2초(Delay time)동안 실행됨
    }
}

패턴 2: 워치독 갱신

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

volatile uint32_t watchdogCounter = 0;

/* Idle Hook에서 워치독 갱신 */
void vApplicationIdleHook(void) {
    /* 하드웨어 워치독 타이머 리셋 */
    watchdogCounter++;
    
    /* 시스템 정상성 체크 후 워치독 갱신(WatchDog Feeding), :
     * WATCHDOG_RESET_REGISTER = WATCHDOG_RESET_VALUE; */
}

void vMonitorTask(void *pvParameters) {
    uint32_t lastCounter = 0;
    
    while(1) {
        uint32_t currentCounter = watchdogCounter;
        uint32_t resetCount = currentCounter - lastCounter;
        
        printf("[모니터] 워치독 리셋 횟수: %lu\n", resetCount);
        
        if(resetCount < 100) {
            printf("경고: 시스템 과부하 가능성\n");
        }
        
        lastCounter = currentCounter;
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

패턴 3: 유휴 시간(Idle Time) 측정

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

volatile uint32_t idleTimeCounter = 0;
volatile uint32_t totalTimeCounter = 0;

void vApplicationIdleHook(void) {
    /* Idle Task 실행 시간 카운트 */
    idleTimeCounter++;
}

/* Tick Hook에서 전체 시간 카운트 */
void vApplicationTickHook(void) {
    totalTimeCounter++;
}

void vCpuUsageTask(void *pvParameters) {
    uint32_t lastIdle = 0;
    uint32_t lastTotal = 0;
    
    while(1) {
        uint32_t currentIdle = idleTimeCounter;
        uint32_t currentTotal = totalTimeCounter;
        
        uint32_t idleDelta = currentIdle - lastIdle;
        uint32_t totalDelta = currentTotal - lastTotal;
        
        if(totalDelta > 0) {
            uint32_t cpuUsage = 100 - ((idleDelta * 100) / totalDelta);
            printf("[CPU 사용률] %lu%%\n", cpuUsage);
        }
        
        lastIdle = currentIdle;
        lastTotal = currentTotal;
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

2.3 Idle Hook 설정과 구현

FreeRTOSConfig.h:

/* Idle Hook 함수 활성화 */
#define configUSE_IDLE_HOOK                1

/* Tick Hook도 함께 사용하는 경우 */
#define configUSE_TICK_HOOK                1

Tick Hook은 시간이 흐흘 때마다 무조건 실행되는 함수입니다.

전체 예제:

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

/* 통계 변수 */
volatile uint32_t idleExecutions = 0;
volatile uint32_t tickCount = 0;

void vApplicationIdleHook(void) {
    idleExecutions++;
    
    /* LED 토글 (실제 하드웨어 동작을 보고 시스템 정상성을 체크하는 경우) */
    /* toggle_status_led(); */
}

void vApplicationTickHook(void) {
    tickCount++;
}

/* 주 동작부, vWorkerTask가 잠들면 Idle Task가 돌면서 idleExecutions를 증가시키고 LED 작동*/
void vWorkerTask(void *pvParameters) {
    int taskId = (int)pvParameters;
    
    while(1) {
        printf("[Worker %d] 작업 수행\n", taskId);
        
        /* 작업 부하 시뮬레이션 */
        for(volatile int i = 0; i < 100000; i++);
        
        vTaskDelay(pdMS_TO_TICKS(500 + taskId * 200));
    }
}

/*Idle Task가 실행되는 동안의 수치(LED_toggle Count)를 모아 시스템 상태치 반환*/
void vStatisticsTask(void *pvParameters) {
    uint32_t lastIdle = 0;
    uint32_t lastTick = 0;
    
    while(1) {
        uint32_t currentIdle = idleExecutions;
        uint32_t currentTick = tickCount;
        
        uint32_t idleDelta = currentIdle - lastIdle;
        uint32_t tickDelta = currentTick - lastTick;
        
        printf("\n=== 시스템 통계 ===\n");
        printf("Tick 증가: %lu\n", tickDelta);
        printf("Idle 실행: %lu회\n", idleDelta);
        
        if(tickDelta > 0) {
            uint32_t idleRatio = (idleDelta * 100) / tickDelta;
            printf("유휴 비율: %lu%%\n", idleRatio);
            printf("활성 비율: %lu%%\n", 100 - idleRatio);
        }
        
        lastIdle = currentIdle;
        lastTick = currentTick;
        
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

int main(void) {
    printf("=== Idle Hook 활용 시스템 ===\n\n");
    
    /* 작업 부하가 다른 여러 Task 생성 */
    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", 512, NULL, 1, NULL);
    
    vTaskStartScheduler();
    while(1);
}

3. CPU 사용률 측정

3.1 기본 CPU 사용률 측정

개념:
CPU 사용률 = (1 - Idle 시간 / 전체 시간) × 100%

간단한 측정 방법:

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

typedef struct {
    uint32_t idleCount;
    uint32_t totalCount;
    uint32_t cpuUsage;
} CpuStats;

volatile CpuStats cpuStats = {0};

void vApplicationIdleHook(void) {
    cpuStats.idleCount++;
}

void vApplicationTickHook(void) {
    cpuStats.totalCount++;
}

void calculate_cpu_usage(void) {
    static uint32_t lastIdle = 0;
    static uint32_t lastTotal = 0;
    
    uint32_t currentIdle = cpuStats.idleCount;
    uint32_t currentTotal = cpuStats.totalCount;
    
    uint32_t idleDelta = currentIdle - lastIdle;
    uint32_t totalDelta = currentTotal - lastTotal;
    
    if(totalDelta > 0) {
        /* CPU 사용률 계산 */
        cpuStats.cpuUsage = 100 - ((idleDelta * 100) / totalDelta);
    }
    
    lastIdle = currentIdle;
    lastTotal = currentTotal;
}

void vCpuMonitorTask(void *pvParameters) {
    while(1) {
        calculate_cpu_usage();
        
        printf("[CPU 모니터] 사용률: %lu%%\n", cpuStats.cpuUsage);
        
        if(cpuStats.cpuUsage > 80) {
            printf("             경고: 높은 CPU 사용률!\n");
        }
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

3.2 Task별 CPU 사용 시간 측정

FreeRTOS 런타임 통계 활성화:

/* FreeRTOSConfig.h */
#define configGENERATE_RUN_TIME_STATS      1
#define configUSE_TRACE_FACILITY           1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1

/* 런타임 카운터 설정 */
extern volatile uint32_t ulHighFrequencyTimerTicks;

#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() \
    (ulHighFrequencyTimerTicks = 0)
    
#define portGET_RUN_TIME_COUNTER_VALUE() \
    ulHighFrequencyTimerTicks

런타임 통계 구현:

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include <string.h>

/* 고주파 타이머 */
volatile uint32_t ulHighFrequencyTimerTicks = 0;

/* Tick Hook에서 고주파 카운터 증가 */
void vApplicationTickHook(void) {
    ulHighFrequencyTimerTicks += 10;  /* Tick보다 10배 빠른 카운터 */
}

void print_task_statistics(void) {
    TaskStatus_t *pxTaskStatusArray;
    volatile UBaseType_t uxArraySize, x;
    uint32_t ulTotalRunTime, ulStatsAsPercentage;
    
    /* Task 개수 확인 */
    uxArraySize = uxTaskGetNumberOfTasks();
    
    /* Task 정보를 저장할 배열 할당 */
    pxTaskStatusArray = pvPortMalloc(uxArraySize * sizeof(TaskStatus_t));
    
    if(pxTaskStatusArray != NULL) {
        /* Task 상태 정보 가져오기 */
        uxArraySize = uxTaskGetSystemState(pxTaskStatusArray, 
                                           uxArraySize, 
                                           &ulTotalRunTime);
        
        printf("\n=== Task 런타임 통계 ===\n");
        printf("%-15s %10s %10s\n", "Task 이름", "실행 시간", "CPU 사용률");
        printf("----------------------------------------\n");
        
        /* 전체 실행 시간이 0이면 나누기 오류 방지 */
        if(ulTotalRunTime > 0) {
            for(x = 0; x < uxArraySize; x++) {
                /* CPU 사용률 계산 (백분율) */
                ulStatsAsPercentage = (pxTaskStatusArray[x].ulRunTimeCounter * 100) 
                                     / ulTotalRunTime;
                
                printf("%-15s %10lu %9lu%%\n",
                       pxTaskStatusArray[x].pcTaskName,
                       pxTaskStatusArray[x].ulRunTimeCounter,
                       ulStatsAsPercentage);
            }
        }
        
        printf("총 실행 시간: %lu\n", ulTotalRunTime);
        
        vPortFree(pxTaskStatusArray);
    }
}

void vHighLoadTask(void *pvParameters) {
    while(1) {
        /* CPU 집약적인 작업 시뮬레이션 */
        for(volatile int i = 0; i < 500000; i++);
        
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void vMediumLoadTask(void *pvParameters) {
    while(1) {
        for(volatile int i = 0; i < 200000; i++);
        
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

void vLowLoadTask(void *pvParameters) {
    while(1) {
        for(volatile int i = 0; i < 50000; i++);
        
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void vStatisticsTask(void *pvParameters) {
    while(1) {
        print_task_statistics();
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

int main(void) {
    printf("=== Task별 CPU 사용률 측정 ===\n");
    
    xTaskCreate(vHighLoadTask, "HighLoad", 256, NULL, 3, NULL);
    xTaskCreate(vMediumLoadTask, "MediumLoad", 256, NULL, 2, NULL);
    xTaskCreate(vLowLoadTask, "LowLoad", 256, NULL, 1, NULL);
    xTaskCreate(vStatisticsTask, "Stats", 512, NULL, 1, NULL);
    
    vTaskStartScheduler();
    while(1);
}

3.3 Task별 스택 사용량 측정

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

void print_stack_usage(void) {
    TaskStatus_t *pxTaskStatusArray;
    UBaseType_t uxArraySize, x;
    
    uxArraySize = uxTaskGetNumberOfTasks();
    pxTaskStatusArray = pvPortMalloc(uxArraySize * sizeof(TaskStatus_t));
    
    if(pxTaskStatusArray != NULL) {
        uxArraySize = uxTaskGetSystemState(pxTaskStatusArray, uxArraySize, NULL);
        
        printf("\n=== Task 스택 사용량 ===\n");
        printf("%-15s %15s %15s\n", "Task 이름", "여유 스택(words)", "상태");
        printf("--------------------------------------------------\n");
        
        for(x = 0; x < uxArraySize; x++) {
            UBaseType_t stackHighWaterMark = 
                uxTaskGetStackHighWaterMark(pxTaskStatusArray[x].xHandle);
            
            const char *status;
            if(stackHighWaterMark < 50) {
                status = "위험";
            } else if(stackHighWaterMark < 100) {
                status = "경고";
            } else {
                status = "정상";
            }
            
            printf("%-15s %15u %15s\n",
                   pxTaskStatusArray[x].pcTaskName,
                   stackHighWaterMark,
                   status);
        }
        
        vPortFree(pxTaskStatusArray);
    }
}

void vStackMonitorTask(void *pvParameters) {
    while(1) {
        print_stack_usage();
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

4. 시스템 상태 모니터링

4.1 종합 시스템 모니터

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include <string.h>

typedef struct {
    uint32_t totalHeap;
    uint32_t freeHeap;
    uint32_t minEverFreeHeap;
    uint32_t usedHeap;
    uint32_t taskCount;
    uint32_t cpuUsage;
} SystemStatus;

SystemStatus sysStatus = {0};

void update_system_status(void) {
    /* 힙 메모리 상태 */
    sysStatus.totalHeap = configTOTAL_HEAP_SIZE;
    sysStatus.freeHeap = xPortGetFreeHeapSize();
    sysStatus.minEverFreeHeap = xPortGetMinimumEverFreeHeapSize();
    sysStatus.usedHeap = sysStatus.totalHeap - sysStatus.freeHeap;
    
    /* Task 개수 */
    sysStatus.taskCount = uxTaskGetNumberOfTasks();
}

void print_system_status(void) {
    update_system_status();
    
    printf("\n");
    printf("╔══════════════════════════════════════════════╗\n");
    printf("║         시스템 상태 모니터링                ║\n");
    printf("╠══════════════════════════════════════════════╣\n");
    
    printf("║ 메모리 상태:                                ║\n");
    printf("║   전체 힙:       %8lu bytes           ║\n", sysStatus.totalHeap);
    printf("║   사용 중:       %8lu bytes           ║\n", sysStatus.usedHeap);
    printf("║   여유 힙:       %8lu bytes           ║\n", sysStatus.freeHeap);
    printf("║   최소 여유:     %8lu bytes           ║\n", sysStatus.minEverFreeHeap);
    
    uint32_t heapUsagePercent = (sysStatus.usedHeap * 100) / sysStatus.totalHeap;
    printf("║   사용률:        %8lu%%                ║\n", heapUsagePercent);
    
    printf("╠══════════════════════════════════════════════╣\n");
    printf("║ Task 정보:                                  ║\n");
    printf("║   총 Task 수:    %8lu                  ║\n", sysStatus.taskCount);
    printf("╚══════════════════════════════════════════════╝\n");
}

void print_detailed_task_info(void) {
    TaskStatus_t *pxTaskStatusArray;
    UBaseType_t uxArraySize, x;
    uint32_t ulTotalRunTime;
    
    uxArraySize = uxTaskGetNumberOfTasks();
    pxTaskStatusArray = pvPortMalloc(uxArraySize * sizeof(TaskStatus_t));
    
    if(pxTaskStatusArray != NULL) {
        uxArraySize = uxTaskGetSystemState(pxTaskStatusArray, 
                                           uxArraySize, 
                                           &ulTotalRunTime);
        
        printf("\n상세 Task 정보:\n");
        printf("%-15s %8s %10s %12s %10s\n", 
               "이름", "우선순위", "상태", "여유스택", "CPU%%");
        printf("----------------------------------------------------------------\n");
        
        for(x = 0; x < uxArraySize; x++) {
            const char *state;
            switch(pxTaskStatusArray[x].eCurrentState) {
                case eRunning:   state = "Running";   break;
                case eReady:     state = "Ready";     break;
                case eBlocked:   state = "Blocked";   break;
                case eSuspended: state = "Suspended"; break;
                case eDeleted:   state = "Deleted";   break;
                default:         state = "Unknown";   break;
            }
            
            UBaseType_t stackFree = 
                uxTaskGetStackHighWaterMark(pxTaskStatusArray[x].xHandle);
            
            uint32_t cpuPercent = 0;
            if(ulTotalRunTime > 0) {
                cpuPercent = (pxTaskStatusArray[x].ulRunTimeCounter * 100) 
                           / ulTotalRunTime;
            }
            
            printf("%-15s %8lu %10s %12u %9lu%%\n",
                   pxTaskStatusArray[x].pcTaskName,
                   pxTaskStatusArray[x].uxCurrentPriority,
                   state,
                   stackFree,
                   cpuPercent);
        }
        
        vPortFree(pxTaskStatusArray);
    }
}

void vSystemMonitorTask(void *pvParameters) {
    while(1) {
        print_system_status();
        print_detailed_task_info();
        
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

4.2 성능 벤치마크

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

typedef struct {
    const char *name;
    uint32_t iterations;
    TickType_t startTime;
    TickType_t endTime;
    uint32_t duration;
} BenchmarkResult;

void run_benchmark(BenchmarkResult *result, void (*testFunc)(void), uint32_t iterations) {
    result->iterations = iterations;
    result->startTime = xTaskGetTickCount();
    
    for(uint32_t i = 0; i < iterations; i++) {
        testFunc();
    }
    
    result->endTime = xTaskGetTickCount();
    result->duration = result->endTime - result->startTime;
}

/* 콜백 함수들 */
void test_integer_math(void) {
    volatile int result = 0;
    for(int i = 0; i < 1000; i++) {
        result += i * 2;
    }
}

void test_memory_access(void) {
    volatile uint8_t buffer[100];
    for(int i = 0; i < 100; i++) {
        buffer[i] = i;
    }
}

void test_context_switch(void) {
    taskYIELD();
}

/* 벤치마크 테스트 실행 결과 확인 */
void print_benchmark_results(BenchmarkResult *results, int count) {
    printf("\n=== 성능 벤치마크 결과 ===\n");
    printf("%-20s %12s %15s\n", "테스트", "반복 횟수", "소요 시간(ms)");
    printf("--------------------------------------------------\n");
    
    for(int i = 0; i < count; i++) {
        printf("%-20s %12lu %15lu\n",
               results[i].name,
               results[i].iterations,
               results[i].duration);
    }
}

void vBenchmarkTask(void *pvParameters) {
    BenchmarkResult results[3];
    
    printf("\n벤치마크 시작...\n");
    
    results[0].name = "정수 연산";
    run_benchmark(&results[0], test_integer_math, 100);
    
    results[1].name = "메모리 접근";
    run_benchmark(&results[1], test_memory_access, 1000);
    
    results[2].name = "컨텍스트 스위칭";
    run_benchmark(&results[2], test_context_switch, 100);
    
    print_benchmark_results(results, 3);
    
    printf("\n벤치마크 완료\n");
    vTaskDelete(NULL);
}

4.3 에러 감지 및 리포팅

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

typedef enum {
    ERROR_NONE = 0,
    ERROR_STACK_OVERFLOW,
    ERROR_HEAP_EXHAUSTED,
    ERROR_TASK_STARVATION,
    ERROR_CPU_OVERLOAD
} ErrorCode;

typedef struct {
    ErrorCode code;
    const char *taskName;
    TickType_t timestamp;
    const char *description;
} SystemError;

#define MAX_ERROR_LOG 10
SystemError errorLog[MAX_ERROR_LOG];
int errorCount = 0;

void log_error(ErrorCode code, const char *taskName, const char *description) {
    if(errorCount < MAX_ERROR_LOG) {
        errorLog[errorCount].code = code;
        errorLog[errorCount].taskName = taskName;
        errorLog[errorCount].timestamp = xTaskGetTickCount();
        errorLog[errorCount].description = description;
        errorCount++;
        
        printf("\n[오류 발생!] %s - %s\n", taskName, description);
    }
}

void check_system_health(void) {
    /* 힙 메모리 체크 */
    size_t freeHeap = xPortGetFreeHeapSize();
    if(freeHeap < 1024) {
        log_error(ERROR_HEAP_EXHAUSTED, "System", "힙 메모리 부족");
    }
    
    /* CPU 사용률 체크 */
    static uint32_t lastIdle = 0;
    static uint32_t lastTotal = 0;
    extern volatile uint32_t idleExecutions;
    extern volatile uint32_t tickCount;
    
    uint32_t idleDelta = idleExecutions - lastIdle;
    uint32_t totalDelta = tickCount - lastTotal;
    
    if(totalDelta > 0) {
        uint32_t cpuUsage = 100 - ((idleDelta * 100) / totalDelta);
        if(cpuUsage > 90) {
            log_error(ERROR_CPU_OVERLOAD, "System", "CPU 과부하");
        }
    }
    
    lastIdle = idleExecutions;
    lastTotal = tickCount;
    
    /* Task 스택 체크 */
    TaskStatus_t *pxTaskStatusArray;
    UBaseType_t uxArraySize = uxTaskGetNumberOfTasks();
    
    pxTaskStatusArray = pvPortMalloc(uxArraySize * sizeof(TaskStatus_t));
    if(pxTaskStatusArray != NULL) {
        uxArraySize = uxTaskGetSystemState(pxTaskStatusArray, uxArraySize, NULL);
        
        for(UBaseType_t x = 0; x < uxArraySize; x++) {
            UBaseType_t stackFree = 
                uxTaskGetStackHighWaterMark(pxTaskStatusArray[x].xHandle);
            
            if(stackFree < 50) {
                log_error(ERROR_STACK_OVERFLOW, 
                         pxTaskStatusArray[x].pcTaskName,
                         "스택 부족");
            }
        }
        
        vPortFree(pxTaskStatusArray);
    }
}

void print_error_log(void) {
    if(errorCount == 0) {
        printf("\n기록된 오류 없음\n");
        return;
    }
    
    printf("\n=== 오류 로그 ===\n");
    printf("%-10s %-15s %-30s %s\n", "시간(ms)", "Task", "설명", "코드");
    printf("----------------------------------------------------------------\n");
    
    for(int i = 0; i < errorCount; i++) {
        printf("%-10lu %-15s %-30s %d\n",
               errorLog[i].timestamp,
               errorLog[i].taskName,
               errorLog[i].description,
               errorLog[i].code);
    }
}

void vHealthCheckTask(void *pvParameters) {
    while(1) {
        check_system_health();
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

void vErrorReportTask(void *pvParameters) {
    while(1) {
        print_error_log();
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

5. 실습: 시스템 모니터링 대시보드

실습 1: 실시간 모니터링 시스템

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
#include <string.h>

volatile uint32_t ulHighFrequencyTimerTicks = 0;
volatile uint32_t idleExecutions = 0;
volatile uint32_t tickCount = 0;

void vApplicationIdleHook(void) {
    idleExecutions++;
}

void vApplicationTickHook(void) {
    tickCount++;
    ulHighFrequencyTimerTicks += 10;
}

typedef struct {
    char name[16];
    uint32_t executionCount;
    uint32_t maxExecutionTime;
    uint32_t minExecutionTime;
    uint32_t avgExecutionTime;
    TickType_t lastExecutionTime;
} TaskMetrics;

TaskMetrics taskMetrics[5];
int metricsCount = 0;

void update_task_metrics(const char *name, uint32_t executionTime) {
    for(int i = 0; i < metricsCount; i++) {
        if(strcmp(taskMetrics[i].name, name) == 0) {
            taskMetrics[i].executionCount++;
            taskMetrics[i].lastExecutionTime = executionTime;
            
            if(executionTime > taskMetrics[i].maxExecutionTime) {
                taskMetrics[i].maxExecutionTime = executionTime;
            }
            
            if(executionTime < taskMetrics[i].minExecutionTime || 
               taskMetrics[i].minExecutionTime == 0) {
                taskMetrics[i].minExecutionTime = executionTime;
            }
            
            taskMetrics[i].avgExecutionTime = 
                (taskMetrics[i].avgExecutionTime * (taskMetrics[i].executionCount - 1) + 
                 executionTime) / taskMetrics[i].executionCount;
            
            return;
        }
    }
    
    if(metricsCount < 5) {
        strcpy(taskMetrics[metricsCount].name, name);
        taskMetrics[metricsCount].executionCount = 1;
        taskMetrics[metricsCount].maxExecutionTime = executionTime;
        taskMetrics[metricsCount].minExecutionTime = executionTime;
        taskMetrics[metricsCount].avgExecutionTime = executionTime;
        taskMetrics[metricsCount].lastExecutionTime = executionTime;
        metricsCount++;
    }
}

void print_task_metrics(void) {
    printf("\n=== Task 실행 메트릭 ===\n");
    printf("%-12s %8s %8s %8s %8s\n", 
           "Task", "실행횟수", "최소(ms)", "평균(ms)", "최대(ms)");
    printf("--------------------------------------------------------\n");
    
    for(int i = 0; i < metricsCount; i++) {
        printf("%-12s %8lu %8lu %8lu %8lu\n",
               taskMetrics[i].name,
               taskMetrics[i].executionCount,
               taskMetrics[i].minExecutionTime,
               taskMetrics[i].avgExecutionTime,
               taskMetrics[i].maxExecutionTime);
    }
}

void vMonitoredTask(void *pvParameters) {
    const char *name = (const char*)pvParameters;
    int workload = (name[4] - '0') * 50000;
    
    while(1) {
        TickType_t startTime = xTaskGetTickCount();
        
        for(volatile int i = 0; i < workload; i++);
        
        TickType_t endTime = xTaskGetTickCount();
        uint32_t executionTime = endTime - startTime;
        
        update_task_metrics(name, executionTime);
        
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void vDashboardTask(void *pvParameters) {
    while(1) {
        printf("\033[2J\033[H");
        
        printf("╔═══════════════════════════════════════════════════╗\n");
        printf("║       FreeRTOS 시스템 모니터링 대시보드          ║\n");
        printf("╠═══════════════════════════════════════════════════╣\n");
        
        size_t freeHeap = xPortGetFreeHeapSize();
        size_t minHeap = xPortGetMinimumEverFreeHeapSize();
        uint32_t taskCount = uxTaskGetNumberOfTasks();
        
        printf("║ 메모리: %lu / %lu bytes (최소: %lu)      ║\n", 
               configTOTAL_HEAP_SIZE - freeHeap, 
               (uint32_t)configTOTAL_HEAP_SIZE,
               minHeap);
        printf("║ Task 수: %lu                                     ║\n", taskCount);
        
        static uint32_t lastIdle = 0;
        static uint32_t lastTotal = 0;
        uint32_t idleDelta = idleExecutions - lastIdle;
        uint32_t totalDelta = tickCount - lastTotal;
        
        if(totalDelta > 0) {
            uint32_t cpuUsage = 100 - ((idleDelta * 100) / totalDelta);
            printf("║ CPU 사용률: %lu%%                               ║\n", cpuUsage);
            
            printf("║ CPU: [");
            for(int i = 0; i < 30; i++) {
                if(i < (cpuUsage * 30 / 100)) {
                    printf("█");
                } else {
                    printf(" ");
                }
            }
            printf("]     ║\n");
        }
        
        lastIdle = idleExecutions;
        lastTotal = tickCount;
        
        printf("╚═══════════════════════════════════════════════════╝\n");
        
        print_task_metrics();
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

int main(void) {
    xTaskCreate(vMonitoredTask, "Task1", 256, (void*)"Task1", 2, NULL);
    xTaskCreate(vMonitoredTask, "Task2", 256, (void*)"Task2", 2, NULL);
    xTaskCreate(vMonitoredTask, "Task3", 256, (void*)"Task3", 2, NULL);
    xTaskCreate(vDashboardTask, "Dashboard", 512, NULL, 1, NULL);
    
    vTaskStartScheduler();
    while(1);
}

실습 2: 성능 프로파일러

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include <string.h>

#define MAX_PROFILING_POINTS 20

typedef struct {
    const char *name;
    TickType_t startTime;
    TickType_t totalTime;
    uint32_t callCount;
} ProfilingPoint;

ProfilingPoint profilingPoints[MAX_PROFILING_POINTS];
int profilingCount = 0;

void profiling_start(const char *name) {
    for(int i = 0; i < profilingCount; i++) {
        if(strcmp(profilingPoints[i].name, name) == 0) {
            profilingPoints[i].startTime = xTaskGetTickCount();
            return;
        }
    }
    
    if(profilingCount < MAX_PROFILING_POINTS) {
        profilingPoints[profilingCount].name = name;
        profilingPoints[profilingCount].startTime = xTaskGetTickCount();
        profilingPoints[profilingCount].totalTime = 0;
        profilingPoints[profilingCount].callCount = 0;
        profilingCount++;
    }
}

void profiling_end(const char *name) {
    TickType_t endTime = xTaskGetTickCount();
    
    for(int i = 0; i < profilingCount; i++) {
        if(strcmp(profilingPoints[i].name, name) == 0) {
            TickType_t elapsed = endTime - profilingPoints[i].startTime;
            profilingPoints[i].totalTime += elapsed;
            profilingPoints[i].callCount++;
            return;
        }
    }
}

void print_profiling_results(void) {
    printf("\n=== 성능 프로파일링 결과 ===\n");
    printf("%-20s %12s %15s %15s\n", 
           "함수/구간", "호출 횟수", "총 시간(ms)", "평균 시간(ms)");
    printf("----------------------------------------------------------------\n");
    
    for(int i = 0; i < profilingCount; i++) {
        uint32_t avgTime = 0;
        if(profilingPoints[i].callCount > 0) {
            avgTime = profilingPoints[i].totalTime / profilingPoints[i].callCount;
        }
        
        printf("%-20s %12lu %15lu %15lu\n",
               profilingPoints[i].name,
               profilingPoints[i].callCount,
               profilingPoints[i].totalTime,
               avgTime);
    }
}

void heavy_computation(void) {
    profiling_start("heavy_computation");
    
    volatile uint32_t result = 0;
    for(int i = 0; i < 1000000; i++) {
        result += i;
    }
    
    profiling_end("heavy_computation");
}

void data_processing(void) {
    profiling_start("data_processing");
    
    uint8_t buffer[256];
    for(int i = 0; i < 256; i++) {
        buffer[i] = i;
    }
    
    profiling_end("data_processing");
}

void vWorkerTask(void *pvParameters) {
    while(1) {
        profiling_start("worker_iteration");
        
        heavy_computation();
        data_processing();
        
        profiling_end("worker_iteration");
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void vProfilerTask(void *pvParameters) {
    vTaskDelay(pdMS_TO_TICKS(5000));
    
    while(1) {
        print_profiling_results();
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

int main(void) {
    printf("=== 성능 프로파일러 ===\n\n");
    
    xTaskCreate(vWorkerTask, "Worker", 512, NULL, 2, NULL);
    xTaskCreate(vProfilerTask, "Profiler", 512, NULL, 1, NULL);
    
    vTaskStartScheduler();
    while(1);
}

실습 3: 이벤트 로거

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdio.h>
#include <string.h>

typedef enum {
    EVENT_TASK_CREATED,
    EVENT_TASK_DELETED,
    EVENT_TASK_SWITCHED_IN,
    EVENT_TASK_SWITCHED_OUT,
    EVENT_ERROR,
    EVENT_WARNING,
    EVENT_INFO
} EventType;

typedef struct {
    EventType type;
    TickType_t timestamp;
    char taskName[16];
    char message[64];
} SystemEvent;

QueueHandle_t eventQueue;

void log_event(EventType type, const char *taskName, const char *message) {
    SystemEvent event;
    event.type = type;
    event.timestamp = xTaskGetTickCount();
    strncpy(event.taskName, taskName ? taskName : "System", 15);
    strncpy(event.message, message, 63);
    
    xQueueSend(eventQueue, &event, 0);
}

const char* get_event_type_string(EventType type) {
    switch(type) {
        case EVENT_TASK_CREATED:      return "TASK_CREATE";
        case EVENT_TASK_DELETED:      return "TASK_DELETE";
        case EVENT_TASK_SWITCHED_IN:  return "SWITCH_IN";
        case EVENT_TASK_SWITCHED_OUT: return "SWITCH_OUT";
        case EVENT_ERROR:             return "ERROR";
        case EVENT_WARNING:           return "WARNING";
        case EVENT_INFO:              return "INFO";
        default:                      return "UNKNOWN";
    }
}

void vEventLoggerTask(void *pvParameters) {
    SystemEvent event;
    
    while(1) {
        if(xQueueReceive(eventQueue, &event, portMAX_DELAY) == pdTRUE) {
            printf("[%8lu] %-12s %-15s %s\n",
                   event.timestamp,
                   get_event_type_string(event.type),
                   event.taskName,
                   event.message);
        }
    }
}

void vTestTask(void *pvParameters) {
    int taskId = (int)pvParameters;
    char msg[64];
    
    snprintf(msg, sizeof(msg), "Task%d started", taskId);
    log_event(EVENT_INFO, pcTaskGetName(NULL), msg);
    
    for(int i = 0; i < 5; i++) {
        snprintf(msg, sizeof(msg), "Working iteration %d", i);
        log_event(EVENT_INFO, pcTaskGetName(NULL), msg);
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
    
    snprintf(msg, sizeof(msg), "Task%d completed", taskId);
    log_event(EVENT_INFO, pcTaskGetName(NULL), msg);
    
    vTaskDelete(NULL);
}

void vSpawnerTask(void *pvParameters) {
    for(int i = 1; i <= 3; i++) {
        char taskName[16];
        snprintf(taskName, sizeof(taskName), "Test%d", i);
        
        TaskHandle_t handle;
        BaseType_t result = xTaskCreate(vTestTask, taskName, 256, (void*)i, 2, &handle);
        
        if(result == pdPASS) {
            log_event(EVENT_TASK_CREATED, taskName, "Task created successfully");
        } else {
            log_event(EVENT_ERROR, taskName, "Failed to create task");
        }
        
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
    
    vTaskDelete(NULL);
}

int main(void) {
    printf("=== 시스템 이벤트 로거 ===\n");
    printf("[시간(ms)] [이벤트 타입] [Task 이름]     [메시지]\n");
    printf("----------------------------------------------------------------\n");
    
    eventQueue = xQueueCreate(50, sizeof(SystemEvent));
    
    xTaskCreate(vEventLoggerTask, "Logger", 512, NULL, 1, NULL);
    xTaskCreate(vSpawnerTask, "Spawner", 512, NULL, 3, NULL);
    
    vTaskStartScheduler();
    while(1);
}

6. Best Practices

6.1 Idle Hook 사용 가이드라인

해야 할 것:

  • 짧고 빠른 작업만 수행
  • 백그라운드 체크섬 계산
  • 워치독 타이머 리셋
  • 통계 수집
  • LED 상태 업데이트

하지 말아야 할 것:

  • vTaskDelay() 호출
  • 무한 대기하는 Queue/Semaphore 작업
  • 무한 루프
  • 시간이 오래 걸리는 작업
  • 다른 Task를 Blocked 상태로 만드는 작업

6.2 모니터링 오버헤드 최소화

/* 통계 수집은 저우선순위 Task에서 수행 */
void vLowPriorityMonitorTask(void *pvParameters) {
    while(1) {
        collect_statistics();
        vTaskDelay(pdMS_TO_TICKS(5000));  /* 자주 수행하지 않음 */
    }
}

/* 조건부 로깅으로 오버헤드 감소 */
#define LOGGING_ENABLED 1

void conditional_log(const char *message) {
    #if LOGGING_ENABLED
    log_event(EVENT_INFO, pcTaskGetName(NULL), message);
    #endif
}

6.3 메모리 효율적인 통계 수집

/* 동적 할당 대신 정적 버퍼 사용 */
#define MAX_TASKS 10
static TaskStatus_t taskStatusArray[MAX_TASKS];

void collect_task_stats(void) {
    UBaseType_t taskCount = uxTaskGetSystemState(taskStatusArray, 
                                                  MAX_TASKS, 
                                                  NULL);
    
    /* 할당 없이 통계 수집 */
}

학습 정리

오늘 배운 핵심 내용

  1. Idle Task

    • 우선순위 0의 특수 Task
    • 삭제된 Task 메모리 정리
    • 시스템 유휴 시간 활용
  2. vApplicationIdleHook()

    • Idle Task에서 호출되는 사용자 함수
    • 짧고 빠른 작업만 수행
    • Blocked 상태로 전환 금지
  3. CPU 사용률 측정

    • Idle 시간 비율로 계산
    • Task별 실행 시간 추적
    • 런타임 통계 활용
  4. 시스템 모니터링

    • 힙 메모리 상태 추적
    • Task 스택 사용량 확인
    • 성능 프로파일링

핵심 개념

개념설명
Idle Task다른 Task가 없을 때 실행되는 우선순위 0 Task
Idle HookIdle Task에서 호출되는 사용자 정의 함수
런타임 통계Task별 CPU 사용 시간 측정 기능
High Water MarkTask 스택의 최대 여유 공간
CPU 사용률(1 - Idle 시간 / 전체 시간) × 100%

실습 과제

과제 1: 실시간 성능 모니터

Task별 CPU 사용률, 메모리 사용량, 실행 횟수를 실시간으로 표시하는 모니터링 시스템 구현

요구사항:

  • 1초마다 화면 갱신
  • CPU 사용률 바 그래프
  • 메모리 사용량 표시
  • 상위 5개 Task 강조 표시

과제 2: 자동 성능 최적화 시스템

시스템 부하를 감지하고 자동으로 Task 우선순위를 조정하는 시스템

요구사항:

  • CPU 사용률 90% 초과 시 경고
  • 스택 부족 Task 감지
  • 우선순위 동적 조정
  • 조정 이력 로그

과제 3: 시스템 건강 진단 도구

주기적으로 시스템 상태를 점검하고 문제를 보고하는 진단 도구

요구사항:

  • 메모리 누수 감지
  • Task 응답 시간 측정
  • 데드락 가능성 체크
  • 건강도 점수 산출

디버깅 팁

문제 1: Idle Hook에서 시스템 멈춤

/* 잘못된 코드 */
void vApplicationIdleHook(void) {
    vTaskDelay(pdMS_TO_TICKS(100));  /* 절대 금지! */
}

/* 올바른 코드 */
void vApplicationIdleHook(void) {
    /* 빠른 작업만 수행 */
    update_watchdog();
    idle_counter++;
}

문제 2: 통계 수집 오버헤드

/* 통계 수집 빈도 조절 */
void vMonitorTask(void *pvParameters) {
    while(1) {
        collect_statistics();
        vTaskDelay(pdMS_TO_TICKS(5000));  /* 5초마다만 수집 */
    }
}

문제 3: 메모리 부족

/* 동적 할당 대신 정적 버퍼 사용 */
static TaskStatus_t taskBuffer[10];

void get_task_info(void) {
    uxTaskGetSystemState(taskBuffer, 10, NULL);
    /* 할당/해제 없이 사용 */
}
profile
당신의 코딩 메이트

0개의 댓글