컴퓨터공부/Embedded

MicroC/OS-II Porting to TMS320C31

achivenKakao 2006. 11. 19. 13:29

 

MicroC/OS-II Porting to TMS320C31
Kim Mose
Department of Electronic Engineering, INCOMING LAB, Sejong University, Seoul, Korea
(Email : shadow_dancer@hanmail.net)

Keywords: RTOS, MicroC/OS-II, uCOS, multitasking, porting, TMS320C31

1. RTOS란 무엇인가?

 RTOS는 Real Time Operating System의 약자로서, 일반적으로 사용되고 있는 윈도우즈(Windows) 나 유닉스(Unix)와는 다른 목적을 지닌 OS의 한 부류이다. 이들 RTOS들은 이름에서도 알 수 있듯이 빠른 응답성(실시간성)을 강조한다. 실제로 마이크로 프로세서나 컨트롤러로 직접 펌웨어를 제작해 본 경험이 있다면 RTOS에 대해 쉽게 이해할 수 있을 것이다. 간단히 스텝 모터와 광센서를 사용하여 마이크로 마우스(주어진 미로를 탐색하여 최단 시간 내에 통과하는 로봇)를 제어하는 프로그램을 짠다고 생각해 보자. 우선 센서와 연결된 포트로부터 외부 정보를 얻어오며 타이머 인터럽트를 사용하여 적절한 시간에 모터로 출력 신호를 인가할 것이다. 여기까지 본다면 굳이 OS의 일종인 RTOS를 쓸 필요를 못 느낄 수 있다. 실제로 이정도의 프로그램은 직접 구현하는 것이 오히려 시간적으로나 코드 사이즈 면에서 효율적일 것이다. 그러나 만약 여기에 센서 정보를 표시하기 위한 LCD를 단다고 하자. 또한 미로를 최단 시간 내에 통과할 수 있도록 미로 정보를 종합하여 최단 거리를 구하는 루틴까지 포함한다고 하면 이는 상당히 복잡한 문제가 될 것이다. 이처럼 기능이 늘어나면 늘어날수록 각 기능(루틴)들 간의 관계가 복잡해지며 이는 필연적으로 시스템의 응답과 확장, 수정, 이식, 시스템 자원의 효율적 이용 등에 큰 영향을 미칠 것이다. 그래서 RTOS는 이런 문제점들을 개선하기 위한 해결책으로 고안되었으며 기본 특성들은 윈도우즈 등의 일반 운영 체제를 기반으로 하고 있으나 강력한 오류 체크나 메모리 관리 등 처리에 많은 시간을 요하는 기능들을 제거 또는 축소하여 멀티태스킹을 구현할 수 있도록 최적화된 운영 체제로 요약할 수 있다.

2. MicroC/OS-II

 uC/OS는 Jean J. Labrosse가 만든 RTOS로서 특징으로는 1) 공개된 소스 코드, 2) 8, 16, 32 그리고 64비트 시스템으로의 높은 이식성, 3) 프로젝트에 따른 소스 코드의 절약, 4) 멀티태스킹(Multitasking), 5) 선점형(Preemptive) 방식 5) 높은 신뢰성과 안정성 그리고 마지막으로 6) 각종 커널 서비스 등을 들 수 있다[1]. uC/OS는 학교나 개인의 교육과 같은 비상업적 목적에 한해 자유로이 사용할 수 있으며 소스가 공개되어 있어 소스 코드의 수정 및 커널의 내부 구조를 이해하는데 이점이 있다. 물론 상업적인 목적에 사용될 경우에는 라이센스를 따로 얻어야 한다. 또한 다양한 프로세서나 컨트롤러, DSP 등에 사용하기 위해 범용적으로 설계되었으며 소스가 모듈별로 나뉘어져 있어 불필요한 기능일 경우 프로그램에 포함시키지 않음으로써 애플리케이션의 사이즈를 줄일 수 있다. 그러나 무엇보다도 uC/OS의 가장 큰 특징은 선점형 방식의 멀티태스킹이 가능하다는 점이다. 실재로 임베디드 시스템에 사용되는 RTOS들은 기능, 성능, 가격 면에서 매우 다양하다. 개인이 제작한 단순한 형태에서부터 시작하여 상용을 목적으로 다양한 기능 및 우수한 성능을 보장하는 고가의 운영 체제까지 그 종류는 여러 가지이다. 그러므로 앞서의 특징들을 포함하여 높은 신뢰성과 안정성을 보장하고 몇몇 중요한 커널 서비스들은 제공하는 RTOS로서는 매우 매력적인 조건이 아닐 수 없다[2].

