컨텍스트 스위칭

다른 태스크, 프로그램으로 전환하기 위해 기존 정보 컨택스트를 다른대다 백업하고 

실행할 태스크의 컨택스트를 로드하는 동작

 

과정

1. 현재 태스크 컨텍스트를 스택에 백업

2. 실행할 태스크 컨트롤 블록을 스캐줄러로 전달받음

3. 전달받은 태스크 컨트롤 블록에서 스택 포인터를 읽고

3. 읽은 스택포인터로부터 컨텍스트를 읽어 ARM 코어에 복구

4. 실행할 태스크 직전 실행 위치로 이동

 

kernel/task.c

위 과정을 하도록 Kernel_task_scheduler 함수를 구현했는데

정작 중요한 Kernel_task_context_switching() 내용이 없어 구현해야한다.

 

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

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

//task
static KernelTcb_t	sTask_list[MAX_TASK_NUM];
static uint32_t		sAllocated_tcb_index;

//scheduling
static uint32_t		sCurrent_tcb_index;
static KernelTcb_t*	Scheduler_round_robin_algorithm(void);

//context switching
static KernelTcb_t*	sCurrent_tcb;
static KernelTcb_t*	sNext_tcb;

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

static KernelTcb_t*	Scheduler_round_robin_algorithm(void)
{
	sCurrent_tcb_index++;
	sCurrent_tcb_index %= sAllocated_tcb_index;

	return &sTask_list[sCurrent_tcb_index];
}

void Kernel_task_scheduler(void)
{
	sCurrent_tcb = &sTask_list[sCurrent_tcb_index];
	sNext_tcb = Scheduler_round_robin_algorithm();

	Kernel_task_context_switching();
}

 

 

그런데 컨텍스트 스위칭 함수 구현 부분은

책에 설명이 제대로 되있지 않아 깃헙 참고해서 구현하면...

 

lib/(아키텍처)/switch.h/.c 이름으로 있긴한데

현재 아키텍처 고려해서 하는건 아니기도하고

뒤에 트리를보면 별도 switch 파일을 만드는게 없어서 그냥 task.c에다가 그대로 구현하야겠다.

 

위 코드의 kernel_task_scheduler 아래에 

kernel_task_context_switching 함수를 구현하면서

__attribute__ ((naked))로 어트리뷰트를 네이크드 설정 시 컴파일러가

자동으로 스택 백업, 복구, 리턴 관련 어셈블리어를 생성하지 않는다고 한다.

=> 역 어샘블시

<Save_context>

<Restore_context>

만 나온다.

 

ARM의 인스트럭션 B를 쓴건 LR(링크 레지스터 : 함수 호출전 리턴 주소 보관)을 바꾸지 않기 위함인데

naked로 스택 확보해서 뭐 하지 않았기 때문이라 한다.

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

 

컨텍스트  스위칭 과정 보기

 

컨텍스트 스위칭 과정을 정리해서 보면 

task1 이 동작하다가 전환 시 

task1 context를 저장하고

변경된 sp를 task1 tcb에 저장.

그 다음 task2 tcb의 sp를 읽고

해당 sp로부터 task2 context를 복구

task2 작업 스택 원래 sp로 돌아와 수행

 

 

 

 

태스크 초기화 및 생성

 

커널 tcb를 sTask_list[MAX_TASK_NUM] 그러니까 64개 배열 형태의 전역 변수로 만들었으니 

global variable 영역에 있을 것이고 ..

태스크 스택은 1MB 씩 task stack 영역에 있으니까 맞겟지..?

 

태스크 컨텍스트 저장 하기

 

이제 컨텍스트 백업을 위한 코드와 복구를 위한 코드를 구현해야하는데.

 

태스크 컨텍스트의 메모리 주소는 spsr, 레지스터, pc가 작은데서 큰 순이지만

스택은 주소가 큰곳에서 작은곳으로 가서 

가장 마지막에 담아야할 pc를 스택의 가장 먼저

중간인 r0_r12은 pc 다음

가장 마지막에 읽어야할 spsr를 어샘블리어 상에서 가장 먼저 저장시켜야한다.

 

 

아까 쓴 kernel_task_context_switching에 이어서

Saved_context를 구현하면

 

먼저 LR을 스택에 푸시해서 태스크 컨택스트 pc 변수 저장(복귀때 리턴 주소)

그 다음 r0, r1 ... r12를 스택에 저장

프로그램 상태 레지스터 psr을 바로 직접 메모리에 저장불가해 r0에 전달했다가 푸시

 

그 뒤 tcb의 포인터 변수를 로드 LDR r0, =sCurrent_tcb

LDR r0, [r0] 로 r0에 저장된 tcb 포인터의 주소에서 실제 값 tcb 포인터를 가져온 뒤

tcb 포인터 주소를 sp에 저장

 

이 과정을 c언어로 쓰면 => sCurrent_tcp->sp = ARM_코어_SP_레지스터값

 

__attribute__ ((naked)) void Kernel_task_context_switching(void)
{
	__asm__ ("B Save_context");
	__asm__ ("B Restore_context");
}

static __attribute__ ((naked)) void Saved_context(void)
{
	//save current task context into the current task stack
	__asm__ ("PUSH {lr}");
	__asm__ ("PUSH {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}");
	__asm__ ("MRS r0, cpsr");
	__asm__ ("PUSH {r0}");
	//save current task stack pointer into the current TCB
	__asm__ ("LDR r0, =sCurrent_tcb");
	__asm__ ("LDR r0, [r0]");
	__asm__ ("STMIA r0!, {sp}");
}

 

-------------------------

컨텍스트 복구하기

 

이 글을 일요일에 쓰고, 화목은 조별 과제 준비하느라 어떻게 했는지 잘 생각안나는데

이번에는 컨텍스트 복구 과정을 그림을 따라보면

1. 다음 TCB SP읽어서 현재 ARM SP에 쓰기

2. 현재 ARM SP로부터 태스크 컨택스트 읽기 - cpsr -> r0_r12 -> pc 순

3. 태스크 2의 작업스택으로 돌아와 동작 수행한다

naked를 오랜만에 보는대

스택 복구, 리턴 같은 코드를 생성하지 않도록 컴파일러에 전달하는 명령어였던거같다.

attribute는 컴파일러 확장기능을 쓰도록 하는 명령어이고

 

static __attribute__ ((naked)) void Restore_context(void)
{
	//restore next task stack pointer from the next TCB
	__asm__ ("LDR	r0, =sNext_tcb");
    __asm__ ("LDR	r0, [r0]");
    __asm__ ("LDMIA	r0!, {sp}");
    // restore next task context from the next task stack
    __asm__ ("POP {r0}");
    __asm__ ("MSR cpsr, r0");
    __asm__ ("POP {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}");
    __asm__ ("POP {pc}");
}

 

+ Recent posts