RTOS를 왜 사용하는지 알았으니 이번에는 제공되는 또 다른 기능들에 대해서 알아보겠습니다. 태스크간 통신, 타이머 서비스, 메모리 관리, 이벤트, 인터럽트 루틴 등 상호작용에 대해서 말이죠.
[메시지큐 / 메일박스 그리고 파이프]
태스크는 동작을 제어하거나 데이터를 공유하기 위해서 태스크들 간에 통신을 할 수 있어야 합니다. 우유 저장 창고 감시 시스템에서 창고에서 우유의 양을 게산하는 태스크가 있다고 하면 시스템의 다른 부분들이 우유가 얼마나 들어있는지 알도록 해야합니다. 시리얼 포트 프린터를 네트워크에 연결시켜 주는 텔레그래프 시스템에서는 네트워크에서 데이터를 받는 태스크는 프린터로 데이터를 넘겨주는 다른 태스크로 데이터를 넘겨 주너가 또는 네트워크로 응답을 보내려는 태스크로 데이터를 넘겨주어야 하는 것처럼요.
태스크간에 통신을 하기 위해 공유 데이터를 사용하거나 세마포어를 사용하는 것에 대해서는 말했었죠. 본격적으로 RTOS가 제공하는 다른 방법인 큐, 메일박스, 파이프에 대해서 파봅시다.
Task1과 Task2 두 개의 태스크를 가지고 있고, 각각의 태스크는 높은 우선 순위를 가지고 있어서, 처리해야 할 급한 일은 가지고 있다고 칩시다. 매번 이 두 태스크는 시간을 많이 소모하는 작업인, 에러 조건을 찾아서 네트워크에 보고하는 일을 한다고도 가정해보죠. Task1과 Task2가 지연되지 않도록, 에러 조건을 네트워크에 보고하는 ErrorsTask를 별도의 태스크로 만드는 것이 합리적이겠죠. (한글로 써보겠습니다.) 태스크1 또는 태스크2는 에러를 발견할 때 에러태스크에 보내고 그들 자신의 일을 처리합니다. 에러태스크가 수행하는 에러 보고는 다른 태스크를 지연시키지 않게됩니다.
RTOS의 큐는 이러한 설계를 바탕에두고 구현한 것입니다. 태스크들이 에러를 기록할 필요가 있을 때 vLogError을 호출합니다. 브이로그에러 함수는 에러를 에러 큐에다가 올려 놓고 에러태스크가 처리하도록 합니다.
애드투큐 함수는 정수 인자를 RTOS가 내부적으로 고나리하는 정수 변수의 큐에 넘겨줍니다. 이걸 게시한다는 용어로 많이 부르곤하죠. 리드프럼큐 함수는 큐의 머리에 있는 값을 읽어서 호출한 태스크에 넘겨줍니다. 만약 큐가 비어있다면, 리드프럼큐 함수는 호출한 태스크를 대기 상태로 만듭니다.
RTOS는 이들 함수가 재진입이 가능하다는 것을 보증합니다. 만약 RTOS가 태스크1으로부터 태스크2로 전화을 한다면 태스크1은 애드투큐를 수행하는 도중에 있고 태스크2 역시 동시에 호출한다면 알토스는 그래도 문제 없이 수행되도록 합니다. 에러태스크가 리드프롬큐를 호출하는 매 순간 큐로부터 다음 에러를 받고 심지어 알토스가 에러태스크로부터 태스크1이나 태스크2로 전환하더라도, 다시 돌아와서 호출의 중간부터 계속 실행하게 됩니다.
[복잡하더라도 짚고 넘어가는 세부사항들]
쉽게 상상할 수 있듯이, 큐는 두 함수처럼 단순하지는 않습니다. 대부분의 RTOS에서 다루어야 할 약간의 복잡한 것들에 대한 내용을 써봤습니다.
- 대부분의 RTOS는 큐 초기화 함수를 호출해서 큐를 사용하기 전에 큐를 초기화해야만 합니다. 어떤 시스템에서는, 알토스가 큐를 관리하기 위한 메모리를 할당하는 것도 프로그래머에게 맡깁니다. 세마포어처럼 어떤 태스크가 큐를 사용하기 전에 가장 먼저 실행되는 것이 보장된 코드에서 큐를 초기화 하는 것이 합리적입니다.
- 대부분의 알토스는 프로그래머가 원하는 만큼 큐를 가질 수 있도록 하기 때문에, 모든 큐 함수에 추가적인 인수를 넘겨주어야 합니다. 쓰길 원하거나 읽기를 원하는 큐에 대해서 인식 정보를 줘야 합니다. 다양한 시스템들은 다양한 방법으로 이일을 수습하죠.
- 큐가 가득 찼을 때 코드가 큐에 쓰려고 하면 RTOS는 쓰기 동작이 실패했다고 에러를 반환해서 프로그래머가 알도록 하거나 또는 태스크를 대기 상태로 만들어서 다른 태스크가 큐로부터 데이터를 읽어서 공간을 만들도록 기다립니다. 작성하는 코드는 알토스의 반응에 따라서 처리 할 수 있어야 합니다. 참고로 전자는 일반적인 반응, 후자는 특별한 반응입니다.
- 많은 RTOS는 큐에 데이터가 있으면 데이터를 읽고 데이터가 없으면 에러를 반환하는 함수를 포함합니다. 이런 함수는 더해서 만약 큐가 비어 있으면 태스크를 대기 상태로 만듭니다.
- RTOS가 한번에 프로그래머가 큐에 쓸 수 있도록 하는 데이터의 양은 정확히 쓰고 싶어하는 양과 일치하지 않을 수도 있습니다. 많은 RTOS는 이에 대해서 융퉁성이 없습니다. 일반적인 RTOS의 특징 중의 하나는 프로그래머가 한 번에 보이드형 포인터에 의해서 얻어진 바이트 개수 만큼 쓰도록 허용하고 있습니다.
[포인터와 큐]
한번에 하나의 보이드형 포인트를 큐에 쓰도록 하는 전형적인 RTOS의 형태를 보여주고 있습니다. 적은 양의 데이터를 보이드형 포인터로 캐스팅해서 보내는 흔한 코딩 테크닉에 대해서도요. 이러한 스타일의 RTOS 형태에 담겨있는 분명한 생각은 데이터를 버퍼에 넣고 큐에 버퍼에 대한 포인터를 넣어서 다른 태스크에 전송함으로써 어떠한 양의 데이터도 다른 태스크에 보낼 수 있도록 한다는 것입니다. 후자의 방법을 보여드리자면, vReadTemperaturesTask 태스크는 각각의 온도 쌍을 위한 새로운 데이터 버퍼를 할당하기 위해 C 라이브러리에 있는 malloc 함수를 호출하고 그 버퍼를 가리키는 포인터를 큐에 전송합니다. vMainTask는 큐로부터 버퍼에 대한 포인터를 읽어서 온도들을 비교하고 버퍼를 해제합니다.
[메일박스]
일반적으로 메일박스는 큐와 매우 비슷합니다. 전형적인 RTOS는 메일박스를 생성하고, 쓰고, 읽는 함수들과 메일박스에 메시지가 있는지 없는지를 점검하고, 더 이상 필요하지 않으면 메일박스를 파기하는 함수들을 가지고 있습니다.
그러나 메일박스의 세부사항은 다른 종류의 RTOS에 따라 다릅니다. 가능한 변형들을 보여드릴게요.
- 어떤 RTOS는 각각의 메일박스에 프로그래머가 생성할 때 설정한 어떤 수의 메시지를 담는 것을 허용하지만, 다른 RTOS는 한 번에 하나의 메시지만 담을 수 있도록 허용합니다. 후자의 시스템에서 한 메시지가 메일박스에 쓰여지면, 메일박스는 꽉 차게 되고, 이 메시지가 읽혀지기 전에는 다른 메시지는 쓰여질 수 없게 됩니다.
- 각각의 메일박스가 담을 수 있는 메시지 수는 제한적이지 않습니다. 시스템에 있는 모든 메일박스에 담길 수 있는 전체의 메시지의 총량의 제한만 있고, 개개의 메일박스에는 필요한 만큼의 메시지가 나누어서 들어갈 수 있는 것입니다.
- 메일박스 메시지에 우선 순위를 할당할 수 있습니다. 메일박스에 쓰여지는 순서에 상관 없이 높은 우선 순위의 메시지는 낮은 우선 순위의 메시지보다 먼저 읽혀질 겁니다.
MultiTask! 시스템에서 각각의 메시지는 보이드형 포인터입니다. 프로그래머는 시스템을 설정할 때 필요한 모든 메일박스를 생성하고 나서 세 함수를 사용할 수 있습니다.
모든 세 함수에서 uMbld 인자는 사용할 메일박스를 인식하는데 사용됩니다. sndmsg함수는 p_vMsg를 uMbId 메일박스에 의해서 유지되는 메시지의 큐에 uPriority가 나타내는 우선 순위를 가지고 더합니다. uMbId가 유효하지 않거나 메일박스에 이미 너무 많은 메시지가 기다리고 있으면 에러가 반환됩니다. rcvmsg 함수는 특정한 메일박스에서 가장 우선 순위가 높은 메시지를 반환합니다. 만약 메일박스가 비어있으면, 이 함수는 호출한 태스크를 대기상태로 만듭니다. 메시지가 없을 때 얼마나 오랫동안 기다릴 지를 uTimeout 인자를 사용해서 결정할 수 있습니다. 그러한 시간 제한 특성에대해서도 다뤄봐야겠지요. chkmsg 함수는 메일박스에 있는 첫 번째 메시지를 반환합니다. 메일박스가 비어 있으면 이 함수는 바로 널값을 반환합니다. 멀티태스크에서는 널 포인터가 메시지로 가능하지 않다는 것을 의미합니다.
'IT > 임베디드 시스템' 카테고리의 다른 글
타이머 함수 - 임베디드 시스템 시간 관리 함수 (0) | 2020.06.09 |
---|---|
파이프 - 큐와 비슷하지만 다른 변형들 (0) | 2020.06.06 |
세마포어의 용도/종류/문제 그리고 공유데이터 보호 방법 (0) | 2020.06.03 |
RTOS 문제 해결 툴 - 세마포어 (0) | 2020.06.03 |
태스크의 독자적인 데이터 구조에 대해서 (0) | 2020.06.03 |
댓글