2.1 멀티태스킹(Multitasking)

  멀티태스킹이란 동시에 여러 개의 작업(Task)이 수행되어지는 것으로 정의된다. 이 경우에는 병렬처리방식(Parallelism)처럼 2개 이상의 프로세서의 의해 다수의 작업이 수행되어지는 것이 아니라 단일 프로세서 상에서 각 작업들이 짧은(유효한) 시간을 할당받아 작업을 수행하고 할당된 시간이 지나면 다른 작업들에게 프로세서 자원을 양보함으로써 동시에 여러 개의 작업을 수행하는 병행처리(Concurrency)로서의 의미를 가진다.

Fig.1 Parallelism vs. Concurrency

 즉, 그림 1의 (a)에서와 같이 Task #1과 Task #2가 각각의 프로세서를 점유하여 각 작업을 수행하는 것이 아니라 (b)에서처럼 단일 프로세서를 틱(Tick)이라고 하는 특정 시간을 할당받아 자신의 작업을 수행하는 것이다. 이를 스위칭(Context Switching)이라 하며 RTOS를 실제 프로세서에 포팅하는데 가장 중요한 고려 사항이 된다. 물론 이런 특성들은 일반 OS들에도 공통된 점이기도 하다.

2.2 스케줄링(Scheduling)과 선점형(Preemptive) 방식

 이런 식으로 스위칭이 일어나게 되면 커널(OS에서 OS서비스 전체를 관리하는 핵심 부분)은 각 태스크들에게 틱을 배분해야하며 이를 스케줄링(Scheduling)이라고 한다. 이 스케줄링에는 여러 가지 방식이 있다. 가장 기본이 되는 것이 우선순위(Priority) 방식으로 각 태스크에 고유한 우선순위를 할당하여 순위가 높은 태스크가 가장 먼저 프로세서를 점유하여 작업을 수행하고 자신의 작업을 완료했거나 프로세서 자원을 사용할 필요가 없을 경우 그 다음으로 높은 우선 순위를 가진 태스크에 프로세서를 양보하는 방식이다. 이밖에 라운드 로빈(Round Robin) 방식과 FCFS(First-Come-First-Served) 방식이 있는데, 이는 각각 태스크에 동등한 시간을 할당하거나 또는 먼저 프로세서를 쓰고자 요청하였을 경우 우선적으로 프로세서를 할당하는 것이다. 실제 이 둘은 독립적으로 사용되어지기 보다는 우선순위 방식을 보완하는 식으로 많이 사용된다. uC/OS는 우선순위 방식만을 사용하며 총 64개의 우선순위를 가진다. 이중에 몇 개는 uC/OS에 의해 사용이 제한되어 있다. 우선순위 방식을 구현할 때 발생되는 문제 중의 하나는 만일 현재 프로세서를 점유하고 있는 태스크 보다 높은 우선순위를 가진 태스크가 프로세서를 요구한다면 언제 프로세서를 점유할 수 있는가이다. 이때 생각할 수 있는 경우가 현재 태스크(낮은 우선순위)가 작업을 완료하거나 또는 스스로 프로세서를 양보하여 다른 태스크(높은 우선순위)가 프로세서를 점유할 수 있도록 하는 것이고 다른 하나는 높은 우선순위를 가진 태스크가 낮은 우선순위의 태스크의 작업을 강제로 중지시켜 자신이 프로세서를 점유하는 것이다. 후자의 방식을 선점형(preemptive) 방식이라 하며 RTOS처럼 실시간으로 응답을 요구해야 하는 시스템에 적합한 방식이다. 예를 들어 화학 공정 시스템을 생각할 수 있는데, 평소에는 공정에 필요한 작업들을 우선순위가 낮은 태스크들이 수행하다가 폭발과 같은 긴급 상황시에는 이를 해결하는 높은 우선순위의 태스크가 우선적으로 이를 처리하는 것이다. 이렇듯 일각을 다투는 상황에서 비선점형 방식을 사용하게 된다면 현재 태스크의 진행 상황에 따라 이를 처리하는데 적절하지 못 할뿐만 아니라 심각한 문제로 발전되는 것을 쉽게 상상할 수 있을 것이다.

