본문 바로가기

SW 개발 공부/OS 개발 프로젝트

[나빌로스] 8.태스크

RealViewPB에서 Task Control Block (TCB) 설계 및 제어 개념

RealViewPB에서 태스크를 관리하기 위해 **Task Control Block (TCB)**를 사용하며, 이를 기반으로 태스크의 상태, 레지스터 컨텍스트, 스택 정보 등을 관리해야 한다. 


1. Task Control Block (TCB)란?

TCB는 각 태스크를 관리하는 자료구조이다.
운영체제가 여러 개의 태스크를 관리하고, **태스크 간 전환(Context Switch)**이 가능하도록 하기 위해 사용된다..

🔹 TCB의 주요 구성 요소:

  1. 컨텍스트(Context)
    • 실행 중인 태스크의 현재 상태를 저장 (CPU 레지스터 값)
    • spsr, r0-r12, pc, sp 등이 포함됨
  2. 태스크 정보
    • 태스크 ID, 태스크 이름, 우선순위 정보 저장
  3. 스택 정보
    • 개별 태스크의 스택 포인터 (sp)
    • 태스크의 스택 베이스 주소

 

작업 순서는 다음과 같다.

 

1) TCB 정의

2) 인스턴스 선언 추가

3) API 선언 및 정의

4) 사용

 

우선 한 테스크에는 레지스터들과 메모리가 존재한다.

레지스터는 KernelTaskContext_t에 정의되어있고, 관리하기 위한 sp와 메모리 주소는 KernelTcb_t에 정의되어있다.

 

#ifndef KERNEL_TASK_H_
#define KERNEL_TASK_H_

#include "MemoryMap.h"

#define NOT_ENOUGH_TASK_NUM		0xFFFFFFFF
#define USR_TASK_STACK_SIZE		0x100000
#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

 

이때, tcb의 stack_base는 base address를 의미하기에 해당 Task의 가장 낮은 주소를 지칭하게 된다.

sp는 현재 stack pointer를 의미함으로 base address + USR_TASK_STACK_SIZE -4; 가 된다.

 

이렇게 tcb를 초기화 한 이후, tcb의 레지스터를 초기화해야한다.

 

KernelTaskContext_t* ctx = (KernelTaskContext_t*)tcb.sp; 해당 코드는 tcb의 최상단 메모리를 KernelTaskContext_t

즉, 레지스터 형식으로인식하게 한다.

 

이를 통해 레지스터 형식으로 수정및 접근이 가능하다.

 

#include <stdint.h>
#include <stdbool.h>

#include "ARMv7AR.h"
#include "task.h"

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_MOD_BIT_SYS;
	}
}

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);
}

 

 

현재는 스케쥴러와 컨텍스트 스위칭을 구현하지 못한 상태이기 때문에 더미 태스크로 구현하면 아래와 같다.

#include <stdint.h>
#include <stdbool.h>

#include "HalUart.h"
#include "HalInterrupt.h"
#include "HalTimer.h"

#include "task.h"

#include "stdio.h"
#include "stdlib.h"

static void Hw_init(void);
static void Kernel_init(void);

static void Printf_test(void);
static void Timer_test(void);

void User_task0(void);
void User_task1(void);
void User_task2(void);


void main(void)
{
    Hw_init();

    uint32_t i = 100;
    while(i--)
    {
        Hal_uart_put_char('N');
    }
    Hal_uart_put_char('\n');

    putstr("Hello World!\n");

    Printf_test();

    User_task0();
    User_task1();
    User_task2();
}

static void Hw_init(void)
{
    Hal_interrupt_init();
    Hal_timer_init();
    Hal_uart_init();
    Kernel_init();
}


static void Printf_test(void)
{
    char* str = "printf pointer test";
    char* nullptr = 0;
    uint32_t i = 5;
    uint32_t* sysctrl0 = (uint32_t*)0x10001000;

    debug_printf("%s\n", "Hello printf");
    debug_printf("output string pointer: %s\n", str);
    debug_printf("%s is null pointer, %u number\n", nullptr, 10);
    debug_printf("%u = 5\n", i);
    debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
    debug_printf("SYSCTRL0 = %x\n",*sysctrl0);
}

static void Timer_test(void)
{
	while(true)
	{
		debug_printf("currunt count : %u \n",Hal_timer_get_1ms_counter());
		delay(1000);	
	}
}

static void Kernel_init(void)
{
	uint32_t taskId;

	Kernel_task_init();

	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");
}
void User_task1(void)
{
	debug_printf("User Task#1\n");
}
void User_task2(void)
{
	debug_printf("User Task#2\n");
}

 

결과는 아래와 같다.