본문 바로가기

Firmware/RTOS

임베디드 OS 개발 프로젝트 11(Scheduler)

.
├── 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

 

스케줄러란 Task가 실행하고 있을 때 다음에 실행할 Task가 무엇인지 골라준다.(9장 참조)

 

1. Round Robin Algorithm Scheduler

 

책에서 소개된 첫번째 스케줄러는 round robin algorithm scheduler 이다.

 

디게 간단한데 그냥 추가 계산 없이 그냥 인덱스를 계속 증가 시키면서 대상을 선택하는 알고리즘이다.

 

여기서 중요한 점은 인덱스가 끊임없이 증가하는 것이 아니라 정해진 최댓값에 이르면 다시 0이되어 계속 돌아간다.

 

예를 들어 Task Control Block이 현재 3까지 할당 되어 있고 4는 비어 있는 인덱스 상태에서 round robin algorithm을 

 

적용하면 index 숫자는 3까지 커지고 다시 0이 된다.

 

이렇게 숫자가 커지다가 맨 처음으로 돌아가는 과정을 반복한다.

 

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

    return &sTask_list[sCurrent_tcb_index];
}

우선 sCurrent_tcb_index 변수를 하나 만든다. ( Kernel_task_init 함수에서 0으로 초기화)

 

sAllocated_tcb_index는 이전 Kernel_task_create 함수에서 task를 만들 때 사용했으며 sAllocated_tcb_index 값은 현재 만들어진 Task의 개수이다.(-1)

 

함수내용은 간단하다.  sCurrent_tcb_index 변수를 ++ 하여 sTask_list 배열에 사용하여 계속 다음 Task가 실행 하도록한다.

 

다만 만들어진 Task의 수를 넘으면 안되므로 sAllocated_tcb_index 와 나머지 연산을 하여 값이 같아지면 0으로 바뀌어

 

계속 돌아가게 한다.

 

2. Priority Scheduler

 

책에서는 우선순위 스케줄러를 만들지 않는다고 한다.

 

에뮬레이터라는 qemu의 제약으로 적당한 테스트 케이스를 만들기는 어렵기 때문이란다.

 

간단히 소개하면,

 

테스크를 생성할 때 개발자가 테스크에 적당한 우선순위를 설정해서 값을 지정해야한다.

 

따라서 Task Control Block 에 변수 하나를 추가하여 우선순위를 부여한다.

 

typedef struct KernelTcb_t
{
    uint32_t sp;
    uint8_t* stack_base;
    uint32_t priority;
}KernelTcb_t;


uint32_t Kernel_task_create(KernelTaskFunc_t startFunc, uint32_t priority)
{
    KernelTcb_t* new_tcb = &sTask_list[sAllocated_tcb_index++];

    if (sAllocated_tcb_index > MAX_TASK_NUM)
    {
        return NOT_ENOUGH_TASK_NUM;
    }

    new_tcb->priority = priority;

    KernelTaskContext_t* ctx = (KernelTaskContext_t*)new_tcb->sp;
    ctx->pc = (uint32_t)startFunc;

    return (sAllocated_tcb_index - 1);   //29line에서 ++해줬으므로 다시 -1 해준다.
}

 

Kernel_task_create 함수에 argument로 priority를 추가해준다.

 

추가한 priority 변수를 parameter로 받고 그대로 tcb에 저장한다.

 

static KernelTcb_t* Scheduler_priority_algorithm(void)
{
    for(uint32_t i = 0; i < sAllocated_tcb_index; i++)
    {
        KernelTcb_t* pNextTcb = &sTask_list[i];
        if (pNextTcb != pNextTcb)   
        {
            if (pNextTcb -> priority <= sCurrent_tcb -> priority);  //다음 tcb 우선순위 >현재 우선 순위
            {
                return pNextTcb;   //다음 tcb 우선순위가 높으므로 nextTcb를 return한다.
            }
        }
    }

    return sCurrent_tcb; //nextTcb 우선순위가 낮으므로 현재 tcb가 계속 실행 되도록 return한다.
}

할당된 Task Control block을 하나씩 읽어서 priority 멤버 변수를 비교한다.

 

여기서 우선순위 숫자가 작을 수록 우선순위가 높다는 뜻이므로 <= 비교연산자를 사용했다.

 

for문을 사용하여 0부터 끝까지 모든 TCB를 비교하여 현재 사용중인 tcb와 비교하여 return한다.

 

 

※책에 sCurrent로만 되어 있어 make 오류가 뜬다.

다음과 같이 수정하면 컴파일이 된다.

 

if (pNextTcb -> priority <= sCurrent-> priority);

                                         ↓

if (pNextTcb -> priority <= sCurrent_tcb -> priority);