인프런 강의 링크
홍영기(가일스쿨) 교수님 블로그 링크
졸업프로젝트로 FreeRTOS 기반 상용 RTOS인 'ESP-IDF'를 활용한 프로젝트를 진행한 뒤, 인프런의 FreeRTOS 강의 수강을 통해 내용을 확실히 정리했습니다. 이해한 내용을 최종적으로 정리하며 기록을 남기고자 글을 올립니다.
저작권을 최대한 존중하기 위해 홍영기 교수님의 강의자료와 실습자료는 일부라도 절대 공유하지 않으며, FreeRTOS 공식 레퍼런스 문서를 기반으로 작성합니다.
FreeRTOS의 코드는 다음과 같은 네이밍 규칙을 사용한다.
.
FreeRTOS에서 변수명을 결정할 때 접두어는 아래와 같이 결정한다.
c
: char
타입을 의미한다.s
: int16_t
타입 (short
)을 의미한다.i
: int32_t
타입 (int
)을 의미한다.x
: BaseType_t
타입을 의미한다. 상당히 자주 사용되는데, 구조체나 인스턴스 핸들 등 일반적인 타입을 제외하면 대부분 x
다.u
: unsigned
를 의미한다.p
: 포인터 변수를 의미한다.FreeRTOS에서 함수명을 결정할 때 접두어는 아래와 같이 결정한다.
v
: void
반환값이 없는 함수를 의미한다.x
: 변수명의 접두어인 x
와 같은 의미다. BaseType_t
를 반환하는 함수다.pv
: void*
타입을 반환하는 함수다.prv
: private 함수를 의미한다. 대표적으로 아무런 task도 실행되지 않을 때 실행되는 idle task가 호출하는 callback 함수 (hook)가 이 접두어를 사용하고 있다.FreeRTOS에서 매크로명은 그 매크로가 선언된 소스파일의 이름을 따라 결정된다.
예를 들어 무한정 기다림을 의미하는 portMAX_DELAY
는 portable.h
라는 커널 소스에 선언돼있다.
문서에 따르면 semaphore 관련 API는 거의 매크로로 구성돼있지만, 함수명 네이밍 규칙을 따라간다고 명시돼있다.
FreeRTOS에서 하나의 task는 하나의 스레드(thread)를 의미한다.
각 task에는 우선순위를 할당하며 숫자가 높을수록 큰 우선순위를 의미한다.
configMAX_PRIORITIES
까지 할당이 가능하다.Task는 return value가 없으며 (void*) 타입으로 여러 자료형을 매개변수로 받을 수 있다.
Task는 일회용 task와 주기적 task 2가지 종류가 있다.
void 태스크이름( void* pvParameters ) {
/* 어쩌고 저쩌고 */
vTaskDelete(NULL); // 꼭 해줘야 함.
}
void 태스크이름( void* pvParameters ) {
while(true) {
/* 어쩌고 저쩌고 */
}
}
각 task마다 local stack 공간이 할당된다. 이 공간은 메모리의 .bss
영역 또는 .heap
영역에 들어간다.
configSUPPORT_DYNAMIC_ALLOCATION
과 configSUPPORT_STATIC_ALLOCATION
값을 어떻게 설정하느냐에 따라, 그리고 task 생성 함수를 xTaskCreate
나 xTaskCreateStatic
중 어느 것을 사용하느냐에 따라 할당 방법이 달라진다.6가지 API가 주로 사용되며 앞 3개가 중요하고, 뒤 3개는 잘 안 쓴다.
xTaskCreate()
.
pvTaskCode
는 task의 기능이 선언된 함수의 함수포인터를 말한다.pcName
은 디버깅 용도로 사용하는 문자열이며 task의 이름을 말한다.usStackDepth
는 task마다 할당되는 stack 메모리를 말한다. 단위는 WORD
이며 우리가 사용하는 ARM Cortex-M보드에서는 1WORD == 4Byte
라는 점을 기억하자.pvParameters
는 task 함수로 전달할 매개변수를 말한다. 없다면 NULL
쓰면 된다. 전달할 매개변수를 (void*)
타입으로 캐스팅한 뒤 여기다가 넣어주면 task 함수에서 사용할 수 있다.pxCreatedTask
는 task를 제어하기 위한 TaskHandle_t
타입 핸들을 말한다. Task의 우선순위를 바꾸거나, task를 멈추거나 등 task에 대한 설정은 모두 이 핸들을 통해서 이뤄진다.vTaskDelay()
.
running
에서 blocked(waiting)
으로 변경하는 함수다. 설정된 시간 xTicksToDelay
동안 해당 task는 blocked
task가 되며, 다음 우선순위를 가진 task가 실행된다.tick
단위 시간을 사용하는 것 보다 pdMS_TO_TICKS()
매크로를 사용해서 우리에게 편한 ms(밀리초) 단위를 tick
으로 변환해서 사용하는 것이 좋다. 1 Tick
이 의미하는 시간이 다르기 때문이다.vTaskDelayUntil()
.
vTaskDelay()
와 똑같은 기능을 수행하지만, 작동 방식이 조금 다르다.
vTaskDelay()
는 호출 시점부터 지정된 시간만큼 blocked 되는 반면,
vTaskDelayUntil()
은 호출 시점과 관계 없이 목표 절대 시간 주기에 맞춰 blocked 된다.
vTaskSuspend()
와 vTaskResume()
vTaskPrioritySet()
전체 소스코드는 저작권을 고려해 올리지 않는다.
실습 내용은 다음과 같다.
10
의 task1을 만든다.9
의 task2를 만든다.xTaskCreate((TaskFunction_t)Task1, "Task1", 128, NULL, 10, &xHandle1);
xTaskCreate((TaskFunction_t)Task2, "Task2", 128, (void*)Param, 9, &xHandle2 );
8
로 낮춘다.vTaskSuspend(xHandle1);
vTaskPrioritySet(xHandle1, 8);
vTaskResume(xHandle1);
vTaskDelay()
를 주석처리 해보면서 두 task가 어떻게 작동하는지 확인한다.vTaskDelay()
를 주석처리 하지 않았을 때 결과다.b
를 출력하고 1초간 blocked 된다.a
를 출력하고 1초간 blocked 된다.b
를 출력하고 1초간 blocked 되는 과정이 반복된다.vTaskDelay()
를 주석했을 때 결과다.vTaskDelay()
를 만난 뒤 blocked 되면 task1이 시작된다.a
를 출력한다.b
를 1회 출력한 뒤 다시 blocked 된다.b
가 한 번 찍힌다.)a
를 출력하는 과정이 반복된다.vTaskDelay()
를 주석했을 때 결과다.vTaskDelay()
를 주석했을 때 결과다. (위 사진과 같은 결과)이번 실습을 통해 다음 세 가지를 배울 수 있었다.
우리는 앞서 각 task마다 지정된 크기의 local stack을 갖고있다고 배웠다. 만일 task 내에서 해당 stack size를 넘는 데이터를 선언하고 연산을 수행한다면 stack overflow가 발생할 것이다. 이 치명적인 fault를 어떻게 발견할 수 있을까? FreeRTOS는 stack overflow를 발견하기 위한 기초적인 두 가지 알고리즘을 제공한다.
FreeRTOSConfig.h
헤더파일 안에는 configCHECK_FOR_STACK_OVERFLOW
라는 상수가 정의돼있다. 이 상수는 0, 1, 2
세 가지 값을 가질 수 있다. 0
일 경우 stack overflow를 컨트롤러가 따로 검사하지는 않는다.
저작권을 존중하므로 전체 실습 소스코드는 공유하지 않는다.