2.3 태스크 상태(Task States)

  각 태스크들은 커널의 스케줄링에 의해 그림 2에서와 같이 크게 Running, Ready 그리고 Blocked의 세 가지 상태를 가진다. Running은 태스크가 실제로 프로세서를 점유하여 자신의 작업을 수행하는 상태이고 Ready는 Running 상태의 태스크보다 낮은 우선순위를 갖는 태스크들이 프로세서를 쓰기위해 기다리는 상태이다. Blocked 상태는 자신의 필요 또는 현재 실행중인 태스크에 의해 강제적으로 프로세서로부터 배제된 상태이다. 즉 Ready의 경우에는 현재 Running 상태의 태스크가 프로세서를 양보할 경우 언제라도 프로세서를 점유하여 실행 가능하지만 Blocked는 자신을 Ready 상태로 변환시키기 전까지는 아무리 순위적으로 높더라도 프로세서를 점유할 수 없다. 커널은 태스크들을 이런 세 가지 상태로 구분(결정)하여 멀티태스킹을 구현한다[3].

Fig.2 Task States

3. TMS320C31

 TMS320C31(이하 C31)은 TI(Texas Instruments)사의 32비트 부동소수점 방식의 범용 DSP로서 TMS320C30에서 일부 기능이 제외된 저가형 모델이다. C31의 주요 특징으로는 1) 32비트 부동소수점 연산, 2) 수정된 하바드 구조, 3) 132핀 PQFP 패키지, 4) 최대 60MHz에서 30MIPS, 60MFLOPS의 처리속도, 5) 2개의 1K x 32비트 내부 RAM, 6) 16M x 32비트 외부 확장 메모리 영역, 7) 마이크로컴퓨터 모드와 마이크로프로세서 모드로의 동작, 8) 2개의 32비트 타이머, 9) 1개의 직렬 포트, 9) DMA Controller 등을 꼽을 수 있다[4]. 포팅에 사용된 C31의 환경으로는 30MHz의 외부 클럭과 마이크로컴퓨터 모드를 사용하였다.

4. Porting

 여기서 포팅할 uC/OS의 버전은 v2.04로서 제작자의 저서 MicroC/OS-II "The Real-Time Kernel"를 통해 구할 수 있으며, 그 이전 버전인 MicroC/OS v1.x를 사용할 경우에는 위 책의 10장 “Upgrading from uC/OS to uC/OS-II”를 참고할 필요가 있다. uC/OS는 사용자가 포팅을 쉽게 할 수 있도록 프로그램 구조를 그림 3에서처럼 프로세서 독립적인 부분(Processor-Independent)과 프로세서 기술(Processor-Specific) 부분 그리고 애플리케이션 기술(application-Specific)의 3부분으로 구분하였다. 실제로 프로세서에 포팅을 하기 위해서는 이 중에서 프로세서 기술 부분과 애플리케이션 기술 부분만을 고려하면 된다.

Fig.3 uC/OS-II Architecture

프로세서 독립 부분은 앞서 언급한대로 포팅을 위해 특별히 코드를 추가하거나 수정할 필요는 없다. 다만 편의상 uCOS_II.C에 구현 파일(OS_xxx.C)들이 모두 포함(including) 되어 있어 이곳에 Main 함수를 작성할 수 있으며 애플리케이션의 크기를 줄여야 할 경우에는 사용하지 않는 파일 부분을 삭제함으로써 전체 용량을 줄일 수 있다. 애플리케이션 기술 부분은 OS_CFG.H와 INCLUDES.H 파일로 구성되어 있다. 이 부분은 애플리케이션을 작성함에 있어 프로세서의 특성을 고려할 필요가 없으며 소프트웨어적으로 uC/OS의 특성을 기술하는 부분이다. OS_CFG.H에서는 애플리케이션에서 사용 가능한 최대 태스크 수와 각종 이벤트 수 그리고 사용하고자 하는 커널 서비스들을 결정하며 INCLUDES.H에서는 사용자가 작성한 외부 파일들을 재차 포함(including)시킴으로써 다른 uC/OS 파일들을 수정하는 번거로움을 막고 파일 수정에 따른 원형 훼손 등의 문제점들을 예방한다. 여기까지는 기본적으로 uC/OS에 코드들이 작성되어 있는 상태이므로 포팅을 처음 하는 경우라면 이 곳을 건드릴 필요 없이 프로세서 기술 부분만 수정하여 테스트하기를 권장한다. 즉, uCOS_II.C에 Main 함수와 기타 사용자 정의 함수(User-defined Function)만 작성하면 된다(기본적으로 생성 가능한 태스크 수가 12로 설정되어 있으므로 태스크 수가 많을 경우 수정해줘야 한다. OS_LOWEST_PRIO는 가장 낮은 우선순위로서 IDLE 태스크에 할당되므로 실제 사용자가 사용할 수 있는 태스크는 최대 10개 이다. 11은 Statistic 태스크로 예약되어 있다).

