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 |