꾸준히
Task 본문
Task
태스크는 운영체제에서 동작하는 프로그램 단위이다
태스크 간 전환이 생길 때 프로그램의 흐름에 문제가 생기면 안되기 때문에 태크스 컨트롤 블록은 현재 진행 중인 프로그램의 현재 상태 정보를 기록하고 있어야한다
Task Control Block (TCB)
개별 태스크 자체를 추상화하는 자료구조
무엇을 포함하고 있어야 태스크를 추상화 할 수 있는가는 사람마다 다르므로 개발자가 필요하다고 생각하는 정보들로 추상화하면 된다
typedef struct KernelTcb_t
{
uint32_t sp;
uint8_t* stack_base;
} KernelTcb_t;
Context
프로그램의 현재 상태 정보 = 현재 레지스터 값
태스크 컨텍스트는 결국 레지스터와 스택 포인터의 값이다. 스택포인터도 레지스터의 일부이므로 태스크 컨텍스트를 전환한다는 것은 코어의 레지스터 값을 다른 태스크의 것을 바꾼다는 말과 같다
typedef struct KernelTaskContext_t
{
uint32_t spsr;
uint32_t r0_r12[13];
uint32_t pc;
} KernelTaskContext_t;
Task 스택 구조
Code
1. 태스크 컨트롤 블록 초기화
void Kernel_task_init(void)
{
sAllocated_tcb_index = 0;
sCurrent_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;
}
}
이 예제에서는 태스크 컨텍스트를 태스크 컨트롤 블록이 아니라 해당 태스크의 스택에 저장한다
(위 그림에서 보면 작업스택 위에 컨텍스트 스택이 존재 한다)
2. 태스크 생성
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);
}
태스크로 동작할 함수를 태스크 컨트롤 블록에 등록
3. 태스크 등록
static void Kernel_init(void)
{
uint32_t taskId;
taskId = Kernel_task_create(User_task0);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task0 creation fail\n");
}
taskId = Kernel_task_create(User_task1);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task1 creation fail\n");
}
taskId = Kernel_task_create(User_task2);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task2 creation fail\n");
}
}
void User_task0(void)
{
debug_printf("User Task #0\n");
while(true);
}
void User_task1(void)
{
debug_printf("User Task #1\n");
while(true);
}
void User_task2(void)
{
debug_printf("User Task #2\n");
while(true);
}
함수 포인터를 파라미터로 kernel_task_create()에 넘긴다. 해당 함수 포인터는 태스크 컨트롤 블록의 pc에 저장 된다
그러면 나중에 컨텍스트 스위칭을 할 때 arm 코어의 pc 레지스터에 태스크 컨트롤 블록의 pc 값이 저장되고 해당 태스크 함수가 호출된다
* 태스크 컨트롤 블록 구조체는 sp와 stack_base밖에 없지만 컨텍스트까지 포함한 개념으로 설명한 것이니 헷갈리지 않도록 주의 !