승코딩당당당

[RTOS] OSEK/Trampoline RTOS Task 스케줄링 실습 본문

개발/임베디드

[RTOS] OSEK/Trampoline RTOS Task 스케줄링 실습

승코딩당당당 2026. 4. 24. 16:41

 

이번 실습에서는 Trampoline RTOS 환경에서 여러 Task(Task1, Task2, TaskM)를 정의하고, Task 간 상태 변화와 스케줄링 동작을 확인하였다. 특히 ActivateTask, ChainTask, GetTaskState API를 활용하여 Task의 실행 흐름과 상태 전이를 관찰하는 데 초점을 맞췄다.

 


 

접근 방법

1. 주기 Task (TickPrint)

  • Timer2 기반 Alarm을 통해 약 1초 주기로 실행
  • 카운터 c를 증가시키며 로그 출력
  • c == 0일 때 Task1을 활성화
if (c == 0) {
    ActivateTask(Task1);
}

 

2. Task1 → Task2 흐름 구성

Task1 동작

  1. 실행 시작 로그 출력
  2. Task 상태 확인
  3. 3초 대기
  4. Task2 활성화
  5. 다시 상태 확인
  6. 3초 대기
  7. 종료 후 TaskM으로 전환 (ChainTask)

Task2 동작

  1. 실행 시작 로그 출력
  2. 상태 확인
  3. 3초 대기
  4. 종료 후 TaskM으로 전환

TaskM 동작

  • Task1, Task2 상태 출력 후 종료
 

 

asw.c

#include "bsw.h"

void printState(TaskType id)
{
	TaskStateType state;
	if (GetTaskState(id, &state) == E_OK) {
		switch (state) {
			case SUSPENDED:
				printfSerial("%d: suspended...", id);
				break;
			case READY:
				printfSerial("%d: ready...", id);
				break;
			case WAITING:
				printfSerial("%d: waiting...", id);
				break;
			case RUNNING:
				printfSerial("%d: running...", id);
				break;
		}
	} 
}
TASK(TaskM)
{
	printState(Task1);
	printState(Task2);
	TerminateTask();
}
TASK(Task1)
{
	TaskType id;
    printfSerial("Task1 Begins...");
	printState(Task1);
	printState(Task2);
    mdelay(3000);
	ActivateTask(Task2);
	printState(Task1);
	printState(Task2);
	mdelay(3000);
	GetTaskID(&id);
	printfSerial("Task ID = %d...", id);
    printfSerial("Task1 Finishes...");
    ChainTask(TaskM);
}

TASK(Task2)
{
	TaskType id;
    printfSerial("Task2 Begins...");
	printState(Task1);
	printState(Task2);
    mdelay(3000);
	GetTaskID(&id);
	printfSerial("Task ID = %d...", id);
    printfSerial("Task2 Finishes...");
    ChainTask(TaskM);
}

 

conf.oil

/* Running the following example:
 * just call goil a first time using the command line:
 * goil --target=avr/arduino/uno --templates=../../../../goil/templates/ serial.oil
 * you may have to update ARDUINO section (l.13) for your configuration.
 *
 */

OIL_VERSION = "2.5" : "test" ;

CPU test {    
  OS config {
    STATUS = STANDARD;
    BUILD = TRUE {
      TRAMPOLINE_BASE_PATH = "../..";
      APP_NAME = "base";
      APP_SRC = "bsw.cpp";
      APP_SRC = "asw.c";
      LDFLAGS = "-Wl,-Map=counter.map";
      CPPCOMPILER = "avr-g++";
      COMPILER = "avr-gcc";
      LINKER = "avr-gcc";
      ASSEMBLER = "avr-gcc";
      COPIER = "avr-objcopy";
      SYSTEM = PYTHON;
      LIBRARY = serial;

    };
    SYSTEM_CALL = TRUE;
  };

  APPMODE stdAppmode {};

  /* The following code block is for basic features of the labs */
  COUNTER DefaultTimer {
    SOURCE = TIMER2_OVF;
    TICKSPERBASE = 1;
    MAXALLOWEDVALUE = 2000;
    MINCYCLE = 1;
  };

  ALARM TimerAlarm {
    COUNTER= DefaultTimer;
    ACTION = ACTIVATETASK {
      TASK = TickPrint;
    };
    AUTOSTART = TRUE {
      // note: no more than 255 => MAXALLOWEDVALUE in counter
      ALARMTIME = 977; 
      CYCLETIME = 977; //activated each 1000 ms
      APPMODE = stdAppmode;
    };
  };

  TASK TickPrint {
    PRIORITY = 20;
    AUTOSTART = FALSE; 
    ACTIVATION = 1;
    SCHEDULE = FULL;
    STACKSIZE = 256;
  };
  /* The above code block is for basic features of the labs */

  /* Fill up your code in here */

  TASK Task1 {
    PRIORITY = 1;
    AUTOSTART = FALSE;
    ACTIVATION = 1;
    SCHEDULE = FULL;
    STACKSIZE = 256;
  };

  TASK Task2 {
    PRIORITY = 2;
    AUTOSTART = FALSE;
    ACTIVATION = 1;
    SCHEDULE = FULL;
    STACKSIZE = 256;
  };

  TASK TaskM {
    PRIORITY = 3;
    AUTOSTART = FALSE;
    ACTIVATION = 1;
    SCHEDULE = FULL;
    STACKSIZE = 256;
  };
};

 

bsw.cpp

#include "tpl_os.h"
#include "Arduino.h"
#include "bsw.h"

#define LEN_BUF 64

