본문 바로가기

Firmware/RTOS

임베디드 OS 개발 프로젝트 18(뮤텍스) - 마지막 장

https://github.com/KJT9109/RTOS_Study

임베디드 OS 개발 프로젝트 진행하면서 TEST 한 소스는 위에 정리해 놓았다

.

├── boot
│   ├── Entry.S
│   ├── Handler.c
│   ├── Main.c
│   └── main.h
├── 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
│   ├── stdbool.h
│   └── stdint.h
├── kernel
│   ├── event.c
│   ├── event.h
│   ├── Kernel.c
│   ├── Kernel.h
│   ├── msg.c
│   ├── msg.h
│   ├── synch.c
│   ├── synch.h
│   ├── task.c
│   └── task.h
├── lib
│   ├── armcpu.c
│   ├── armcpu.h
│   ├── stdio.c
│   ├── stdio.h
│   ├── stdlib.c
│   └── stdlib.h
├── Makefile
└── navilos.ld

책에 마지막 프로젝트인 뮤텍스에 대해 TEST를 해본다.

 

뮤텍스는 이전 글에 실험했었던 바이너리 세마포어랑 동작은 똑같다.

 

이전 글에서 다시 보면

 

세마포어는 공유 자원에 화장실 처럼 칸막이와 문을 설치한 다음 그 문에 맞는 열쇠(sSem:코드)를 만들어

열쇠를 가지고 있는 Task 만이 공유자원을 사용할 수 있다.

 

라고 썼다.

 

여기서 뮤텍스는 세마포어랑 개념이 비슷한데 조금 다른 점은 "문을 잠금 사람의 열쇠로만 다시 문을 열 수 있다." 

 

이전 글에 세마포어는 바이너리 세마포어라서 문을 잠그기만 하면 다른 다시 열 때 까지 문을 열수 없었지만

 

만약 바이너리 세마포어가 아닌 열쇠(Ssem:코드)가 2개 or 3개 이상으로 설정 하였을 때 " 1번 Task가 sSem을 -- 하여

문을 잠궜어도 sSem 이 1이상이라면 다른 Task가 열 수 있다."

 

그런데 뮤텍스에서는 위 작업이 되지 않는다. 즉 뮤텍스는 바이너리 세마포어에 소유의 개념을 더한 동기화 알고리즘이다.

 

뮤텍스를 사용하기 위해 다음과 같은 구조체를 추가하였다.

typedef struct KernelMutext_t
{
    uint32_t owner;
    bool     lock;
} KernelMutext_t;

/*********** TASK init**********************/

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;
    new_tcb->TaskId = sAllocated_tcb_index -1; //Kernel Mutex ID 

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

    return (sAllocated_tcb_index - 1); 
}

 

여기서 owner는 소유자(Task) 의 ID를 뜻하고 lock 은 문이 잠겨 있는지, 열려있는지를 뜻한다.

 

예전 글에 Task를 만들 때 사용했던 new_tcb -> TaskId가 여기에 속한다.

 

 

Kernel_mutex_init(void)

static KernelMutext_t sMutex;

void Kernel_mutex_init(void)
{
    sMutex.owner = 0;
    sMutex.lock = false;
}

 뮤텍스를 사용하기 위해 sMutex를 선언한다음 멤버를 0, false로 초기화 한다.

 

Kernel_mutex_lock(uint32_t owner)

bool Kernel_mutex_lock(uint32_t owner)
{
    if (sMutex.lock)
    {
        return false;
    }

    sMutex.owner = owner;
    sMutex.lock = true;
    return true;
}

공유 자원을 사용하기 전 lock을 거는 함수이다.

 

세마포어에 사용했던 알고리즘과 비슷한데 여기서 중요한건 sMutex.owner 를 지정 해주고 lock을 건다는 점이다.

 

Kernel_mutex_unlock(uint32_t owner)

bool Kernel_mutex_unlock(uint32_t owner)
{
    if (owner == sMutex.owner)
    {
        sMutex.lock = false;
        return true;
    }
    return false;
}

 공유 자원을 사용 후 걸려있던 lock을 푸는 함수이다.

 

여기서 중요한 코드는 owner 와 sMutex.owner가 같은 지 확인 후 lock 을 푼다는 점이다

 

Task 마다 ower id는 다르므로 만약 다른 Task 에서 lock을 풀려고 하면 false를 return 하여 코드 처리한다.

 

Kernel_lock_mutex(void)

void Kernel_lock_mutex(void)
{
    while(true)
    {
        uint32_t current_task_id = Kernel_task_get_current_task_id(); //함수 호출한 Task의 id를 불러온다.
        if (false == Kernel_mutex_lock(current_task_id))
        {
            Kernel_yield();
        }
        else
        {
            break;
        }
    }
}

