Message 기반 프로세스

RTOS를 사용하지 않는 베어메탈 Firmware에서는 각각의 기능 프로세스의 처리 시간을 적당히 잘 분배 해야 합니다.

 

처리 시간이 긴 프로세스는 여러개로 쪼개야 하며, 특히 인터럽트 발생시 인터럽트 인터럽트 루틴에 긴 처리시간을 요구하는(지연이 발생하는)코드를 넣으면 절대 안됩니다.

 

더욱이 애매한 처리 시간을 요구하는 코드를 넣는 경우는 더욱 위험합니다. 왜냐하면 불규칙적으로 에러를 발생되며 디버깅을 위해 많은 시간이 소요될 수도 있습니다.

 

인터럽트 서비스 루틴에서는 입력된 데이터를 적당한 자료구조에 적재해놓던가, 특정 플래그를 set하는 수준 혹은 메시지 큐에 쌓는 수준의 처리가 가장 좋습니다.

 

만약 전체 시스템이 1초단위로 어떠한 처리가 끝나야 한다면, 각각의 프로세스들이 최대 처리되는 시간의 합이 1초를 넘어서는 안되며 50%~70% 정도가 적당하다고 생각합니다.

 

처리시간 측정은 특정한 GPIO를 이용하여 처리할 동안 set, 처리가 끝나면 reset하는 과정을 모든 프로세스에 적용시키고 scope로 측정하는 방법도 있으며. 아니면 Core의 Cycle 측정하는 firmware를 구현 하는 방법도 있습니다.

 

아무튼 이러한 이유로 Message기반으로 프로세스를 처리하도록 하는 것은 괜찮은 아이디어이며 실제 프로젝트에서도 사용해보니 복잡한 프로세스를 처리하는 데에도 문제가 없었습니다.

 

*Message를 너무 자주 발생시키는 곳에는 적합하지 않습니다. Message 적재가 한정되어 있다보니 다 처리하기도 전에 overwrite 되는 경우도 발생할 수 있습니다.

 

아래는 헤더파일입니다. 메시지 자체가 있습니다. 현재는 3개의 실질적인 메시지가 있으며 여기서 MSG_NONE는 처리하지 않을 예정입니다. 그 외 메시지를 set하고 get하는 부분과 clear 시켜주는 extern 함수가 있습니다.

/* message.h */
#ifndef MESSAGE_H__
#define MESSAGE_H__

typedef enum
{
	MSG_NONE = 0,
	MSG_TEST_1,
	MSG_TEST_2,
	MSG_TEST_3,

	...

	MSG_MAX_COUNT,

}msg_t;

extern void clear_message(void);
extern void set_message(msg_t msg, uint32_t param1, uint32_t param2);
extern bool get_message(msg_t *msg, uint32_t *param1, uint32_t *param2);

#endif
 

외부에서 메시지를 발생시키려면 set_message에 message종류와 파라메터를 넣어주면 됩니다. 이렇게 쌓인 message들은 main 무한루프에서 들어오는 순서대로 처리할 예정입니다.

 

아래 코에서 message 는 총 30개까지로 정해놓았습니다. 필요에 따라 늘려서 사용할 수 있습니다. message 자료 구조는 전형적인 Circle Queue 입니다.

/* message.c */
typedef struct
{
	msg_t msg;
	uint32_t param1;
	uint32_t param2;
}message_t;

#define MESSAGE_QUEUE_LEN	(30)
static message_t g_message_queue[MESSAGE_QUEUE_LEN]={MSG_NONE};
static int g_message_queue_head = 0;
static int g_message_queue_tail = 0;

void clear_message(void)
{
	g_message_queue_head = 0;
	g_message_queue_tail = 0;	
}

void set_message(msg_t msg, uint32_t param1, uint32_t param2)
{
	g_message_queue[g_message_queue_head].msg = msg;
	g_message_queue[g_message_queue_head].param1 = param1;
	g_message_queue[g_message_queue_head].param2 = param2;

	g_message_queue_head++;

	if(g_message_queue_head >= MESSAGE_QUEUE_LEN)
	{
		g_message_queue_head = 0;
	}
}

bool get_message(msg_t *msg, uint32_t *param1, uint32_t *param2)
{
	if(g_message_queue_head == g_message_queue_tail)
	{
		return false;
	}

	*msg = g_message_queue[g_message_queue_tail].msg;
	*param1 = g_message_queue[g_message_queue_tail].param1;
	*param2 = g_message_queue[g_message_queue_tail].param2;

	g_message_queue_tail++;

	if(g_message_queue_tail >= MESSAGE_QUEUE_LEN)
	{
		g_message_queue_tail = 0;
	}

	return true;
}
 

위의 함수를 이용해서 메인 무한루프 코드를 만들어 보도록 하겠습니다. 이전 포스트 schedule을 이용해보도록 하겠습니다.

 

schedule을 사용하지 않는 경우에는 while 루프에 message_process를 직접 넣으면 됩니다. 보시다시피message_process의 두개의 파라메터는 혹 직접 호출할 경우를 대비해서 미리만들어 놓은 것입니다.

 

앞으로도 이런식으로 두개의 파라메타는 습관적으로 들어갈 예정입니다.

/* main.c */
void main(void)
{
	register_schedule(message_process);

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

void message_process(uint32_t param1, uint32_t param2)
{
	msg_t msg;
	uint32_t msg_param1, msg_param2;

	if(get_message(&msg, &msg_param1, &msg_param2) == true)
	{
		switch(msg)
		{
		case MSG_TEST_1:
			process_test1(msg_param1, msg_param2);
			break;

		case MSG_TEST_2:
			process_test2(msg_param1, msg_param2);
			break;

		case MSG_TEST_3:
			process_test3(msg_param1, msg_param2);
			break;

		case MSG_NONE:
			break;

		default:
			break;
		}
	}
}

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

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

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

코드를 보면 이해하는데 어려운 사항은 없어 보입니다. 메인 무한루프에서는 메시지 큐를 확인하여 메시지가 있으면 해당되는 메시지에 종속적은 프로세스를 계속해서 실행하면 됩니다. 메시지는 각각의 센서나 다른 입력단에서 쌓을 수 있습니다.

 

즐거운 C 생활하세요

 

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

Observer 패턴  (0) 2023.12.14
Strategy 패턴  (0) 2023.12.12
RTOS 없이 구현한 스케줄러  (0) 2023.12.09
State 패턴  (1) 2023.12.08
C언어에서의 this 구현  (1) 2023.12.06