본문 바로가기

Firmware/RTOS

임베디드 OS 개발 프로젝트 13(Context Switching - Priority)

tree 생략

 

책에서 10장까지 Round Robin Algorithm으로 구현했고

 

Priority algorithm 은 단순 소개로 넘어갔지만 개인적으로 한번 구현 해 보았다.

 

KernelTcb_t 구조체

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

 

 

우선순위 스케줄러 알고리즘을 구현 할 거기에 priority 와 TaskId를 추가한다.

 

Task Create 함수에 parameter로 priority를 받는다.

 

TaskId는 스케줄러 알고리즘을 통해 우선순위가 가장 높은 Task를 호출 하기 위해 사용한다.

(Priority 는 숫자가 가낭 낮을수록 우선순위가 높다)

 

Kernel_task_create

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

    if (sAllocated_tcb_index > MAX_TASK_NUM)
    {
        return NOT_ENOUGH_TASK_NUM;
    }

    new_tcb->priority = priority;
    new_tcb->TaskId = sAllocated_tcb_index -1;

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

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

Kernel_task_create 함수에서는 Priority 가 추가되었으며 생성 될때 argument로 값을 받는다.

 

받은 argument를 new_tcb -> priority 에 저장한다.

 

TaskId 는 sTask_list에서 자신이 몇번째 배열에 있는지 확인하는 변수이다.

 

Scheduler_priority_algorithm

//-------------------------변경 전 코드---------------------------------
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);
            {
                return pNextTcb;
            }
        }
    }
    return sCurrent_tcb;
}
//---------------------------------------------------------------------_

static KernelTcb_t* Scheduler_priority_algorithm(void)
{
    
    KernelTcb_t* pCurrentTcb = &sTask_list[0];
    
    for(uint32_t i = 1; i < sAllocated_tcb_index; i++)
    {
        KernelTcb_t* PrepareTcb = &sTask_list[i];
        
        if(pCurrentTcb != PrepareTcb)
        {   
            if(pCurrentTcb -> priority > PrepareTcb -> priority)
            {
                pCurrentTcb = PrepareTcb;
            }

        }
        else
        {
            putstr("Task Compare fail#01");
            while(true);
        }
    }
    //--------우선순위가 같을 시 RoundRobin algorithm 처럼 동작하기 위한 코드--------- 
    KernelTcb_t*  PrepareTcb = &sTask_list[Compare_tcb_index++];
    Compare_tcb_index %= sAllocated_tcb_index; //compare_tcb_index 값이 task_list의 maximum이 넘지 않게 나머지 연산을 해준다.

    if(pCurrentTcb -> priority == PrepareTcb -> priority)
    {
        pCurrentTcb = PrepareTcb;
    }

    sCurrent_tcb_index = (pCurrentTcb -> TaskId); //현재 돌고 있는  Task의 Tcb는 PrepareTcb로 바꿨으므로 TaskId도 수정한다.
    return pCurrentTcb;
}

 

변경전 코드를 보면 for 문을 통해 sTask_list 0번째 Task를 호출 한 다음 현재 실행하고 있는 Task의 우선순위와 비교해서 priority 가 같거나 낮으면 비교한 Task를 return 하여 실행한다.

 

이 같은 경우 sTask_list 의 전체 Task를 비교를 못하고 priority가 실행하고 있는 priority 보다 낮기만 하면 즉시 return 하기 때문에 Task가 많고 프로그램이 실행 중 우선순위가 변경 되었을 때 catch 하기 힘들 수 있어 알고리즘을 조금 변경하였다.

 

변경 후 코드를 보면 

 

pCurrentTcb를 sTask_list에 가장 첫번째 Task를 불러 온다.

 

for 문에서 다음 Task(PrepareTcb)를 호출한 다음, priority가 pCurrentTcb 보다 낮으면 pCurrentTcb를 PrepareTcb로 업데이트, 그렇지 않으면 pCurrentTcb는 유지하고 다음 Task(PrepareTcb)를 호출하여 또 비교한다.

 

이렇게 모든 Task를 비교하여 priority가 가장 낮은 task를 불러온 다음 priority 가 같은 Task가 있는지 Check 하여 

있으면 Task가 다음 scheduler를 호출 할때마다 순서대로 Switching을 하게 된다.

-> (모든 Task 순위가 똑같으면 Round Robin algorithm 으로 동작한다,)

 

Kernel_init

static void Kernel_init(void)
{
    uint32_t taskId;

    taskId = Kernel_task_create(User_task0,4);
    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,2);   //우선순위가 가장 낮음 Task#2
    if (NOT_ENOUGH_TASK_NUM == taskId)
    {
        putstr("Task2 creation fail\n");
    }

    taskId = Kernel_task_create(User_task3,2);   //우선순위가 가장 낮음 Task#3
    if (NOT_ENOUGH_TASK_NUM == taskId)
    {
        putstr("Task3 creation fail\n");
    }

    taskId = Kernel_task_create(User_task4,3);
    if (NOT_ENOUGH_TASK_NUM == taskId)
    {
        putstr("Task4 creation fail\n");
    }


    Kernel_start();

}

이전 글 Kernel_init에서 Task를 2개 더 추가하였다.

 

Task의 우선순위는 Task3이 가장 낮으므로 Scheduler를 위한 Task0이 처음에 실행 되고 Task 3만 계속 실행 된다.

 

 

만약 우선순위가 모두 같은 경우 RoundRobin 처럼 Task가 실행 된다.