RTOS 없이 구현한 스케줄러

RTOS를 사용하지 않는 베어메탈 Firmware의 프로그램 절차는 무한루프와 인터럽트로 이루어져 있습니다.

이번장에서는 무한루프의 구조를 어떻게 바꾸며 보다 효율적이고 좀 더 그럴듯하게 만들 수 있는지 알아보겠습니다.

 

우선 우리가 알고 있는 기본적인 while 무한루프입니다.

/* main.c */
void main(void)
{
	while(1)
	{
		process1();
		process2();

		switch(key)
		{
		case button1:
			break;

		case button2:
			break;

		case button3:
			break;

		default:
			break;
		...
		}
	}
}
 

위의 코드는 간단해보이지만 실제적으로는 훨씬 더 복잡해 질 가능성이 있습니다. 만약 설정(#ifdef)에 따른 process 유무가 들어 간다면 더욱 복잡해 지며 버튼의 수가 늘어난다면 더욱 복잡해 집니다.

 

이제 우리가 만들 scheduler는 RTOS가 없기 때문에 타임슬라이스나 선점형 컨텍스트 스위칭인 일어나지 않는 것을 잘 이해해야 합니다. 순차적 프로세싱하기 때문에 하나의 프로세스가 너무 많은 시간을 소요하면 지연 문제가 발생 할 수 있습니다.

 

defines.h는 기본적인 type definition 입니다.

/* defines.h*/
#define null (void *)0

typedef enum
{
  false = 0,
  true  = 1
}bool;
 

파라메터를 두개 가지고 있는 프로세스 저장할 수 있는 Queue를 만듭니다. 제 개인적인 생각에 두개의 파라메터는 각종 프로세스를 처리하기에 알맞은 갯수입니다. uint32_t 로 형태이기 때문에 값을 전달하기도 넉넉하고 주소를 넘기기에도 좋습니다. 또한 다른 프로세스에서 호출 하여도 파라메터를 넘길 수 있습니다.

/* scheduler.c */
typdef void(*schedule_handler)(uint32_t param1, uint32_t param2);

#define MAX_SCHEDULE_HANDLERS	(10)
static schedule_handler schedule_handlers[schedule_handler]={0};
static int current_schedule_no = 0;

bool register_schedule(schedule_handler handler)
{
	for(int i=0; i<MAX_SCHEDULE_HANDLERS; i++)
	{
		if(schedule_handlers[i] == null)
		{			
			schedule_handlers[i] = handler;
			return true;
		}
	}

	return false;
}

bool unregister_schedule(schedule_handler handler)
{
	for(int i=0; i<MAX_SCHEDULE_HANDLERS; i++)
	{
		if(schedule_handlers[i] == handler)
		{
			schedule_handlers[i] = null;
			return true;
		}
	}

	return false;
}

void execute_schedule(uint32_t param1, uint32_t param2)
{
	if(schedule_handlers[current_schedule_no] != null)
	{
		schedule_handlers[current_schedule_no](param1, param2);
	}

	current_schedule_no++;
	if(current_schedule_no >= MAX_SCHEDULE_HANDLERS)
	{
		current_schedule_no = 0;
	}
}

schedule_handler get_current_schedule(void)
{
	...
}

schedule_handler get_next_schedule(void)
{
	...
}
 

위의 코드중 현재 스케줄과 다음 스케줄의 포인터를 가져오는 부분은 코딩하지 않았습니다. 스스로 구현해 보는 것도 재미있을 것 같습니다.

 

아래 코드는 while 루프 들어가기 앞으로 사용할 프로세스들을 등록하도록 되어 있습니다. 그 후 while 문에서 계속하여 순차적을 등록된 프로세스들이 수행되도록 합니다.

/* main.c */
void main(void)
{
	register_schedule(process1);
	register_schedule(process2);
	register_schedule(process3);

	while(1)
	{
		execute_schedule(0, 0);
	}
}

void process1(uint32_t param1, uint32_t param2)
{
	...
}

void process2(uint32_t param1, uint32_t param2)
{
	...
}

void process3(uint32_t param1, uint32_t param2)
{
	switch(key)
	{
	case button1:
		break;

	case button2:
		break;

	case button3:
		break;

	default:
		break;
	...
	}
}
 

#ifdef를 사용해도 가독성이 떨어지거나 하는 것이 덜합니다.

/* main.c */
void main(void)
{
#ifdef PROCESS_1
	register_schedule(process1);
#endif
	register_schedule(process2);
	register_schedule(process3);

	while(1)
	{
		execute_schedule(0, 0);
	}
}
 

간단하게 프로세스 스케쥴러를 작성해 보았습니다. main.c의 무한루프는 최대한 간단하게 유지하셔야 합니다. 그리고 코드의 흐름이 서로 약하게 연결되어야 디버깅하기가 편합니다. 코드간의 연결성을 낮추기 위해서는 메시지 처리 방식이 좀 더 나은데 다음장에서 다뤄 보도록 하겠습니다.

'▶ C Application > 디자인 패턴' 카테고리의 다른 글

Strategy 패턴  (0) 2023.12.12
Message 기반 프로세스  (0) 2023.12.10
State 패턴  (1) 2023.12.08
C언어에서의 this 구현  (1) 2023.12.06
C언어로 만드는 Interface  (1) 2023.12.06