RTOS 에서 기본적으로 사용되고 있는 TASK, MESSAGE, SEMAPORE 에 대해서 직접 실습을 통해 개념을 이해하도록 만들어졌다.
참고로 여기서 사용되는 소스 코드는 ST 라이브러리를 근간으로 한다.
이제부터는 각각 태스크, 메세지, 세마포어로 표기할 것이다. 부디 혼동이 없기를…
태스크
태스크를 한 마디로 정의하자면, 백그라운드 프로세스라고 보면 되겠다. 예를 들어 다음과 같은 프로그램이 있다고 보자!
uart_init(); pod_init(); main();
일반적으로는 함수가 call 되어지는 순서대로 수행되어진다. 만일 pod_init() 함수에서 무한 루프에 빠진다면, 뒤에 실행되는 main() 함수는 영원히 실행되지 못한다. 간단한 알고리즘을 갖는 프로그램의 경우, 기존의 순차적인 수행방식이 크게 문제되지 않는다.
하지만, 하나의 프로그램에서 여러가지 루틴이 동시다발적으로 일어나게 되면, 순차 처리 방식은 문제가 발생한다.
이럴 때 필요한 것이 바로 태스크이다. 직접 소스코드를 통해 알아보도록 하자!
일단 내가 태스크로 돌리려고 하는 함수가 있어야 한다. 여기서 유의할 것은 반드시 함수의 인자가 void 이어야 한다는 것이다.
void test_color(void) { U32 task_i; for(task_i=0; task_i < 0x10000; task_i=task_i+2) { STSYS_WriteRegDev32LE((0x60006200 + 0x0044), task_i); task_delay(1560); } printf("\nthe end\n"); }
함수의 내용은 특정 레지스터에 순차적으로 값을 증가시키는 단순한 내용이다. 보다시피 인자가 void 이다.
이제 이 함수를 main.c 에서 태스크를 정의해보자!
void test_task(void) { task_create((void (*)(void *))test_color, NULL, 8192, 5, "tKEY", 0); }
정의할 때의 각 인자들을 잘 보기 바란다.
task_create | 함수 이름 |
((void (*)(void *))test_color | 태스크로 실행할 함수 이름 |
NULL | 함수의 인자값 |
8192 | 태스크의 스택 사이즈 |
5 | 태스크의 우선순위 |
tKEY | 태스크의 이름(나중에 디버거에서 사용됨) |
0 | Various flags |
위의 인자 중에서 가장 중요한 우선순위이다. 우선 순위가 다른 우선 순위보다 낮을 때는 제대로 수행이 안될 수 있다.
메세지
메세지의 경우는 앞의 태스크 보다 훨씬 더 복잡하다. 하지만, 동작 원리만 이해한다면 그리 어렵지는 않다. 일반적으로 메세지는 주고 받는 형태로 이루어진다. 이것은 뒤에 설명할 세마포어보다는 약한 개념이다. 한 태스크에서 메세지를 보내면, 또 다른 태스크에서 메세지를 받는다. 어찌보면, 서로 통신을 하면서 상대방의 상황을 체크하는 데 유용하게 사용된다.
일단 메세지를 주고 받기 위해서는 2개 이상의 태스크가 존재해야만 한다. 직접 실습해보면서 알게된 것이지만, 메세지 큐를 통해 보내려는 변수는 반드시 구조체(struct)로 만들어야 한다.
2개의 태스크는
- ans_task
- que_task
이다. 직접 소스 코드를 보면서 이해하기 바란다.
#include "task.h" #include "message.h" #include "ostime.h" #include <stsys.h> #include <arenaDefine.h> // 이 파일에 메세지로 보낼 변수의 구조체를 정의 #define POD_MSG_SIZE 10 #define POD_MSG_MAX 40 message_queue_t *pPod_Qid; void test_color(void) { //U32 Msg_pod, *Msg_pods; TEST_Q Msg_pod, *Msg_pods; clock_t times; Msg_pod.rev_val = 43; *Msg_pods = Msg_pod; times = time_plus(time_now(), 15625); printf("\nQUESTION FUNCTION!!!\n"); message_send (pPod_Qid, (void*)Msg_pods); task_delay(TICKS_10MS*100); Msg_pods = message_receive_timeout(pPod_Qid, ×); printf("\nRev_pod Values!! = %d\n", Msg_pods->rev_val); // 66 } void que_color(void) { TEST_Q *Rev_pod, Value; //U32 *Rev_pod, Value; clock_t time; time = time_plus(time_now(), 15625); printf("\nANSWER FUNCTION!!!\n"); //Rev_pod = message_receive(pPod_Qid); Rev_pod = message_receive_timeout(pPod_Qid, &time); printf("\nRev_pod Value = %d\n",Rev_pod ->rev_val); // 43 Value = *Rev_pod; printf("\nValue = %d\n",Value.rev_val); // 43 //message_release (pPod_Qid, Rev_pod); Value.rev_val = Value.rev_val+ 23; printf("\nValue = %d\n",Value); // 66 *Rev_pod = Value; message_send(pPod_Qid, (void*)Rev_pod); printf("\nPreSend_Value = %d\n",Rev_pod ->rev_val); // 66 } void ans_task(void) { task_create((void (*)(void *))test_color, NULL, 8192, 5, "tKEY", 0); } void que_task(void) { task_create((void (*)(void *))que_color, NULL,8192, 5,"qKEY",0); } void q_test(void) { pPod_Qid = message_create_queue_timeout (POD_MSG_SIZE, POD_MSG_MAX); ans_task(); que_task(); }
다음은 arenaDefine.h 파일이다.
typedef struct TEST_Q{ U32 rev_val; } TEST_Q;
위의 루틴을 설명하자면, 다음과 같다.
- 주고 받을 메세지 큐를 생성, 이때 메세지 사이즈 등을 지정한다
- ans_task 실행
- que_task 실행
- 먼저 ans_task 부터 설명하면, Msg_pod 라는 구조체의 변수 rev_val 에 43 을 넣는다. 그리고 나서 메세지를 보낸다.
- que_color() 에서 앞서 보낸 메세지를 받는다. 받은 값 43 에 23 을 더해서 메세지를 다시 보낸다.
- 다시 test_color() 에서 메세지를 받아서 출력한다.
주의할 점
위의 예처럼, 보낼 변수를 TEST_Q 라는 구조체로 보내지 않으면, 메세지를 받아서 23을 더할 때, 전혀 다른 값이 나올 수 있다.
세마포어
세마포어는 앞에서 설명한 것처럼 메세지보다 좀 더 강한 영향을 가진다. 우선 본격적으로 예제를 보기 전에 ST20 에서 지원하는 세마포어 관련 시스템 콜을 알아보자!
이름 | 설명 |
semaphore_create_fifo | 세마포어를 사용하기 위해 제일 처음 선언하는 API 로서, 세마포어의 초기값을 함께 정의해 주어야 한다 |
semaphore_init_fifo | 세마포어의 초기값을 지정해준다. create 할 때, 함께 선언해줘도 되지만 중간에 값을 수정할 때 사용한다 |
semaphore_signal | semaphore_wait 를 풀어주는 API 로서, 여러번 호출해도 상관없다 |
semaphore_wait | 호출 될 때마다 세마포어 값을 1 감소 시키고, 0 이 되면 더 이상 진입할 수 없다 |
이제는 직접 프로그램 코드를 보면서, 알아보자!
semaphore_t *pSema; // 세마 포어를 위한 변수 선언 void sema_test(void) { pSema = semaphore_create_fifo(3); // 세마 포어 선언, 초기값을 3 으로 지정 if(pSema ==NULL) { printf("Failed to create semaphore\n"); exit(1); } semaphore_init_fifo(pSema, 2); // 역시, 초기값을 2 로 지정, 결국 2 로 수정됨 while(1) { semaphore_wait(pSema); // 값이 2 -> 1 로 감소 printf("\n Put the address => 0x"); scanf("%x",&check); switch (check) { case 0: // 0 을 입력했을 경우 check = 0; printf("this is a %d\n", check); break; case 1: // 1 을 입력했을 경우 check = 3; semaphore_signal(pSema); // 값이 1 -> 2 로 증가 printf("\nthis is a %d\n", check); break; case 2: // 2 를 입력했을 경우 check = 4; printf("\nthis is a %d",check); break; } } }
위의 소스를 설명하자면, 다음과 같다.
- 세마포어를 선언할 변수를 선언하고, 세마포어를 create 한다. 이때 초기값은 3 으로 정의했다.
- semaphore_init_fifo 에서 다시 초기값을 2 로 정의했다. 그래서 결국 값은 2 이다.
- 루프문 안에서 semaphore_wait 로 인해 값이 2 에서 1 로 감소했다. 아직 값이 0 이 아니므로 서브 루틴이 수행된다.
- switch 문에서 0 과 2 를 입력했을 때는 입력값을 출력한 후에 ③으로 간다. 이때는 1 에서 0 으로 감소한다.
- 마지막으로 서브 루틴이 수행된다. 이 후에 0 또는 2 를 입력하면, semaphore_wait 로 인해 서브 루틴이 수행되지 않는다.
- switch 문에서 1 을 입력했을 때는 semaphore_signal 로 인해 값이 1 에서 2 로 증가한다.
주의할 점
직접 예제를 통해 알아본 결과, 세마포어는 특정 루틴을 몇번이상 수행되지 못하도록 하는 데 유용하게 쓰인다. 일종의 카운터 세마포어인 셈이다.