Linux 혹은 Windows 의 프로그램 혹은 프로세스로 봐도 됨. RTOS 에서는 Task 라고 부르는 것.
개별 태스크 자체를 추상화하는 자료 구조를 의미.
태스크는 운영체제에서 동작하는 프로그램 그자체이다. 태스크 간에 전환(Switching)이 이루어질때 현재 진행 중인 프로그램의 현재 상태 정보를 기록하고 있어야 한다.
프로그램의 현재 상태 정보를 Context 라고 한다.
ARM의 SPR 레지스터와 범용 레지스터를 구조체로 추상화하고, SP 와 SP Base 를 구조체로 추상화한다.
kernel/task.h
1 #ifndef KERNEL_TASK_H_
2 #define KERNEL_TASK_H_
3
4 #include "MemoryMap.h" //TASK_STACK_SIZE 를 사용하기 위해서 include 하였음.
5
6 #define NOT_ENOUGH_TASK_NUM 0xFFFFFFFF
7 #define USR_TASK_STACK_SIZE 0x100000 // 1 MB allocated for a task
8 #define MAX_TASK_NUM (TASK_STACK_SIZE/USR_TASK_STACK_SIZE) // total stack allocated for task = 64 MB / stack allocated for a each task = 1 MB -> 64 개의 Task 동작 가능
9
10 typedef struct KernelTaskContext_t{ //프로그램 컨텍스트 저장
11 uint32_t spsr; //프로그램 상태 레지스터 R
12 uint32_t r0_r12[13]; //USR & SYS 의 범용 레지스터 R0~R12
13 uint32_t pc; //R15, Program Counter
14 } KenrelTaskContext_t;
15
16 typedef struct KernelTcb_t { //프로그램 스택 관련 정보 저장
17 uint32_t sp; //Stack Pointer
18 uint8_t *stack_base;
19 } KernelTcb_t;
20
21 typedef void (*KernelTaskFunc_t)(void);
22
23 void Kernel_task_init(void);
24 uint32_t Kernel_task_create(KernelTaskFunc_t startFunc);
25
26 #endif
결국, 태스크 컨텍스트는 레지스터와 스택 포인터의 값이다. 즉, 태스크 컨텍스트 스위칭이란 코어의 레지스터 값을 다른 태스크의 것으로 바꾼다는 말과 같다.
동적 메모리 할당을 피하기 위해서 태스크 컨트롤 블록 어레이를 만들어서 관리.
kernel/task.c
1 #include "stdint.h"
2 #include "stdbool.h"
3
4 #include "ARMv7AR.h" //CSPR "M" Mode bit
5 #include "task.h" //Task contexts
6
7 KernelTcb_t sTask_list[MAX_TASK_NUM];
8 static uint32_t sAllocated_tcb_index; //생성한 태스크를 인덱스로 관리. 하나 추가할때마다 1씩 증가함.
9
10 void Kernel_task_init(void) {
11 sAllocated_tcb_index = 0;
12
13 for(int i = 0; i <= MAX_TASK_NUM; i++) {
14 sTask_list[i].stack_base = (uint8_t*)(TASK_STACK_START + i * USR_TASK_STACK_SIZE); //해당 태스크 스택 베이스 주소 설정
15 sTask_list[i].sp = (uint32_t)sTask_list[i].stack_base + USR_TASK_STACK_SIZE - 4; //스택은 거꾸로 내려감을 이용해서 스택 포인터 지정, 4 바이트씩 비움으로써 태스크간의 스택 경계를 표시
16
17 sTask_list[i].sp -= sizeof(KernelTaskContext_t); //태스크 컨텍스트를 스택에 저장하기 위함.
18 KernelTaskContext_t *ctx = (KernelTaskContext_t*)sTask_list[i].sp;
19 ctx->pc = 0;
20 ctx->spsr = ARM_MODE_BIT_SYS;
21 }
22 }
태스크 컨텍스트는 스위칭 작업에 의해 모두 레지스터로 복사되고 스택 포인터는 태스크 컨텍스트가 없는 위치로 이동하게 된다. 그래서 동작중인 태스크의 스택에는 태스크 컨텍스트가 존재하지 않게 된다.
여기서 sTask_list 는 static 변수로 만들면 안된다. 그렇게 되면 sAllocated_tcb_index와 sCurrent_tcb_index가 76545985 라는 숫자로 나옴.(인식이 제대로 안됨)
다른 방법으로는 sAlloc 와 sCur 를 전역 변수로 만드는 방법이 있긴함.
어쨋든, 이유는 모르겠으나 sTask_list 를 전역 변수로 만들면 모두 제대로 인식됨.
kernel/task.c
23
24 uint32_t Kernel_task_create(KernelTaskFunc_t startFunc) {
25 KernelTcb_t *new_tcb = sTask_list[sAllocated_tcb_index++];
26
27 if(sAllocated_tcb_index > MAX_NUM) {
28 return NOT_ENOUGH_TASK_NUM;
29 }
30
31 KernelTaskContext_t *ctx = (KernelTaskContext_t*)new_tcb->sp;
32 ctx->pc = (uint32_t)startFunc;
33
34 return (sAllocated_tcb_index - 1);
35 }
스택 관련 정보를 먼저 받아온 후, 현재 SP 에 태스크 컨트롤 블록이 저장되어 있음을 사용하여 ctx 생성
해당 PC 에 태스크 함수를 넣음으로써 함수를 태스크 컨트롤 블록에 등록, PC 는 다음에 실행할 명령어의 주소를 읽어오는 레지스터임.