1. Real-Time Kernel Concepts
우선 real-time system은 다음과 같은 두가지로 나뉜다.
SOFT real-time system
System이 task를 가능한 빨리 수행하나 반드시 명시된 시간안에 끝마칠 필요는 없는 system
HARD real-time sysem
Task들이 정확히 명시된 시간안에 끝나야 하는 system
본서는 SOFT real-time system을 가정한다.
1.1 Critical Section of Code
우리가 코드의 일정 부분을 critical section으로 나눈다는 말은 critical section이 수행될 때에는 interrupt가 disable됨으로써 발생하지 않도록 한다는 의미이다.
1.2 Task
* 하나의 task는 priority, 자신의 register set, stack area를 가진다.
* 각각의 task는 주로 무한루프로 구성되며 다음과 같은 6가지 states를 가질 수 있다.
① DORMINANT : 메모리 상에는 존재하나 커널이 사용할 수 있는 상태는 아닌 것.
② READY : 실행될 수 있으나 현재 실행중인 task보다 priority가 낮아 실행되지 못하고 있는 상태
③ RUNNING : 실행되고 있는 상태.
④ DELAYED : 일정 시간이 지나기 까지 task가 멈추어 있는 상태
⑤ WAITING FOR AN EVENT : 말그대로
⑥ INTERRUPTED : 말그대로
1.3 Context Switch or Task Switch
* Multitasking kernel이 다른 task를 run 시키고자 할 때 현재 running 중인 task의 context(=CPU registers)를 task's context storage area에 저장한다. 그리고 나서 새로운 task의 context를 storage area에서 가지고 와서 새로운 task를 실행한다.
* Context switching은 application에 부하를 주게 되며CPU 레지스터 가 많을수록 더욱 많은 부하를 주게 된다.
1.4 Kernel
* Kernel은 task의 관리를 위해 CPU time을 조절하고, task간의 통신을 책임지는 부분으로서 가장 기본적인 일이 context switching 이다.
1.5 Scheduler
* Dispatcher라고도 하며 kernel의 한부분으로서 다음에 수행될 task를 결정하는 곳이다. 대부분의 real-time kernel은 priority를 기반으로 한다. 즉 중요도에 따라 각 task에 priority를 배정하고 가장 높은 priority를 가진 task가 run을 위한 ready 상태에 들어가는 것이다. 가장 높은 priority를 가진 task가 언제 CPU를 차지하느냐(running)는 다음 두가지 형태에 따라 다르다. 그 두가지는 non-preemptive이고 또한가지는 preemtive이다.
1.6 Non-Preemptive Kernel
* 말그대로 특정 Task가 현재 Running 중인 Task를 제치고 CPU를 선점할 수 없다. 즉 현지 진행중인 task가 알아서 CPU의 제어권을 포기 하기 전까지는 그 Task는 계속 작동한다. (Priority가 높은 Task가 Ready상태이더라도 기다려야 한다.)단 인터럽트가 발생하면 현재 진행중인 Task는 중단하고 해당 ISR(Interrupt Service Routine)을 실행한다.
1.7 Preemptive Kernel
* UC/OS는 preemtive kernel이다.
* 현재 running중인 task보다 priority가 높은 task가 ready상태가 되면 당연히 현재 진행중인 task는 preempted(suspended)된다. priority가 높은 task가 running을 끝내면 멈추었던 task는 다시 running을 시작한다.
* Interrupt가 발생하고 이 interrupt에 의해 suspeded된 task보다 더 높은 task가 생성되면 ISR이 수행된후 가장높은 priority를 가진 task(interrupt에 의해 발생한)가 running을 하면 이 과정이 끝나고 suspended 된 task가 가장 높은 priority를 가질경우 running을 시작한다.
아래 그림을 보면 이해가 쉬울 것이다.
그림 1. Preemptive vs Non-Preemptive Scheduling
1.8 Reentrancy
* Data의 손상없이 여러 task에 의해 사용될 수 있는 function을 reentrancy function이라고 한다.
* Preemptive Kernel에서는 배타적 접근이 보장되지 않는한 reentrancy function을 사용해야한다.(p10)
1.9 Round Robin Scheduling
* 같은 priority를 가지는 task가 여럿있을때 일정 시간이 되면 그 task가 일을 다 끝내지 못해도 다음 task로 넘어가는 방식. Time slicing이라고도 한다.
* UC/OS를 Round Robin 방식을 지원안한다. 한 priority에 한 task만을 설정할 수 있다.
1.10 Priority
* 각각의 task에는 priority가 배정된다. 또 priority를 프로그램 실행중(run time 중에)에 변화시킬 수 있는 경우와 그렇지 못한 경우가 있는데 전자를 dynamic priority 후자를 static priority라고 한다.
* UC/OS는 dynamic priority를 지원한다.
1.11 Semaphores
* 세마포의 용도는 다음과 같이 세가지로 요약될 수 있다.
a) Shared resource에 대한 access 제어(Mutual Exclusion)
b) Event 발생에대한 signal
c) 두 task의 synchronize
* Semaphore에는 Bianary(즉 1 or 0)과 counting semaphore 이렇게 두가지가 있다. 말 그대로 counting semaphore는 0~255, 또는 65535등의 값을 갖는다.
* Semaphore는 INITIALIZE(=CREATE), WAIT(=PEND), SIGNAL(=POST)의 세가지 동작이 있다.
* Semaphore를 받기를 원하는 Task는 WAIT(PEND)상태로 들어간다. Semaphore의 사용이 가능하다면(즉 Semaphore의 count가 0보다 크다면) count는 하나 감소하고 task는 실행을 한다. 만약 count가 0이라면 task는 WAIT 상태를 유지하면서 waiting list에 semaphore를 등록한다. 보통 이때부터 timer가 동작하기 시작하여 정해진 시간이 지나도 semaphore를 동작할 수 없으면 task는 run 상태로 들어가며 error message를 return한다.
* Task는 SIGNAL operation을 통해 Semaphore를 놓아준다. 만약 이 Semaphore를 기다리는 Task가 없다면 count는 증가하고, 기다리는 task가 있다면 그 task는 run 상태로 되며 count는 증가하지 않는다.
* 만일 여러 task가 기다리고 있다면
a) 가장 prioirty가 높은 task가 semaphore를 받거나
b) 가장 먼저 semaphore를 요구한 task가 semaphore를 받는다.
UC/OS는 가장 priority가 높은 task가 semaphore를 받는다.
1.12 Mutual Exclusion
* 말그대로 상호 배제이다. 예를 들어 하나의 프리터(리소스)에 두개의 task가 접근한다고 하자. 만일 하나의 task는 "나는 김치를"이라고 인쇄하고 또하나의 task에는 "사랑하다"라고 하면 프린터는 "나는 김치를 사랑한다"라고 인쇄한다. 이를 방지하기 위해 하나의 task만이 프린터(리소스)에 접근할 수 있게 해야 한다. 이를 mutlal exclusion이라고 하며 이를 실현하기위해 semaphore를 많이 사용한다.
* 보통 우리는 이 세마포를 발생시키고 보내는 함수를 만들어 필요시 이 함수를 사용함으로서 겉으로는 semaphore가 보이지 않도록 하는데 이를 encapsulation이라고 한다.
좀더 생각해보면 device driver에서 이 semaphore를 많이 사용하리란 것을 예측할 수 있다.
* 다음과 같이 program을 작성할 수 있을 것이다.
UBYTE CommSendCmd(char *cmd, char *response, UWORD timeout)
{
Acquire port's semaphore;
Send command to device;
Wait for response(with timeout);
if(timeout)
{
Release semaphore;
return(error code);
}
else
{
Release semaphore;
return(no error);
}
}
쉽게 말해 세마포를 얻어야만 device에 접근할 수 있는 것이다. 또한 명령을 수행한 후에는 semaphore를 release한다. 만일 다른 task가 접근을 시도할 때에는 semaphore가 사용중이므로 그 task는 suspend된 상태로 있을 것이다. 물론 이때에는 bianary semaphore가 사용된다.
* 만일 resource에 여러 task가 접근할 수 있다면 counting semaphore를 사용한다.
1.13 Deadlock(or Deadly Embrace)
* 만약 Task T1이 Resource R1을 사용하고 있고, T2가 R2를 사용하고 있다고 하자. 이제 T1은 R2를 T2는 R1을 사용하고자 한다. 그러나 두 resource가 모두 사용중이므로 두 task는 동시에 정지하게 된다. 이를 피하기 위해서는 task가 시작되기 전에 필요한 모든 리소스를 획득하는 것이다.