ex) #define OS_MAX_TASKS      11
ex) #define OS_LOWEST_PRIO   12

프로세서 기술 부분은 프로세서와 uC/OS를 상호 연결하는 가장 핵심적인 부분으로 프로세서의 특성에 맞게 코드를 추가하거나 수정해줘야 한다. 그러므로 이 단계를 진행하기 위해서는 먼저 프로세서의 구조와 특성, 특히 어셈블리어를 충분히 숙지하고 있어야 한다. 비록 uC/OS가 C 언어로 작성되어 있지만 C 언어의 구조적 한계로 - C 코드들은 최종적으로 컴파일러에 의해 정형화된 기계어(또는 어셈블리어)로 번역되며, 이런 정형성에 의해 안정성은 보장되나 구현의 자유도는 떨어진다. - 인하여 프로그램을 프로세서에 맞게 구현할 수 없기 때문이다. 다만 여기서는 편의상 인라인 어셈블리어를 통하여 구현하였다.

4.1 OS_CPU_H

 OS_CPU.H 파일에서는 uC/OS에서 사용하는 10개의 변수 타입과 4개의 매크로를 설정한다. 일반적으로 프로세서나 컴파일러에 따라 변수 타입들의 크기가 다르며 이는 범용적인 포팅을 어렵게 한다. uC/OS는 이런 문제점을 해결하기 위하여 INT8U와 같은 자체 변수 타입을 사용하며, 사용자는 typedef 명령어를 통해 이를 적절히 정의해 준다. C31에서는 다음과 같이 정의한다.

typedef unsigned char    BOOLEAN;
typedef unsigned char    INT8U;
typedef signed char        INT8S;
typedef unsigned short   INT16U;
typedef signed short      INT16S;
typedef unsigned int       INT32U;
typedef signed int          INT32S;
typedef float                  FP32;
typedef double               FP64;
typedef unsigned char    OS_STK;

4개의 매크로에는 각각 OS_STK_GROWTH와 OS_ENTER_CRITICAL, OS_EXIT_CRITICAL, OS_TASK_SW 등이 있다. OS_ENTER_CRITICAL과 OS_EXIT_CRITICAL 매크로는 크리티컬 섹션을 구현하기 위해서 전체 인터럽트(GIE)를 On/Off 시키며 OS_TASK_SW는 태스크 스위칭이 일어나도록 OSCtxSW 함수(이 함수는 다음 절의 OS_CPU_A에서 정의해준다)를 호출한다. 마지막으로 OS_STK_GROWTH는 프로세서에서 스택의 증가 방향을 지정하는 매크로로서 1일 때는 하향 증가, 반대로 0일 때는 상향 증가를 나타낸다. C31은 스택 포인터가 상향 증가식(즉, 증가)이므로 0으로 지정한다.

#define OS_ENTER_CRITICAL();    enterCri();
#define OS_EXIT_CRITICAL();       exitCril();
#define OS_STK_GROWTH           0
#define OS_TASK_SW();              OSCtxSw();

void enterCri(){
  /* simply GIE = 0; */
  asm(" OR IE, AR7");
  asm(" LDI 0H, IE");
}

void exitCri(){
  /* simply GIE = 1; */
  asm(" LDI AR7, IE");
  asm(" LDI 0H, AR7");
}

4.2 OS_CPU_C 와 OS_CPU_A

 OS_CPU_C와 OS_CPU_A는 uC/OS 포팅에 있어 가장 핵심적인 파일들이다. 이 두 파일에는 컨텍스트 스위칭에 관련된 함수들이 정의되어 있는데 각 함수가 구현되는 프로그래밍 레벨에 따라 C 소스 파일과 ASM 소스 파일로 구분된다. 또한 이 두 파일에는 총 10개의 함수 원형만이 지정되어 있어서 사용자가 직접 코드를 작성해 주어야 한다. OS_CPU_C 파일은 C 언어로 작성할 수 있는 함수들로 구성되어 있으며 다음과 같다.

