지노랩 /JinoLab

FreeRTOS 태스크 우선순위 본문

임베디드 시스템/RTOS

FreeRTOS 태스크 우선순위

지노랩/JinoLab 2025. 6. 13. 09:33

두 개의 “Hello-World” 태스크로 배우는 우선순위·스택·생성 API


1. 우선순위가 필요한 이유

상황 설명

단일-코어 MCU 한 순간에 CPU 명령을 실행할 수 있는 태스크는 1개뿐입니다.
READY 태스크 ≥ 2 어느 태스크에게 CPU를 양보할지 스케줄러가 판단해야 함
우선순위 숫자가 높을수록 시급함을 뜻하며, 스케줄러는 높은 값부터 실행

우선순위는 실시간성 확보와 자원 효율성 간의 균형점입니다.


2. configMAX_PRIORITIES 설정

/*  FreeRTOSConfig.h  */
#define configMAX_PRIORITIES    5   /* 0~4 총 5단계 */
  • 0 : 최하위(백그라운드 로깅 등)
  • 4 : 최상위(센서 캡처, 모터 제어 등)
  • 값을 지나치게 키우면 ➜ 문맥 전환/RAM 사용량 증가 → 성능 저하

3. 태스크 생성 API xTaskCreate()

BaseType_t xTaskCreate(
    TaskFunction_t  pxTaskCode,     /* 태스크 함수(핸들러)              */
    const char     *pcName,         /* 디버그용 이름(선택)             */
    uint16_t        usStackDepth,   /* 스택 크기 – “Word” 단위          */
    void           *pvParameters,   /* 첫 매개변수(선택)               */
    UBaseType_t     uxPriority,     /* 우선순위(0~configMAX_PRIORITIES)*/
    TaskHandle_t   *pxCreatedTask); /* 핸들 저장 포인터(선택)          */

반환값 의미

pdPASS 태스크·스택 메모리 동적 할당 성공
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 힙 공간 부족

4. 스택 크기(usStackDepth) 계산법

  • Word 단위다.
    • Cortex-M (32-비트) ⇒ 1 Word = 4 Byte
  • 예) usStackDepth = 256 → 256 ×4 = 1 KiB

🚩 스택 오버플로 감시 → configCHECK_FOR_STACK_OVERFLOW 2 활성화 권장


5. 예제 코드 — 두 태스크(동일 우선순위) 만들기

목표 : 1 초 간격으로 교대로 “Hello World from Task-1/2” 출력

/*-----------------------------------------------------------
 * 1.  include
 *----------------------------------------------------------*/
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

/*-----------------------------------------------------------
 * 2.  태스크 핸들
 *----------------------------------------------------------*/
static TaskHandle_t hTask1, hTask2;

/*-----------------------------------------------------------
 * 3.  태스크 함수 선언
 *----------------------------------------------------------*/
static void vTaskFn1(void *arg);
static void vTaskFn2(void *arg);

/*-----------------------------------------------------------
 * 4.  main()
 *----------------------------------------------------------*/
int main(void)
{
    HAL_Init();              /* HAL 초기화                          */
    SystemClock_Config();    /* CubeMX 생성 함수                    */

    /* ----- Task-1 생성 ----- */
    configASSERT(
        xTaskCreate( vTaskFn1,          /* 태스크 함수               */
                     "Task-1",          /* 태스크 이름               */
                     256,               /* 스택(Word) = 1 KiB        */
                     NULL,              /* 첫 매개변수 없음          */
                     2,                 /* 우선순위 = 2              */
                     &hTask1 ) == pdPASS);

    /* ----- Task-2 생성 ----- */
    configASSERT(
        xTaskCreate( vTaskFn2,
                     "Task-2",
                     256,
                     NULL,
                     2,                 /* 같은 우선순위             */
                     &hTask2 ) == pdPASS);

    vTaskStartScheduler();   /* 스케줄러 시작 – 반환 안 됨 */

    while (1);               /* 비실행 – 예외 상황용 루프 */
}

/*-----------------------------------------------------------
 * 5.  태스크 핸들러 구현
 *----------------------------------------------------------*/
static void vTaskFn1(void *arg)
{
    (void)arg;
    for (;;)
    {
        printf("Hello World from Task-1\r\n");
        vTaskDelay(pdMS_TO_TICKS(1000));   /* 1 초 block → 타임슬라이스 */
    }
}

static void vTaskFn2(void *arg)
{
    (void)arg;
    for (;;)
    {
        printf("Hello World from Task-2\r\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

필수 설정 체크

항목 값

configMAX_PRIORITIES ≥ 3 (예: 5)
configUSE_PREEMPTION 1 (프리엠프형 스케줄링)
configUSE_TIME_SLICING 1 (같은 우선순위끼리 타임슬라이스)
USE_NEWLIB_REENTRANT 1 (printf 다중-태스크 안전)

6. 실행 결과 (UART 콘솔 예시)

Hello World from Task-1
Hello World from Task-2
Hello World from Task-1
Hello World from Task-2
...
  • Tick 인터럽트마다 동일 우선순위 태스크가 교대로 CPU 차지
  • configUSE_TIME_SLICING을 0으로 바꾸면 자진 양보(vTaskDelay) 시에만 교대됨

7. 더 실험해보기

  1. 우선순위 변경
  2. vTaskPrioritySet(hTask2, 3); // 런타임 중 Task-2 승격
  3. 스택 모니터링
  4. UBaseType_t watermark = uxTaskGetStackHighWaterMark(NULL); printf("남은 워터마크 = %lu word\r\n", watermark);
  5. 우선순위 역전 → 뮤텍스(Priority Inheritance) 사용 예제는 다음 섹션에서.

정리

  • 태스크마다 우선순위를 부여해 실시간 요구를 만족시킨다.
  • 너무 많은 단계는 문맥 전환·RAM 사용량을 늘려 오히려 성능 저하시킬 수 있다.
  • 간단한 예제로 구조를 익힌 뒤, 실제 프로젝트에서 점진적으로 적용해보자!