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장 펌웨어에 기초라 할 수 있는 부팅 과정 과 메모리 설계......
왜 부팅은 어셈블리로 밖에 구현 할 수 밖에 없는지 직접 코드를 들어가보면 이해할 수 있다.
'Firmware > RTOS' 카테고리의 다른 글
임베디드 OS 개발 프로젝트 17(세마포어) (0) | 2020.03.20 |
---|---|
임베디드 OS 개발 프로젝트 16(메시징-2) (0) | 2020.03.16 |
임베디드 OS 개발 프로젝트 15(메시징) (0) | 2020.03.15 |
임베디드 OS 개발 프로젝트 14(Event 처리) (0) | 2020.03.11 |
임베디드 OS 개발 프로젝트 13(Context Switching - Priority) (0) | 2020.03.10 |