void OSTaskStkInit();
void OSTaskCreateHook();
void OSTaskDelHook();
void OSTaskSwHook();
void OSTaskStatHook();
void OSTimeTickHook();

 이 중에서 이름에 Hook가 들어있는 함수들은 특별히 구현해주지 않아도 상관 없으므로 함수 선언만 해주면 되며 OSTaskStkInit 함수만 구현한다. OSTaskStkInit 함수는 uC/OS가 태스크를 생성할 때 해당 태스크에 할당된 스택을 초기화하는 역할을 한다. 스택은 지역 변수나 함수 호출 등의 경우에 임시로 데이터를 저장하는 메모리 공간으로 하드웨어적으로 스택 포인터(SP)가 가리키는 곳을 의미한다. 멀티태스킹이 가능하기 위해서는 이런 스택이 각 태스크들에 하나씩 할당되어야 한다. 그렇다면 물리적으로 하나밖에 없는 스택(또는 스택 포인터)를 가지고 어떻게 복수의 태스크에 하나씩 할당할 수 있는 것일까? uC/OS에서는 태스크에 할당할 스택 공간을 배열(array)로 생성한 후 이 배열의 시작 주소를 스택 포인터에 저장하는 방식을 사용하며 그 구조는 보통 다음과 같다.

Fig.4 Structure of Stack

일반적으로 스택 시작 부분에서는 CPU의 레지스터 값들을 저장하는데, 이는 각 태스크들이 수행되는 동안에 레지스터 값들이 서로 다르기 때문이다. 만일 스택에 레지스터 정보를 저장해 주지 않으면 컨텍스트 스위칭 후에, 지금까지 진행돼왔던 정보들을 잃어버릴 뿐만 아니라 스위칭된 태스크에도 치명적인 영향을 미친다. 사용자는 OSTaskStkInit 함수에서 스택 모양을 자유롭게 설계(레지스터가 저장되는 순서)할 수 있으며, 뒤의 OSCtxSw와 OSIntCtxSw 함수에서 이 모양대로 스택 값들을 저장 또는 복원해 주면된다. C31에서의 코드 구현은 다음과 같다.

OS_STK *OSTaskStkInit(void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt){
  OS_STK *stk;
  opt = opt;
  stk = (OS_STK *)ptos;

  *(++stk) = (OS_STK)pdata;
  *(++stk) = (OS_STK)0x2000; /* ST */
  *(++stk) = (OS_STK)0x0000; /* R0 */
  *(++stk) = (OS_STK)0x0000; /* FR0 */
  *(++stk) = (OS_STK)0x0000; /* R1 */
  *(++stk) = (OS_STK)0x0000; /* FR1 */
  *(++stk) = (OS_STK)0x0000; /* R2 */
  *(++stk) = (OS_STK)0x0000; /* FR2 */
  *(++stk) = (OS_STK)0x0000; /* R3 */
  *(++stk) = (OS_STK)0x0000; /* FR3 */
  *(++stk) = (OS_STK)0x0000; /* R4 */
  *(++stk) = (OS_STK)0x0000; /* FR4 */
  *(++stk) = (OS_STK)0x0000; /* R5 */
  *(++stk) = (OS_STK)0x0000; /* FR5 */
  *(++stk) = (OS_STK)0x0000; /* R6 */
  *(++stk) = (OS_STK)0x0000; /* FR6 */
  *(++stk) = (OS_STK)0x0000; /* R7 */
  *(++stk) = (OS_STK)0x0000; /* FR7 */
  *(++stk) = (OS_STK)0x0000; /* AR0 */
  *(++stk) = (OS_STK)0x0000; /* AR1 */
  *(++stk) = (OS_STK)0x0000; /* AR2 */
  *(++stk) = (OS_STK)0x0000; /* AR3 */
  *(++stk) = (OS_STK)0x0000; /* AR4 */
  *(++stk) = (OS_STK)0x0000; /* AR5 */
  *(++stk) = (OS_STK)task; /* AR6 */
  *(++stk) = (OS_STK)0x0000; /* AR7 */
  asm(" LDI DP, R7 ");
  asm(" STI R7, @_uvar");
  *(++stk) = (OS_STK)uvar; /* DP */
  *(++stk) = (OS_STK)0x0000; /* IR0 */
  *(++stk) = (OS_STK)0x0000; /* IR1 */
  *(++stk) = (OS_STK)0x0000; /* BK */
  *(++stk) = (OS_STK)0x0100; /* IE */
  *(++stk) = (OS_STK)0x0000; /* IF */
  asm(" LDI IOF, R7 ");
  asm(" STI R7, @_uvar");
  *(++stk) = (OS_STK)uvar; /* IOF */
  *(++stk) = (OS_STK)0x0000; /* RS */
  *(++stk) = (OS_STK)0x0000; /* RE */
  *(++stk) = (OS_STK)0x0000; /* RC */
  return (OS_STK *)stk;
}

