Task
- RTOS의 Task는 리눅스 같은 범용 OS의 프로세스와 같은 의미이다
- Task는 우선순위를 갖는다
- 현재 진행 중인 Task가 UART 인터럽트에 의한 Task보다 우선순위가 높다면 UART Task는 기존 Task가 끝날 때 까지 기다려야 한다
- 반대로, 모든 입력에 대한 응답은 10초 안에 처리되어야 한다는 조건이 있다면, 우선순위가 낮은 Task라도 10초 이전에 실행될 수 있다
Task control block
- Task control block은 개별 태스크 자체를 추상화 하는 자료 구조이다
- Task control block은 대표적으로 아래와 같은 내용을 포함한다
- Task의 이름, 번호
- Task의 우선순위
- Task의 상태 정보인 context
- 기타 등등 부수적 정보
- task control block은 kernel 디렉토리를 만들어서 구현하자
# kernel/task.h
#ifndef KERNEL_TASK_H
#define KERNEL_TASK_H
#include "MemoryMap.h"
#define NOT_ENOUGH_TASK_NUM 0xFFFFFFFF
#define USR_TASK_STACK_SIZE 0x00100000
#define MAX_TASK_NUM (TASK_STACK_SIZE / USR_TASK_STACK_SIZE)
typedef struct KernelTaskContext_t
{
uint32_t spsr;
uint32_t r0_r12[13];
uint32_t pc;
} KernelTaskContext_t;
typedef struct KernelTcb_t
{
uint32_t sp;
uint8_t* stack_base;
} KernelTcb_t;
typedef void (*KernelTaskFunc_t)(void);
void Kernel_task_init(void);
uint32_t Kernel_task_create(KernelTaskFunc_t startFunc);
#endif /* KERNEL_TASK_H */
- task stack 크기
USR_TASK_STACK_SIZE는 개별 task의 스택 크기로, 1mb이다
- 개별 task마다 필요에 따라 스택 크기가 다른게 유연한 설계이지만, 우리는 간단하게 구현하자
TASK_STACK_SIZE는 task 스택용으로 memory map에서 할당해 놓은 64mb 크기이다
- 즉 위 구성에서는 task를 최대 64개 만들 수 있다
- task control block
KernelTaskContext_t는 context를 추상화 한 자료구조이다
- 상태 레지스터, 범용 레지스터를 백업할 수 있는 영역이 있다
KernelTcb_t는 스택 관련 정보만 저장한다
- sp는 범용 레지스터에 있는 stack pointer이다
- stack_base는 context에 포함되지 않는 부가 데이터들이다
- task context는 결국 레지스터와 stack pointer 값이다
- 즉, task context를 전환한다는 말은 코어의 레지스터 값을 다른 task의 것으로 바꾼다는 말과 같다
task control block 초기화
- 메모리에 task control block 인스턴스를 만들고 초기화하는 코드
Kernel_task_init 함수를 만들어보자
# kernel/task.c
static KernelTcb_t sTask_list[MAX_TASK_NUM];
static uint32_t sAllocated_tcb_index;
void Kernel_task_init(void)
{
sAllocated_tcb_index = 0;
for (uint32_t i = 0; i < MAX_TASK_NUM; i++)
{
sTask_list[i].stack_base = (uint8_t*)(TASK_STACK_START + i * USR_TASK_STACK_SIZE);
sTask_list[i].sp = (uint32_t)(sTask_list[i].stack_base) + USR_TASK_STACK_SIZE - 4;
sTask_list[i].sp -= sizeof(KernelTaskContext_t);
KernelTaskContext_t* ctx = (KernelTaskContext_t*)sTask_list[i].sp;
ctx->pc = 0;
ctx->spsr = ARM_MODE_BIT_SYS;
}
}
- task control block들은 sTask_list에 선언된다
- task control block 개수는 sAllocated_tcb_index에 저장된다
- init 함수에서 sTask_list를 순회하면서 task control block을 모두 초기화한다
- stack_base는 task 스택의 시작 주소다
- 스택 포인터 sp는 거꾸로 내려가므로 stack의 마지막 주소에서 4byte를 뺀 값으로 설정하였다
- sp에서 4를 빼주는건 task 사이에 경계를 표시하고자 함이다
- sp에서 추가로 task control block 공간만큼 더 빼주었다
- pc는 0으로 초기화하였다
- 프로그램 상태 레지스터 spsr은 기본값으로 SYS로 설정하였다
Task 생성
- 이어서 task 생성을 위한
Kernel_task_create 함수를 만들어보자
- 이 함수는 task로 동작할 함수를 task control block에 등록하고 task control block을 커널에 만든다
uint32_t Kernel_task_create(KernelTaskFunc_t startFunc)
{
KernelTcb_t* new_tcb = &sTask_list[sAllocated_tcb_index++];
if (sAllocated_tcb_index > MAX_TASK_NUM)
{
return NOT_ENOUGH_TASK_NUM;
}
KernelTaskContext_t* ctx = (KernelTaskContext_t*)new_tcb->sp;
ctx->pc = (uint32_t)startFunc;
return sAllocated_tcb_index - 1;
}
sAllocated_tcb_index로 비어있는 task list 인덱스를 가져온다
- init 단계에서 스택에 저장해두었던 context 메모리 주소 포인터를 ctx로 가져온다
startFunc의 주소를 ctx->pc에 넣어준다
- 이 부분이 task 함수를 task control block에 등록하는 과정이다
sAllocated_tcb_index가 이미 1이 올라가 있으므로, 현재 task의 index를 가르키기 위해 1을 빼서 return 한다
Task 등록
- 현재는 스케줄러, 컨텍스트 스위칭이 구현되지 않아서 task를 실행시켜볼 수는 없다
- 보통 전체 시스템을 각 기능별로 나누어 개발하고, 해당 기능을 실행하는 task 함수를 대표로 하나씩 만들어 펌웨어 시작 후 RTOS 초기화 코드에서 개별적으로 task를 등록한다
- 예를 들어, 네트워크 모듈을 위한 별도 디렉토리가 있고, 그 중 네트워크 모듈 시작을 위한 task 함수도 있다
- 우리는 그런 모듈들이 없으므로, Main.c 파일에서 Kernel을 초기화하고 dummy task 함수를 만들어 커널에 등록시켜보자
void dummy_task(void)
{
debug_printf("dummy task\n");
while (true);
}
static void Kernel_init(void)
{
uint32_t taskId;
Kernel_task_init();
taskId = Kernel_task_create(dummy_task);
if (taskId == NOT_ENOUGH_TASK_NUM)
{
debug_printf("dummy task creation failed\n");
}
}
void main(void)
{
Hw_init();
Kernel_init();
...
}
Kernel_task_create 함수에 넘어간 함수 포인터는 task control block의 pc에 저장되고 해당 task 함수가 호출 될 것이다
- 현재 task 관리 설계에서는 task 종료를 보장하는 기능이 없으므로 task는
while(true)로 종료되지 않도록 하였다