본문 바로가기

Firmware/RTOS

임베디드 OS 개발 프로젝트 17(세마포어)

.
├── 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 중인 QEMU 환경에서는 CORE가 1개로 실행 되기때문에 동기화 알고리즘을 사용을 억지로 실행 해볼 예정이다.

 

세마포어란 여러 쓰레드, RTOS 환경에서 TASK 들 끼리 같은  공유 자원(Share resource)을 사용할 때 서로 충돌을 방지하기 위해 사용하는 알고리즘이다.

 

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

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

 

예제코드에서는 binary 세마포어를 사용한다.

 

synch.c

Kernel_sem_init()

static int32_t sSemMax;
static int32_t sSem;

void Kernel_sem_init(int32_t max)
{
    sSemMax = (max <= 0) ? DEF_SEM_MAX : max; //(max <= 0) 이 참이면 sSemMax에 DEF_SEM_MAX을 할당 아니면 max를 할당
    sSem = sSemMax;
}

 

위에 언급한 열쇠의 갯수를 정한다.

 

만약 열쇠(sSem)의 갯수가 0 보다 작거나 같으면 MAX 값인 8로 설정한다.

 

 

Kernel_sem_check()

bool Kernel_sem_check(void)
{
    if (sSem <= 0)
    {
        return false;
    }

    sSem--;

    return true;
}

열쇠가 있는지 체크한다.

만약 열쇠가 없으면 false를 return 하여 열쇠가 없음을 알리고 만약 열쇠가 있으면 열쇠를 가지고 가기때문에 

sSem-- 하여 열쇠를 -1 해주고 true를 return 한다.

 

Kernel_sem_realease()

void Kernel_sem_release(void)
{
    if (sSem >= sSemMax)
    {
        sSem = sSemMax;
    }

    sSem++;
}

열쇠를 반납한다.

열쇠가 max값보다 크면 max 값과 동일하게 만든 후 +1을 한다.

 

Kernel_lock_sem()

void Kernel_lock_sem(void)
{
    while(false == Kernel_sem_check())
    {
        Kernel_yield();
    }
}

 

앞서 언급했던 kernel_sem_check에서 false를 return할 시 kernel_yield를 호출 하여 강제로 Context switching 한다.

 

kernel_unlock_sem()

void Kernel_unlock_sem(void)
{
    Kernel_sem_release();
}

Kernel_sem_realese를 호출하여 lock 을 푼다.

 

Test_critical_section()

static uint32_t shared_value;
static void Test_critical_section(uint32_t p, uint32_t taskId)
{
    Kernel_lock_sem();

    debug_printf("section Task #%u Send=%u\n", taskId, p);
    shared_value = p;
    Kernel_yield();
    delay(1000);
    debug_printf("Recive Task #%u Shared Value=%u\n", taskId, shared_value);

    Kernel_unlock_sem();
}

 

공유 자원을 전역변수로 선언하여 critical_section 함수 안에 활용한다.

 

함수 안에서는 세마포어 lock 을 사용한다음 debug_printf 를 활용하여 share_value값을 확인한다.

 

그 다음 kernel_yield를 호출하여 Context Switching을 한다.

 

이때 다른 Task에서 Critical_section을 호출하여 shared_value 값을 바꿀려고 해도 lock 이걸려있어 share_value 값을 바꾸지 못하고 다시 Context switching을 한다.

 

이번에 사용한 예제 코드의 알고리즘이다.

지금까지 사용했던 Task는 유지하고 Task#2 와 Task#3을 사용했다.

 

Task#2에서는 새로 추가된 semaphore 이벤트가 set 되었을 시 Critical Section에 진입하며

 

Task#3에서는 호출 될때마다 Critical Section에 진입한다.

 

여기서 주목할 block은 Critical Section인데  Task#2에서는 공유자원값을 5로 설정하고

 

Task#3에서는 공유자원 값을 3으로 설정한다.

 

아래 실제 Task#2, Task#3 코드이다.

void User_task2(void)
{
    int32_t local = 0;

    while(true)
    {
        debug_printf("User Task #2\n", &local);
        KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_Semaphore);
        switch(handle_event)
        {
            case KernelEventFlag_Semaphore:
                debug_printf("event semaphore\n");
                Test_critical_section(5,2);
            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);
        delay(1000);
        Kernel_yield();
    }

}

 앞서 올린 순서도를 이해 했다면 위 코드는 어렵지 않을 것이다.

 

 

Uart.c

Interrupt_handler()

static void interrupt_handler(void)
{
    uint8_t ch = Hal_uart_get_char();
    Hal_uart_put_char(ch);
    Hal_uart_put_char('\n');
    
    Kernel_send_msg(KernelMsgQ_Task0,&ch,1);
    Kernel_send_events(KernelEventFlag_UartIn);
    if(ch =='s')
        Kernel_send_events(KernelEventFlag_Semaphore);
}

 

Semaphore event는 interrupt에서 s라는 문자가 왔을 시 보낸다.

 

실행화면

Task#3에서 Critical Section에 진입하여 lock 걸고 Context switching 하였을 때

 

's' 문자를 입력하여 Tesk#2에서 Critical Section에 진입하여 Shared value 값을 5로 수정할려고 하였으나

 

lock으로 인해 변경하지 못하고 Context switching 하였으며

 

unlock 되자 Task#2에서 다시 진입하여 5로 변경 후 출력 되는 화면이다.