1.
FreeRTOS kernel의 portable-GCC-ARM_CMX 폴더에 들어가보면
port.c와 portmacro.h가 보인다.
이 port.c에서
인터럽트들과 스케쥴러가 어떻게 작동하는지 확인할 수 있다.
2.
ARM Cortex M시리즈에서 돌아가는 FreeRTOS Kernel interrupt에는
3가지 종류의 인터럽트가 존재한다.
(1) SVC Interrupt
(2) PendSV Interrupt
(3) SysTick Interrupt
3.
SVC Interrupt는
특정 instruction을 수행하여 SVC handler를 실행시키고,
이 핸들러를 통해 very first task를 수행한다.
PendSVInterrupt는
PendSVHandler를 실행시키고, 태스크의 Context Switching를 수행한다.
SysTickInterrupt는
SysTick Handler를 실행시키고, RTOSTickManagement의 과정을 수행한다.
SysTick Timer는 유저어플리케이션이
시스템 내부의 사용하능한 다른 Peripheral Timer를 사용할 수 있도록 해준다.
(ioc파일에서 타이머 종류를 보면 TIM1부터 TIM4나 TIM6등 다양한 타이머가 존재함을 볼 수 있다.)
4.
RTOS Tick
FreeRTOSConfig.h에서
configTICK_RATE_HZ를 통해 SysTick를 변경할 수 있었다.
FreeRTOS 커널을 포함해 대부분의 ARM 아키텍처의 커널들은 SysTick Timer를 사용할 수 있다.
해당 MCU의 성능이나 작업상황, Cycle, Task들의 Context Switch 에 구애받지 않고
독립적으로 시간을 측정해야 하는 상황이 많다.
(가령 sleep()함수와 같이 정밀한 Time Elapse를 측정해야 하는 상황)
(https://www.youtube.com/watch?v=e5g8eYKEhMw)
6.
port.c 의 xPortSysTicHandler 함수를 보면
이러한 인터럽트들과, SysTic에 따라 컨텍스트 스위치가 일어나는 과정을 다 추적할 수 있다.
(1) SysTic Tick Interrupt가 발생하고, tick ISR이 수행된다면 - traceISR_ENTER()
(2) ready state에 있는 task들이 스캔된다. - xTaskIncrementTick()
(3) 스케쥴러는 다음으로 올 잠재적 next task를 결정하고, - xTaskIncrementTick()
(4) next task가 존재한다면, PendSV interrupt를 발생시킨다.
- portNVIC_INT_CTRL_REG = mportNVIC_PENDSVSET_BIT
(Interrupt를 해당 레지스터에 trigger)
7.
(2), (3)
xPortSysTicHandler 함수를 보면,
xTaskIncrementTick()함수를 수행하는 것을 확인할 수 있다.
task.c파일의 xTaskIncrementTick()
함수를 확인해보면,
pxReadyTasksLists와 같이 Ready 상태의 Task들을 체크하는 과정을 확인할 수 있다.
--------------------------------
8.
지금까지 RTOS의 세가지 인터럽트를 알아보았다.
이제 FreeRTOS의 Scheduler가 어떻게 구현되어 동작
9.
메인문에서, 태스크 핸들러를 다 설정한 뒤
vTaskStartScheduler 함수를 수행한 것을 볼 수 있다.
(1) vTaskStartSchedule()는
아키텍처 전용 코드인 xPortStartSchedule()함수를 호출하며,
(2) xPortStartScheduler()는
- (2-1) FreeRTOSConfig.h에서 configTICK_RATE_HZ 값을 토대로
SysTick timer가 원하는 주기에 따라 interrupt를 issue(알리다?)하도록 설정
- (2-2) PendSV와 SysTick interrupt를 설정
- (2-3) SVC instruction을 실행하여 가장 첫 first task를 수행
하는 과정을 거친다.
10.
tasks.c의 vTaskStartScheduler()는
port.c의 xPortStartScheduler()를 호출하고,
이 함수에서
port.c의 SysTick = tick Timer를 설정한다.(2-1)
11.
이 부분은 port.c의 xPortStartScheduler()를 직접 보는게 더 이해하기 좋다.
xPortStartScheduler( void )를 통해 스케쥴러가 시작되면,
(1)
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
를 통해서 PENDSV와 SYSTIC의 priority를 ARM CM시리즈의
제일 낮은 우선순위인 15로 설정한다.
ARM CM시리즈의 메모리맵 상에서
portNVIC_SHPR3_REG는 Priority Register로서
인터럽트의 우선순위를 나타낸다.
(2)
이후 vPortSetupTimerInterrupt()를 통해
portNVIC_SYSTIC 부분의 레지스터를 설정하여 타이머를 사용할 수 있도록 설정한다.
SYSTIC 타이머는, Load 레지스터와 Ctrl 레지스터의 값들을 설정함으로써 작동시킬 수 있다.
configSYSTIC_CLOCK_HZ은 FreeRTOSConfig.에서 설정한 값(1000ms)이고
configTICK_RATE_HZ는 device configuration tool에서 확인했던 프로세서의 clock speed이다.
내 보드는 SYSCLK 168MHz이고, prescaler없이 Cortext System Timer에게 168Mhz로
타이머를 제공하고 있다.
(prescaler를 1로 둬서 configTICK_RATE_HZ가 정확히 어디에 대응되는 값인지는 모르겠다.)
12.
(2)
이렇게 SysTick Timer Setup이 끝나고,
portSYSTICK_NVIC_LOAD_REG에
configSYSTIC_CLOCK_HZ / configTICK_RATE_HZ - 1
= configCPU_CLK_HZ / configTICK_RATE_HZ - 1
= 168,000,000Hz / 1000Hz - 1 = 168,000 - 1 = 167,999
값을 넣어준 뒤,
portSYSTICK_NVIC_CTRL_REG를 설정하여
SysTick Timer를 작동시키면,
167999 부터 downcounting을 수행하며 0이 될때마다 인터럽트를 발생시키고,
다시 167999 로 값을 리필한뒤 다시 down counting을 하는 타이머가 완성된다.
CPU는 1/168,000,000 초 마다 이 Load Register의 값을 낮추고,
168,000 번의 Cycle이 돌아서 167,999의 값이 0이 되었을 때,
그 때는 실제로 1/ 1000 초가 지난 시점이 된다.
이 지점에서 SysTic 인터럽트가 발생하고, Load Register는 다시 167999 값으로 리필되고
계속 이 과정을 반복한다.
13.
이런식으로 SysTick 인터럽트가 1ms마다 발생하는 것을 볼 수 있다.
처음 task2가 추가 된 뒤, 1ms이후 컨텍스트 스위칭이 일어난 것을 볼 수 있다.
14.
요약1
SysTick Interrupt가 발생하면,
(1) ISR, 인터럽트 서비스 루틴이 xPortSysTickHandler를 실행
(2) xPortSysTickHandler는 portDISABLE_INTTERUPT로 해당 시스템이 인터럽트를 받는 것을 중지
(3) xTaskIncrementTick()를 호출하여 전역변수인 xTickCount를 1만큼 늘림
new tick value로 인해 unblock 될 task가 있는지 확인한 후
(선택, 디버깅시) applicationHook function을 호출
context switch 가 필요하면 True를 리턴
(4) xPortSysTickHandler에서 PendSV 인터럽트를 대기시키고
(5) portENABLE_INTTERUPTS로 인터럽트 사용mode로 이행
하는 과정을 통해서,
Task들의 Context Switch하면서 일을 수행한다.
15.
요약2.
스케쥴러를 시작하면
(1) SysTick timer가 원하는 주기에 따라 interrupt를 issue(알리다?)하도록 설정
(2) PendSV와 SysTick interrupt를 설정
(3) SVC instruction을 실행하여 가장 첫 first task를 수행
하는 과정을 통해 스케쥴러를 시작하고 Task를 처리하기 시작한다.
'지식이 늘었다 > RTOS' 카테고리의 다른 글
Hook과 Debug (1) | 2025.04.28 |
---|---|
FreeRTOS와 task creation, SRAM, 힙 메모리 (0) | 2025.04.15 |
Task란 무엇인가?, xTaskCreate, xTaskDelete (0) | 2025.04.11 |
CubeIDE에서 STM32 FreeRTOS 커널 올리기 (1) | 2025.04.10 |
RTOS의 인터페이스 - CMSIS (0) | 2025.04.09 |