.
├── boot
│ ├── Entry.S
│ ├── Handler.c
│ └── Main.c
├── hal
│ ├── HalInterrupt.h
│ ├── HalTimer.h
│ ├── HalUart.h
│ └── rvpb
│ ├── Interrupt.c
│ ├── Interrupt.h
│ ├── Regs.c
│ ├── Timer.c
│ ├── Timer.h
│ ├── Uart.c
│ └── Uart.h
├── include
│ ├── ARMv7AR.h
│ ├── memio.h
│ ├── MemoryMap.h
│ ├── stdarg.h
│ └── stdint.h
├── kernel
│ ├── task.c
│ └── task.h
├── lib
│ ├── armcpu.c
│ ├── armcpu.h
│ ├── stdio.c
│ ├── stdio.h
│ ├── stdlib.c
│ └── stdlib.h
├── Makefile
└── navilos.ld
본격적으로 RTOS를 만들기 위해 책에서는 따로 KERNEL이란 파일을 만들어 주었다.
Task란 운영체제에서 프로그램 그 자체를 봐도 된다. 태스크가 바뀐다는건 동작하는 프로그램이 바뀐다는 말이다.
이처럼 Task가 바뀌는 것을 Switching 되었다라고 표현한다.
Task가 Switching 할 때 사용중이던 Task의 정보를 기록해 놓아야 하는데 이때
Task의 현재 상태 정보를 Context 라고 한다.
1. 메모리 구조
이전에 직접 짰던 Stack 구조이다
아래 그림과 같이 Task stack은 0x00800000 로 시작하는 걸 알 수 있다.
실제 코드
#ifndef KERNEL_TASK_H_
#define KERNEL_TASK_H_
#include "MemoryMap.h" // TASK_STACK_SIZE
#define NOT_ENOUGH_TASK_NUM 0xFFFFFFFF
#define USR_TASK_STACK_SIZE 0x100000 //1MB
#define MAX_TASK_NUM (TASK_STACK_SIZE / USR_TASK_STACK_SIZE)
// TASK_STACK_SIZE = (GLOBAL_ADDR_START - TASK_STACK_START) = 0x04800000 - 0x00800000
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_ */
그림에서 Code region이 코드에 주석 처리 되어있는 GLOBAL_ADDR_START 이다.
task의 context는 아래의 정보를 가지고 있다.
1. 현재 TASK가 실행되고 있는 동작모드(SPSR 레지스터)
2. R0 ~ R12 까지 레지스터
3. Program Counter
정의 된 USR_TASK_STACK_SIZE 는 1MB 이고
Task Stack size는 64MB 이므로 최대 64개의 TASK를 구현 할 수 있다.
Kernel_task_init()
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)); //STACK Base calculate
sTask_list[i].sp = (uint32_t)sTask_list[i].stack_base + USR_TASK_STACK_SIZE - 4; //STACK allocate
sTask_list[i].sp -= sizeof(KernelTaskContext_t); // size of Context
KernelTaskContext_t* ctx = (KernelTaskContext_t*)sTask_list[i].sp; // stack에 Context 저장
ctx ->pc = 0;
ctx ->spsr = ARM_MODE_BIT_SYS;
}
}
Kernel_task_init 함수는 말 그대로 초기화 하는 함수이다.
앞서 task.h 에서 선언한거 같이 MAX_TASK_NUM은 64개이고
for 문을 통하여 할당 된 stack 시작점, SP 레지스터, PC 레지스터, SPSR 레지스터를 모두 지정해준다.
위 코드를 통해 초기화한 Task Stack 구조는 다음 그림과 같다.(책 140p 참조)
위 그림이 이해 되었다면 GDB에서 할당된 Task_list 역시 각각 1MB씩 메모리가 할당 되었음을 알 수 있다.
초기화가 되었으므로 이제 Task를 만들어 보자
Kernel_Task_create
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); //29line에서 ++해줬으므로 다시 -1 해준다.
}
new_tcb는 task control block으로 Task list에서 사용하지 않은 task control block 객체를 하나 가지고 온다.
그 다음 sAllocated_tcb_index++하여 다음 create 호출 시 사용하지 않은 task를 가지고 올 수 있게 한다.
KernelTaskContext_t* ctx = (KernelTaskContext_t*)new_tcb->sp;
이 코드는 ctx를 변수라 생각하지말고 그냥 메모리 주소라 생각하면 이해하기가 쉽다.
Task Context 를 기록하는 메모리 주소(ctx)에 new_tcb ->sp 를 가지고온다.
위 그림에서 보면 new_tcb -> sp는 Task Context를 나타내고 있다.
따라서 ctx 에 현재 만들어진 task의 context를 가지고온다.
ctx->pc = (uint32_t)startFunc;
가지고 온 task context에 등록된 함수 주소를 가지고 온다.
따라서 task가 실행하게 되면 등록된 함수를 실행 하게 된다.
main.c 를 따라가면서 흐름을 보자.
int main(void)
{
Hw_init();
uint32_t length;
putstr("Hello world!\n");
Kernel_task_init();
Kernel_init();
printf_test();
while(true);
}
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);
}
main 함수에 보면 Kernel_init 함수를 호출하여 총 4개의 task를 만든다.
(User_task0, User_task1, User_task2, User_task3, User_task4)
4개의 함수를 만들었지만 현재 스케줄러는 만들지 않았으므로 while(true)만 돈다.
여기서 위에서 언급한 ctx 주소를 출력해 보면 3개의 Task가 정상적으로 등록 되어 있음을 알 수 있다.
(GDB에서 ctx 가 전역변수로 선언되지 않았으므로 단순하게 ctx 출력하면 없다고 에러가 뜬다. 따라서 create 함수 내부에서 ctx 메모리 주소를 확인 후 외부에서 확인 할 때 메모리 주소를 직접 출력하여 확인한다.)
Kernel init 함수 종료 후 GDB를 통해 Task가 정상적으로 할당 된걸 확인 할 수 있다.
1번째 ctx 메모리를 살펴보면
SYS 모드(0x1f), 레지스터R0~R12가 할당 된걸 확인 할 수 있고
Program Counter Register가 0x18c 즉 User_task0 에 정상적으로 할당 된걸 알 수 있다.
나머지 ctx 역시 동일하게 sys모드로 되어 있으며 다만 코딩 한대로 User_task1, User_task2로
Program Counter만 다르게 들어가 있다.
'Firmware > RTOS' 카테고리의 다른 글
임베디드 OS 개발 프로젝트 11(Context Switching - 1) (0) | 2020.03.08 |
---|---|
임베디드 OS 개발 프로젝트 11(Scheduler) (0) | 2020.03.08 |
임베디드 OS 개발 프로젝트 9(Timer) (0) | 2020.02.16 |
임베디드 OS 개발 프로젝트 8(Uart Interrupt) (0) | 2020.02.15 |
임베디드 OS 개발 프로젝트 7(Uart Interrupt) (0) | 2020.02.15 |