여기서 task와 pdata, ptos는 각각 태스크의 시작 번지, 함수 인자 그리고 할당된 스택의 시작 번지를 의미한다. stk는 ptos의 값을 복사 받아 이 값(주소)을 하나씩 증가해가면서 레지스터의 값을 저장하며, 최종적으로 stk가 가리키는 주소를 리턴 한다. 일반적으로 스택 초기화시에 스택에 저장하는 레지스터 정보들은 편의상 0값을 가지지만 경우에 따라서는 특정한 값이나 현재의 레지스터 값을 유지해야 하는 경우가 있다. 전자의 경우로써 AR6에 task의 값(시작 주소)을 저장하였으며 태스크 생성 시의 스위칭이나 또는 OSStartHighRdy 함수가 호출될 때 이 값을 통해 태스크를 시작한다. DP와 IE, IOF는 후자의 경우로 프로세서가 정상적으로 동작하기 위해서 새로운 태스크가 생성되어도 계속 유지되어야 하는 정보로 사용자의 시스템에 맞게 적절히 수정해주어야 한다.

 OS_CPU_A 파일에는 4개의 함수가 있으나 편의상 인라인 어셈을 이용하여 OS_CPU_C 파일에 함께 구현하였다. 함수들은 다음과 같다.

void OSTickISR();
void OSStartHighRdy();
void OSCtxSw();
void OSIntCtxSw();

 OSTickISR 함수는 앞에서 설명한 틱(tick)을 발생시켜주는 함수로 프로세서의 타이머 인터럽트를 통해 동작된다. 즉, 타이머 인터럽트 주기로 OSTickISR 함수를 호출함으로써 uC/OS에 틱을 발생한다. 그러므로 틱의 주기에 의해 uC/OS의 속도가 결정된다. 단, 주의할 점은 이상적으로는 틱의 간격(인터럽트 주기)이 작으면 작을수록 빠르고 부드러운 동작이 가능하지만 실제로는 그렇지 않다는 것이다. uC/OS는 틱이 증가함과 동시에 커널 함수가 호출되기 때문에 컨텍스트 스위칭이나 기타 기능들이 동작하게 되면 이에 따른 하드웨어의 처리량이 증가하게 되어 태스크가 프로세서를 점유하는 시간을 뺏기 때문에 태스크가 실제로 수행하는데 방해가 된다. 그렇기 때문에 사용자는 시스템이 동작하는 환경에 맡게 주기를 결정해야 한다. OSTickISR의 코드 구현은 매우 간단하며 다음과 같다.

void OSTickISR(void){
  OSIntEnter();
  OSTimeTick();
  OSIntExit();
}

 OSStartHighRdy 함수는 uC/OS가 초기화 된 후 OSStart 함수에 의해 딱 한번 호출되며 최초로 실행될 태스크를 선택한다. 만일 OSStart 함수가 호출되기 이전에 태스크를 하나만 생성했다면 이 태스크가 실행되지만 여러 개의 태스크를 생성했다면 그 중에서 가장 우선순위가 높은 태스크가 선택되어 실행되게 된다[5]. OSStartHighRdy 함수의 구현은 앞서 OSTaskStkInit 함수에서 스택에 저장한 레지스터 정보를 프로세서의 레지스터에 저장되도록 하면 된다.