Kernel_task_get_current_task_id를 호출하여 현재 돌아가고 있는 Task 의 ID를 return 한다.

 

return 한 ID로 Kernel_mutex_lock의 인자로 사용한다.

 

Kernel_unlock_mutex(void)

void Kernel_unlock_mutex(void)
{
    uint32_t current_task_id = Kernel_task_get_current_task_id();
    if (false == Kernel_mutex_unlock(current_task_id))
    {
        Kernel_yield();
    }
}

 Kernel_lock_mutex(void) 함수와 동일하다. 다만 lock 되신 unlock을 호출 하여 lock을 해제 한다.

 

만약 ID가 맞지 않아 해제가 되지 않을 경우 Kernel_yield를 호출 하여 Context switching을 한다.

 

Mutex_critical_section(uint32_t p, uint32_t taskId)

static void Mutex_critical_section(uint32_t p, uint32_t taskId)
{
    Kernel_lock_mutex();

    debug_printf("Mutex 1st Task #%u Send=%u\n", taskId, p);
    shared_value = p;
    Kernel_yield();
    delay(1000);
    debug_printf("Mutex 2nd Task #%u Shared Value=%u\n", taskId, shared_value);

    Kernel_unlock_mutex();
  
}

 Mutex 동기화 알고리즘이 들어간 Critical section 이다.

 

세마포어때 사용했던 critical section 과 비슷한데 다만 lock을 걸 때 세마포어가 아닌 Kenel_lock_mutex를 사용하여 lock을 걸었다.

 

Mutex_critical_section 함수를 호출 할때 같은 Task에서 Lock을 풀지 않는 이상 공유자원에 접근 할 수 없다.

 

Task#1에서는 m이라는 글자를 입력 시 mutex lock을 걸게 작업하였고

Task#3 과 Task#4에서 Mutex_critical_section을 호출하여 lock을 걸었을 때 공유자원에 접근 할 수 있는지 Test 하였다.

 

void User_task0(void)
{
    uint8_t  cmdBuf[16];
    uint8_t  recvBuf[16];
    
    uint8_t recv_length = 0;
    uint8_t recv_index = 0;
    uint8_t fail_index = 0;

    while(true)
    {
        debug_printf("User Task #0\n");
        KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn|KernelEventFlag_MutexLock);
        switch(handle_event)
        {

       /**...  코드가 길어 임의로 생략...**/

            case KernelEventFlag_MutexLock:  //m 을 호출 시 Mutex 동기화 이벤트 발생
                Mutex_critical_section(0,0);
            
            break;

        }
        
        delay(1000);
        Kernel_yield();
    }
}

//...... 생략..............

void User_task3(void)
{
    int32_t local = 0;

    while(true)
    {
        debug_printf("User Task #3\n", &local);
        //Test_critical_section(3,3);
        Mutex_critical_section(3,3);
        delay(1000);
        Kernel_yield();
    }

}


void User_task4(void)
{
    int32_t local = 0;

    while(true)
    {
        debug_printf("User Task #4\n", &local);
        Mutex_critical_section(4,4);
        delay(1000);
        Kernel_yield();
    }

}

 

 

실행 화면

Task#3에서 Mutex lock을 건 후 share value를 3으로 수정후 unlock 하였다.

 

그 이후 Task#4 에서 다시 mutex lock을 건 후 value를 4로 수정 하였고, lock을 풀지 않은 채로 context switching 하여

Task#0,Task#3에서 공유 자원에 접근 하였지만 접근하지 못하고 User Task#? log만 남기고 다시 스위칭 되었다.

 

다시 Task#4로 왔을 때 unlock을 하자 Task#0에서 바로 lock을 건 후  공유자원을 0으로 변경 후 출력 된 화면이다.

 

 

 

 

--기록--

13장을 끝으로 책에 모든 예제를 돌려보고 좀 더 추가하여 구현 해보았다.

가장 기억에 남는 건 책에서 구현이 힘들꺼라 했던 priority algorithm을 직접 구현 했던 게 기억에 남는다.

 

해보면서 느꼈던 건 역시 기초.. 책에서 가장 중요한 단원을 꼽으라 하면 1장 ~ 13장이 아닌 부록 A 인 거 같다.

 

부록 A에 나오는 기초지식은 ARM에 모두 공통으로 적용 되는 지식으로 RTOS 뿐만 아니라 Non OS 에서 

펌웨어 구현 할 때 많은 도움이 될듯 하다. 

 

그래도 단원에서 꼽으라 하면 1장 ~ 4장 펌웨어에 기초라 할 수 있는 부팅 과정 과 메모리 설계......

 

왜 부팅은 어셈블리로 밖에 구현 할 수 밖에 없는지 직접 코드를 들어가보면 이해할 수 있다.