extern "C" {

void mdelay(unsigned long delay_ms)
{
    unsigned long prev_ms = millis(), current_ms = millis();
    unsigned long period_ms = 20, cnt = 0;
    while (cnt < (delay_ms / period_ms)) {
        current_ms = millis();
        if (current_ms - prev_ms >= period_ms) {
            cnt++;
            prev_ms = millis();
        }
    }
}

void printfSerial(const char *fmt, ...)
{
    char buf[LEN_BUF];
    va_list args;
    va_start(args, fmt);
    vsnprintf(buf, LEN_BUF, fmt, args);
    va_end(args);
    Serial.print(buf);
}

} // extern "C"

/**
 * configure TIMER2 to generate an interrupt
 * each 1 ms
 */
void timerInit()
{
  TCCR2A = 0;
  TCNT2 = 0;
  // clkio = 16MHz => clk = 250KHz
  TCCR2B = (1 << WGM22) | (1 << CS22); // CTC and clk = Clkio/64
                                       // interrupt each 1ms
  OCR2A = 250 - 1;
  TIMSK2 |= (1 << TOIE2);
  interrupts();
}

void setup(void)
{
    pinMode(13, OUTPUT);
    Serial.begin(115200); //115200 bps, 8N1
    printfSerial("\n...............\n");
    printfSerial("...OS Starts...\n");
    printfSerial("...............\n");
    timerInit();
}

/* This task will be activated every second */
TASK(TickPrint)
{
    static long c = -4;
    if (c == 0) {
        ActivateTask(Task1);
    }
    printfSerial("\n%4ld: ", c++);
    TerminateTask();
}

 


 

실행 결과

 

1. OS 시작 및 Tick 증가

...OS Starts...
-4:
-3:
-2:
-1:

 

  • TickPrint Task가 주기적으로 실행됨
  • 아직 Task1은 실행되지 않음

 

2. Task1 실행 시작

0: Task1 Begins...0: running...1: suspended...

 

 

  • Task1이 실행되며 RUNNING 상태
  • Task2는 아직 SUSPENDED 상태

 

3. Task2 활성화 및 선점 발생

3: Task2 Begins...0: ready...1: running...

 

  • Task1이 Task2를 Activate
  • Task2(priority 2)가 Task1(priority 1)보다 우선순위 높음

👉 선점 발생

  • Task1 → READY 상태
  • Task2 → RUNNING 상태

4. Task2 종료 후 TaskM 실행

6: Task ID = 1...Task2 Finishes...
0: ready...1: suspended...0: running...1: suspended...

 

  • Task2 종료 후 ChainTask(TaskM)
  • TaskM 실행되며 상태 출력

 

5. Task1 재실행 및 종료

9: Task ID = 0...Task1 Finishes...
0: suspended...1: suspended...

 

 

  • Task1이 다시 실행되어 마무리
  • 모든 Task가 SUSPENDED 상태로 종료

 


 

비선점(Non-Preemptive) 스케줄링 동작 확인

앞선 실습에서는 SCHEDULE = FULL 설정을 통해 선점형 스케줄링 동작을 확인하였다.
이번에는 동일한 코드 구조에서 SCHEDULE = NON으로 변경하여 비선점 방식에서의 실행 흐름을 비교해보았다.

 

변경 사항

SCHEDULE = NON;

 

 

  • Task1, Task2, TaskM 모두 비선점 방식으로 설정
  • 실행 중인 Task는 종료 전까지 CPU를 계속 점유
  TASK Task1 {
    PRIORITY = 1;
    AUTOSTART = FALSE;
    ACTIVATION = 1;
    SCHEDULE = NON;
    STACKSIZE = 256;
  };

  TASK Task2 {
    PRIORITY = 2;
    AUTOSTART = FALSE;
    ACTIVATION = 1;
    SCHEDULE = NON;
    STACKSIZE = 256;
  };

  TASK TaskM {
    PRIORITY = 3;
    AUTOSTART = FALSE;
    ACTIVATION = 1;
    SCHEDULE = NON;
    STACKSIZE = 256;
  };

 

 


 

실행 결과

 

1. Task1 실행 시작

0: Task1 Begins...0: running...1: suspended...

 

  • Task1이 실행되며 RUNNING 상태
  • Task2는 아직 SUSPENDED 상태

 

2. Task2 활성화 (하지만 즉시 실행되지 않음)

0: running...1: ready...

 

  • Task1 내부에서 ActivateTask(Task2) 호출
  • Task2는 READY 상태로 전이

하지만 여기서 중요한 점은,

👉 Task2가 READY 상태임에도 불구하고 실행되지 않음

 

3. Task1이 끝날 때까지 계속 실행

Task ID = 0...Task1 Finishes...
  • Task1이 ChainTask() 또는 종료될 때까지 계속 실행됨
  • 중간에 Task2로 전환되지 않음

👉 선점형과 가장 큰 차이 발생 지점

 

4. Task2 실행

Task2 Begins...0: suspended...1: running...

 

  • Task1 종료 이후에야 Task2가 실행됨
  • Task2가 RUNNING 상태로 전이

 

5. Task2 종료

Task ID = 1...Task2 Finishes...
0: suspended...1: suspended...

 

모든 Task가 종료되며 SUSPENDED 상태로 돌아감

 

 


 

선점 vs 비선점 핵심 비교

구분 선점 (FULL) 비선점 (NON)
Task 전환 시점 즉시 현재 Task 종료 후
우선순위 반영 즉시 지연됨
실행 흐름 중간에 끊김 끝까지 실행
Task2 실행 시점 Activate 직후 Task1 종료 후