Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

꾸준히

Context Switching 본문

RTOS

Context Switching

S210530 2023. 5. 29. 18:10

컨텍스트 스위칭이란

태스크란 동작하는 프로그램이고, 동작하는 프로그램의 정보는 컨텍스트이다
이 컨텍스트를 어딘가에 저장하고 다른 어딘가에서 컨텍스트를 가져다가 프로세서 코어의 레지스터에 복구하는 작업이 컨텍스트 스위칭이며 컨텍스트 스위칭이 완료되면 다른 프로그램(태스크)가 실행되는 것이다

* 컨텍스트

 

컨텍스트 스위칭 과정

1. 현재 동작하고 있는 태스크의 컨텍스트를 현재 스택에 백업한다
2. 다음에 동작할 태스크 컨트롤 블록을 스케줄러에서 받는다
3. 2에서 받은 태스크 컨트롤 블록을에서 스택 포인터를 읽는다
4. 3에서 읽은 스택포인터로 태스크 스택의 컨텍스트를 읽어 ARM코어에 복구한다
5. 다음에 동작할 태스크의 직전 프로그램 실행 위치로 이동한다
6. 태스크가 전환 된다

 

 

Task1이 현재 동작 중인 태스크입니다 Task2가 다음에 동작할 태스크입니다

<Task1>
Task1의 현재 스택 포인터에 그대로 현재 컨텍스트를 백업합니다
그리고 스택 포인터를 태스크 컨트롤 블록에 저장합니다 스택 포인터만 따로 저장하는 이유는 커널이 스택 포인터의 위치를 쉽게 가져올 수 있어야 스택에서 컨텍스트를 복구할 수 있기 때문입니다

<Task2>
Task2의 태스크 컨트롤 블록에서 스택 포인터 값을 읽습니다 그리고 범용 레지스터 SP에 그 값을 씁니다
그러면 ARM 코어에서는 스택 포인터가 바로 바뀝니다
그 상태에서 스택 관련 어셈블리 명령을 사용해서 컨텍스트를 복구합니다
컨텍스트를 복구하면서 자연스럽게 스택 포인터를 Task2가 컨텍스트 스위칭 하기 직전의 정상적인 스택 포인터 위치로 복구됨으로 써 태스크 전환이 완료 됩니다

 

Code

1. 컨텍스트 자료 구조

typedef struct KernelTaskContext_t
{
    uint32_t spsr;
    uint32_t r0_r12[13];
    uint32_t pc;
} KernelTaskContext_t;


spsr, r0_r12, pc 순서입니다 c언어에서 구조체의 멤버 변수는 메모리 주소가 작은 값에서부터 큰 값으로 배정됩니다
spsr이 0x04에 저장된다면 r0는 0x08, r1은 0x0c에 저장되는 식입니다 하지만 스택은 메모리 주소가 큰 값에서 작은 값으로 진행되기 때문에 KernelTaskContext_t에 맞춰 컨텍스트를 스택에 백업할 때는 pc, r0_r12, spsr 순서로 백업해야 합니다

 

2. 컨택스트 백업 코드

static __attribute__ ((naked)) void Save_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}");
}


<컨텍스트 백업>
LR은 KernelTaskContext_t의 pc 멤버 변수에 저장됩니다
그리고 범용 레지스터인 r0~r12까지 스택에 푸쉬합니다 여기까지 진행하면서 현재 r0부터 r12에 다른 값을 덮어 쓰지 않았으므로 이 값은 컨텍스트 스위칭 함수를 호출하기 직전의 값이 계속 유지되고 있습니다
마지막으로 cpsr을 spsr 멤버 변수 위치에 저장합니다 프로그램 상태 레지스터는 직접 메모리에 저장할 수 없으므로 r0를 사용합니다 필요한 범용 레지스터의 값은 이미 백업했으므로 이제 범용 레지스터는 값을 덮어쓰고 활용해도 됩니다
그이유는 어차피 나중에 컨텍스트를 복구할 때는 백업해 놓은 값이 복구되므로 지금 사용하는 값은 컨텍스트 복구에 영향을 주지 못합니다

<TCB 백업>
현재 동작 중인 태스크 컨텍스트 블록의 포인터 변수를 읽습니다
그리고 포인터에 저장된 값을 읽습니다 포인터에 저장된 값이 주솟값이므로 r0로 태스크 컨트롤 블록의 온전한 메모리 위치를 읽습니다
마지막으로 읽은 값을 베이스 메모리 주소로 해서 SP를 저장합니다
C언어로 간단하게 표현하면 다음과 같습니다

sCurrent_tcb->sp = ARM_Core_SP_Register;

 

3. 컨텍스트 복구 코드

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


컨텍스트를 복구하는 작업은 컨텍스트를 백업하는 작업의 역순입니다

<TCB 복구>
백업 동작의 역순이므로 첫 번째 작업은 sNext_tcb에서 스택 포인터 값을 읽어오는 작업입니다
태스크 컨트롤 블록의 sp 멤버 변수의 값을 읽어서 ARM 코어의 SP에 값을 씁니다

<컨텍스트 복구>
cpsr의 값을 꺼내서 ARM 코어의 CPSR에 값을 씁니다 이 시점 이후로는 r0~r12까지의 레지스터 값을 변경하면 컨텍스트 복구에 실패하게 됩니다
그래서 다른 작업을 하지 않고 r0~r12 값을 변강한 뒤 pc값을 쓰며 새로운 태스크 코드로 점프합니다
__asm__("POP {pc}"); 코드가 실행되는 순간 ARM 코어는 컨텍스트가 백업되기 직전의 위치로 pc를 옮기고 실행을 이어서 합니다
태스크 입장에서는 누락되는 코드 없이 그대로 이어서 프로그램이 계속 실행되는 것입니다

'RTOS' 카테고리의 다른 글

동기화  (0) 2022.10.22
이벤트와 메시징  (0) 2022.10.22
Task  (0) 2022.10.22
인터럽트  (0) 2022.10.22
HAL  (0) 2022.10.22