void OSStartHighRdy(void){
  pstack = (OS_STK*)0;
  pstack = OSTCBHighRdy->OSTCBStkPtr;
  OSRunning = TRUE;

  asm(" LDI @_pstack, SP");
  asm(" POP RC ");
  asm(" POP RE ");
  asm(" POP RS ");
  asm(" POP IOF ");
  asm(" POP IF ");
  asm(" POP IE ");
  asm(" POP BK ");
  asm(" POP IR1 ");
  asm(" POP IR0 ");
  asm(" POP DP ");
  asm(" POP AR7 ");
  asm(" POP AR6 ");
  asm(" POP AR5 ");
  asm(" POP AR4 ");
  asm(" POP AR3 ");
  asm(" POP AR2 ");
  asm(" POP AR1 ");
  asm(" POP AR0 ");
  asm(" POPF R7 ");
  asm(" POP R7 ");
  asm(" POPF R6 ");
  asm(" POP R6 ");
  asm(" POPF R5 ");
  asm(" POP R5 ");
  asm(" POPF R4 ");
  asm(" POP R4 ");
  asm(" POPF R3 ");
  asm(" POP R3 ");
  asm(" POPF R2 ");
  asm(" POP R2 ");
  asm(" POPF R1 ");
  asm(" POP R1 ");
  asm(" POPF R0 ");
  asm(" POP R0 ");
  asm(" POP ST ");
  asm(" LDI AR6, R1 ");
  asm(" BU R1");
}

 OSCtxSw 함수는 컨텍스트 스위칭이 일어날 때 지금 까지 수행되던 태스크의 정보(레지스터 포함)들을 스택에 저장하고 새로 수행될 태스크의 정보를 프로세서의 레지스터에 저장하는 역할을 한다. 기본적인 스텝은 OSStartHighRdy 함수와 유사하며 구현은 다음과 같다.

void OSCtxSw(void){
  asm(" LDIU *-AR3(1), AR6");
  asm(" PUSH ST ");
  asm(" PUSH R0 ");
  asm(" PUSHF R0 ");
  asm(" PUSH R1 ");
  asm(" PUSHF R1 ");
  asm(" PUSH R2 ");
  asm(" PUSHF R2 ");
  asm(" PUSH R3 ");
  asm(" PUSHF R3 ");
  asm(" PUSH R4 ");
  asm(" PUSHF R4 ");
  asm(" PUSH R5 ");
  asm(" PUSHF R5 ");
  asm(" PUSH R6 ");
  asm(" PUSHF R6 ");
  asm(" PUSH R7 ");
  asm(" PUSHF R7 ");
  asm(" PUSH AR0 ");
  asm(" PUSH AR1 ");
  asm(" PUSH AR2 ");
  asm(" PUSH AR3 ");
  asm(" PUSH AR4 ");
  asm(" PUSH AR5 ");
  asm(" PUSH AR6 ");
  asm(" PUSH AR7 ");
  asm(" PUSH DP ");
  asm(" PUSH IR0 ");
  asm(" PUSH IR1 ");
  asm(" PUSH BK ");
  asm(" PUSH IE ");
  asm(" PUSH IF ");
  asm(" PUSH IOF ");
  asm(" PUSH RS ");
  asm(" PUSH RE ");
  asm(" PUSH RC ");
  asm(" LDI SP, R7 ");
  asm(" STI R7, @_uvar");

  OSTCBCur->OSTCBStkPtr = uvar;
  OSTaskSwHook();
  OSTCBCur = OSTCBHighRdy;
  OSPrioCur = OSPrioHighRdy;
  uvar = OSTCBHighRdy->OSTCBStkPtr;

  asm(" LDI @_uvar, SP");
  asm(" POP RC ");
  asm(" POP RE ");
  asm(" POP RS ");
  asm(" POP IOF ");
  asm(" POP IF ");
  asm(" POP IE ");
  asm(" POP BK ");
  asm(" POP IR1 ");
  asm(" POP IR0 ");
  asm(" POP DP ");
  asm(" POP AR7 ");
  asm(" POP AR6 ");
  asm(" POP AR5 ");
  asm(" POP AR4 ");
  asm(" POP AR3 ");
  asm(" POP AR2 ");
  asm(" POP AR1 ");
  asm(" POP AR0 ");
  asm(" POPF R7 ");
  asm(" POP R7 ");
  asm(" POPF R6 ");
  asm(" POP R6 ");
  asm(" POPF R5 ");
  asm(" POP R5 ");
  asm(" POPF R4 ");
  asm(" POP R4 ");
  asm(" POPF R3 ");
  asm(" POP R3 ");
  asm(" POPF R2 ");
  asm(" POP R2 ");
  asm(" POPF R1 ");
  asm(" POP R1 ");
  asm(" POPF R0 ");
  asm(" POP R0 ");
  asm(" POP ST ");
  asm(" OR 2000H, ST ");
  asm(" LDIU *AR3, AR3");
  asm(" LDI AR6, R1 ");
  asm(" SUBI 2, SP");
  asm(" BU R1");
}

 OSIntCtxSw 함수는 OSCtxSw 함수와 더불어 태스크의 컨텍스트 스위칭을 위해 호출된다. OSCtxSw 함수와의 다른 점은 OSCtxSw 함수가 태스크와 태스크 사이의 스위칭을 맡는다면 OSIntCtxSw 함수는 인터럽트 루틴과 태스크 사이의 스위칭을 담당한다. 한 예로 어떤 태스크가 수행되는 중간에 인터럽트가 발생한 경우를 들 수 있다. 이 경우 태스크의 레지스터 값들은 프로세서(또는 사용자)에 의해 강제적으로 스택에 저장되고 인터럽트 루틴이 수행된다. 그리고 인터럽트 루틴이 완료되면 프로세서는 태스크에 다시 할당되게 되는데, 이때 uC/OS에서는 인터럽트 시점에서의 태스크로 바로 복귀하지 않고 스케쥴링을 통해 대기 중인 가장 높은 운선 순위를 가진 태스크를 호출한다. 그렇기 때문에 인터럽트가 발생했을 때 스택에 레지스터 값들이 이미 저장되어 있으므로 OSCtxSw 함수에서의 앞부분이 생략되어지게 된다.

