.
├── 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
│ ├── Kernel.c
│ ├── Kernel.h
│ ├── task.c
│ └── task.h
├── lib
│ ├── armcpu.c
│ ├── armcpu.h
│ ├── stdio.c
│ ├── stdio.h
│ ├── stdlib.c
│ └── stdlib.h
├── Makefile
└── navilos.ld
이전 작업에서 Context Switching 함수까지 만들었으므로 이제 Kernel을 시작하면 된다.
main
int main(void)
{
Hw_init();
uint32_t length;
putstr("Hello world!\n");
Kernel_task_init();
Kernel_init();
// printf_test();
//Timer_test();
//length = getstr();
while(true);
}
간단하다.
Kernel_task_init 에서 task 메모리를 초기화 해주고 Kernel_init에서 task를 만든후 마지막에 Kernel Start를 호출한다.
Kernel_task_init은 바뀐게 없으므로 생략한다.
Kernel_init
static void Kernel_init(void)
{
uint32_t taskId;
taskId = Kernel_task_create(User_task0,1);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task0 creation fail\n");
}
taskId = Kernel_task_create(User_task1,1);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task1 creation fail\n");
}
taskId = Kernel_task_create(User_task2,1);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task2 creation fail\n");
}
Kernel_start();
}
이전 코드에서 바뀐건 없다.
마지막 Kernel_start를 추가하여 Kernel을 시작한다.
Kernel_start
void Kernel_start(void)
{
Kernel_task_start();
}
void Kernel_task_start(void)
{
sNext_tcb = &sTask_list[sCurrent_tcb_index];
Restore_context();
}
Kernel_start 함수를 통해 Kernel_task_start를 호출한다.
Kernel_task_start에서는 sNext_tcb에 실행할 Task 를 가지고 온다음
Restore_context를 통해 task를 실행한다.
실행하게 될 task 코드는 간단하다.
void User_task0(void)
{
uint32_t local = 0;
while(true)
{
debug_printf("User Task #0\n", &local);
delay(1000);
Kernel_yield();
}
}
void User_task1(void)
{
int32_t local = 0;
while(true)
{
debug_printf("User Task #1\n", &local);
delay(1000);
Kernel_yield();
}
}
void User_task2(void)
{
int32_t local = 0;
while(true)
{
debug_printf("User Task #2\n", &local);
delay(1000);
Kernel_yield();
}
}
while문을 통해 terminal 에서 자신이 몇번 째 Task인지 출력한다.
local 인자가 붙는데 출력하기 위한 %x 넣는걸 깜빡했다.
어차피 gdb를 통해 볼 수 있으므로 컴파일 후 진행함.
Kernel_yield
void Kernel_yield(void)
{
Kernel_task_scheduler();
}
void Kernel_task_scheduler(void)
{
sCurrent_tcb = &sTask_list[sCurrent_tcb_index];
sNext_tcb = Scheduler_round_robin_algorithm();
Kernel_task_context_switching();
}
__attribute__ ((naked)) void Kernel_task_context_switching(void)
{
__asm__ ("B Save_context");
__asm__ ("B Restore_context");
}
Kernel_yield를 통해 Kernel_task_scheduler를 호출한다.
Kernel_task_shceduler에서 round_robin_algorithm을 통해 다음에 실행 할 Task를 정한 후
Context switching을 한다.
Round robin algorithm은 간단하므로 여기서는 디버깅하지 않고 task가 switching 하는 부분을 제대로 분석해보자.
Task가 Switching 하기 위해서는 우선 Task의 정보를 Save를 해야한다.
Save_context
static __attribute__ ((naked)) void Save_context(void)
{
// save current task context into the current task stack
__asm__ ("PUSH {lr}"); //__attribute__ 가 없으면 lr 이 현재 Task의 lr 이 아닌 Save_context의 lr이 들어가게 된다.
__asm__ ("PUSH {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}");
__asm__ ("MRS r0, cpsr"); //current program status register
__asm__ ("PUSH {r0}");
// save current task stack pointer into the current TCB
__asm__ ("LDR r0, =sCurrent_tcb"); //r0에 sCurrent_tcb의 값을 복사한다.
__asm__ ("LDR r0, [r0]"); //r0에 sCurrent_tcb의 Stack pointer 값을 복사한다.
__asm__ ("STMIA r0, {sp}"); //sCurrent_tcb의 stack ponter 값을 갱신한다.
}
__asm__("push {lr}") 부터 __asm__("push {r0}") 까지는 stack에 레지스터 값을 push 하는 코드이므로 어렵지 않다.
실제 레지스터 값과
stack 메모리를 확인해보면 Stack에 정상적으로 push 된걸 알 수 있다.
마지막 cpsr 값까지 push 한 stack의 값은 0x8fffa0인데 sCurrent_tcb -> sp 값은 0x8fffc0 이지만
Task 레지스터를 push 하고 난 다음 sp 값은 0x8fffa0 이다 (스택이 Top에서 bottom으로 증가)
나중에 load를 하기 위해서는 sCurrent_tcb -> sp 값을 업데이트 해줘야 한다.
현 sp 값을 읽어 오기 위해 sCurrent_tcb 주소를 load 한다. (LDR r0, =sCurrent_tcb)
Kerneltcb 구조체의 1번째 member는 sp 이므로 따로 계산 없이 sCurrent_tcb가 나타내는 값을 호출한다.(LDR r0, [r0])
Task의 sp 값을 갱신한다. (STMIA r0, {sp})
sp 갱신 후 gdb를 통해 갱신 확인
sp가 갱신 됨으로 써 사용중인 Task back up이 완료 되었다.
Restore_context
static __attribute__ ((naked)) void Restore_context(void)
{
// restore next task stack pointer from the next TCB
__asm__ ("LDR r0, =sNext_tcb"); // 다음에 실행할 Task의 tcb를 r0에 load한다.
__asm__ ("LDR r0, [r0]"); //r0의 stack pointer를 load한다.
__asm__ ("LDMIA r0!, {sp}"); //불러 온 stack pointer를 범용 sp에 load 한다.
// restore next task context from the next task stack
__asm__ ("POP {r0}"); // sNext_tcb에 저장 된 r0(cpsr)을 복구 한다.
__asm__ ("MSR cpsr, r0"); // cpsr을 복구 한다.
__asm__ ("POP {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}"); //r0 ~ r12 까지 register를 복구한다.
__asm__ ("POP {pc}"); //pc register를 복구한다.
}
Next Tcb의 KernelTcb의 구조체를 호출한다. (LDR r0, =sNext_tcb)
이전 Page에 언급 했듯이 Stack은 FILO 이므로 save 했던 거와 반대 순서로 Load한다.
Stack을 load한다.(LDMIA r0!, {sp})
->순서상 Stack 복구 후 다음 Stack은 r0 가 들어가 있다.그러므로 LDMIA Base Register에 ! 붙여 pointer가 다음 stack을 나타내게 한다.
r0를 load한다.(POP {r0})
-이하 생략-
Terminal에서 Context가 Switching 하는 log.
'Firmware > RTOS' 카테고리의 다른 글
임베디드 OS 개발 프로젝트 14(Event 처리) (0) | 2020.03.11 |
---|---|
임베디드 OS 개발 프로젝트 13(Context Switching - Priority) (0) | 2020.03.10 |
임베디드 OS 개발 프로젝트 11(Context Switching - 1) (0) | 2020.03.08 |
임베디드 OS 개발 프로젝트 11(Scheduler) (0) | 2020.03.08 |
임베디드 OS 개발 프로젝트 10(TASK) (0) | 2020.03.05 |