void OSIntCtxSw(void){
  asm(" LDI AR7, SP");
  asm(" LDI SP, R7 ");
  asm(" STI R7, @_uvar");

  OSTCBCur->OSTCBStkPtr = uvar;
  OSTaskSwHook();
  OSTCBCur = OSTCBHighRdy;
  OSPrioCur = OSPrioHighRdy;
  uvar = OSTCBHighRdy->OSTCBStkPtr;

  asm(" LDI @_uvar, SP");
  asm(" POP RC ");
  asm(" POP RE ");
  asm(" POP RS ");
  asm(" POP IOF ");
  asm(" POP IF ");
  asm(" POP IE ");
  asm(" POP BK ");
  asm(" POP IR1 ");
  asm(" POP IR0 ");
  asm(" POP DP ");
  asm(" POP AR7 ");
  asm(" POP AR6 ");
  asm(" POP AR5 ");
  asm(" POP AR4 ");
  asm(" POP AR3 ");
  asm(" POP AR2 ");
  asm(" POP AR1 ");
  asm(" POP AR0 ");
  asm(" POPF R7 ");
  asm(" POP R7 ");
  asm(" POPF R6 ");
  asm(" POP R6 ");
  asm(" POPF R5 ");
  asm(" POP R5 ");
  asm(" POPF R4 ");
  asm(" POP R4 ");
  asm(" POPF R3 ");
  asm(" POP R3 ");
  asm(" POPF R2 ");
  asm(" POP R2 ");
  asm(" POPF R1 ");
  asm(" POP R1 ");
  asm(" POPF R0 ");
  asm(" POP R0 ");
  asm(" POP ST ");
  asm(" OR 2000H, ST ");
  asm(" LDIU *AR3, AR3");
  asm(" LDI AR6, R1 ");
  asm(" SUBI 2, SP");
  asm(" BU R1");
}

5. Conclusion

 uC/OS는 공개된 RTOS임에도 불구하고 충실한 커널 서비스를 지원할 뿐만 아니라 그 구조 또한 간단하여 코드 분석에 용이하며 여타의 프로세서에도 쉽게 포팅할 수 있는 장점이 있다. 특히 적은 커널 사이즈와 개발하려는 시스템 사양에 맞게 커널 사이즈를 조절할 수 있는 가변성은 점점 소형화 되가는 현재의 임베디드 시스템 추세에 매우 적합하다고 할 수 있다. 다만 네트워크나 GUI와 같은 고수준의 커널 서비스는 아직 지원되지 않고 있으며 무료 또는 저가의 판매 정책에 의해 커널에 대한 A/S가 부족한 단점이 있다. 그러나 이와 같은 단점에도 불구하고 uC/OS는 매우 우수한 RTOS임에 틀림이 없으며 앞으로 더욱 향상된 성능이 기대된다.

References

[1] Jean J. Labrosse, MicroC/OS-II “The Real-Time Kernel”, CMP books, 1999.
[2] 이석주 역, Programming Embedded Systems in C and C++, 한빛미디어, 2000.
[3] David E. Simon, An Embedded Software Primer, Addison Wesley, 1999.
[4] 윤덕용, TMS320C31 마스터, Ohm 사, 2000.
[5] 김효준, uC/OS-II EP7290(ARM7) 포팅

 

출처 : http://user.chollian.net/~mose/msucos.htm