[필요할 수도 있지만 아닐 수도 있는 바로 그 태스크들]
다른 곳에서 볼 수 있는 시스템을 태스크로 나누는 몇 가지 제안과 그와 관련된 의견들에 대해 얘기해볼게요.
개별로 단순하게 되어 있는 아주 작은 태스크들을 많이 가져야 합니다. 단순하고 명료함은 언제나 추구되어야 할 목표지만 이미 말했듯 많은 데이터를 공유하기 때문에 세마포어들을 사용해야 하는 것과 많은 태스크 간 통신을 가져야만 하는 것, 그리고 태스크 전환에 소모되는 프로세서 시간이 시스템의 작업 처리량을 감소시키는 것이 트레이드오프로 작용합니다.
별도의 이벤트에 응답하기 위해서 필요한 작업에 대한 별도의 태스크를 가지세요.
문제가 발생하지 않을 때까지는 매우 멋진 생각임에 틀림없습니다. 그러나 task1과 2가 데이터를 공유하거나 서로 통신을 해야 한다면 앞서의 우선순위 또는 캡슐화를 따랐을 때보다 더 복잡하게 코드를 작성해야만 하고 계속 그래 왔던 것처럼 문제를 일으킬 수 있죠.
[태스크 구조 추천]
대부분 사용하는 태스크 구조에 대한 가상 코드가 있습니다. 태스크는 RTOS의 시그널을 기다리면서 무한 루프에서 대기합니다. 그 시그널은 단지 이 태스크만 읽고 다른 태스크들이나 인터럽트 루틴이 값을 써넣을 큐에서 가장 일반적인 메시지 형태를 가질 겁니다. 이 태스크는 독자적인 고유한 데이터를 선언하고요.
태스크 구조를 추천했으니 이런 형태였을 때의 장점을 살펴봅시다.
- 태스크는 단지 한 군데에서만 멈춰서 대기합니다. 다른 태스크가 이 태스크의 큐에 요청을 보낼 때, 이 태스크는 제 때 발생할지 안 할지 모르는 다른 이벤트를 기다리느라 멈추지 않습니다. 이론적으로만 따져보면 고유한 데이터를 가지고 있기 때문에 태스크는 심지어 어디에서도 세마포어를 기다리지 않습니다.
- 이 태스크가 해야 할 일이 전혀 없을 때 입력 큐는 텅 빌 텐데 태스크는 대기 상태로 들어가 쓸데없이 마이크로프로세서의 시간을 소모하지 않게 됩니다.
- 이 태스크는 다른 태스크와 공유하는 공개 데이터를 가지고 있지 않습니다. 고유 데이터를 보거나 수정하려는 다른 태스크는 큐에 요청을 보내야만 하는데 이 태스크가 그것을 다루게 됩니다. 데이터를 사용하는 다른 태스크가 세마포어를 적절히 사용하는지에 대해서 근심할 필요가 없는 것이죠. 공유 데이터도 없고 세마포어 또한 없습니다.
윈도 프로그래밍에 익숙할 경우에는 이런 태스크 구조가 윈도에서 윈도 루틴과 매우 비슷하다는 것을 눈치챘을 겁니다.
임베디드 시스템에서 태스크는 종종 상태 기계로 구성됩니다. 상태는 태스크 안에 있는 고유 변수에 저장되고요. 큐로부터 받는 메시지는 이벤트입니다. RTOS는 이벤트들이 깔끔하게 하나씩 큐에 저장되도록 할 것이고, 태스크는 구조적으로 한 번에 하나씩 처리할 것이기 때문에 상태 기계 구조는 자연스럽기 그지없습니다.
가끔 다른 태스크 구조도 사용되긴 합니다. 입력큐와 지연 시간 두 곳에서 멈춰서 기다려야 하는 예를 들 수 있어요. 이 태스크는 지연시간 동안은 일을 할 수 없기 때문에, 다른 구조가 이 태스크에 더 적합합니다. 만약에 플래시 메모리가 쓰는 것을 완료하는 것을 태스크가 기다리는 동안에 다른 메시지가 큐에 쓰이면 이들 메시지는 큐에서 똑같이 기다려야 합니다. 태스크가 처리할 수 없는 상태에서 큐로부터 메시지를 읽는 것은 무의미합니다.
[태스크를 생성하거나 소멸시키는 것 모두 피해야 한다.]
모든 RTOS는 시스템이 시작할 때, 프로그래머가 태스크를 생성하도록 허용합니다. 대부분의 RTOS는 시스템 동작 시 태스크를 생성하거나 소멸시킬 수 있도록 허용하고요. 이렇게 하는 것을 피해야 하는 이유가 두 가지나 있는데 엄청 타당합니다. 첫 번째는 태스크를 생성하거나 소멸시키는 함수는 일반적으로 RTOS 안에서 많은 시간을 소모하는 함수라는 겁니다. 보통 세마포어를 얻거나 메시지를 메일박스에 쓰는 것보다 훨씬 더 오래 걸립니다. 이 함수들을 실행하고 있는 동안에는 시스템은 어떤 건설적인 작업도 수행하지 못합니다. 따라서 태스크를 생성하거나 소멸하는 것은 시스템의 작업 처리량에 막대한 영향을 끼칠 수 있습니다.
두 번째로 이어가 보면 태스크를 생성하는 것은 상대적으로 안정된 작업일지라도 태스크를 소멸시키는 것은 버그를 일으킬 수 있는 가능성이 약간이라도 존재한다는 겁니다. 태스크가 세마포어를 가지고 있을 때 그 태스크를 소멸시키면, 그 세마포어를 필요로 하는 다른 태스크들은 영원히 기다릴 수도 있긴 합니다. 복잡한 RTOS는 이런 것이나 다른 것들을 자동으로 처리해 주지만, 성가신 문제들이 항상 일어날 가능성이 뒤따릅니다. 입력큐에 메시지가 있는 상태로 태스크를 소멸시키면 어떤 일이 발생할까요? 아마 메시지를 지우기 위해서 큐 또한 소멸시킬 수 있을 겁니다. 하지만 입력 큐의 메시지 중에 소멸된 태스크가 나중에 해제시키려고 했던 메모리 버퍼를 가리키는 포인터가 들어 있다면? 필연적으로 발생하는 메모리 누수를 어떻게 피할 수 있는가? 하는 일들이 반복됩니다.
태스크를 생성하고 소멸시키는 것의 대안은 필요한 모든 태스크를 시스템이 시작할 때 생성시키는 겁니다. 나중에 태스크가 아무런 일도 하지 않는다면, 입력 큐에 뭔가 필요가 나타날 때까지 태스크는 멈출 겁니다. 태스크가 멈추고 있는 동안에 태스크가 사용하는 유일한 자원은 스택을 공간을 위한 것과 RTOS가 태스크를 관리하기 위해 필요한 제어 구조를 위한 메모리뿐입니다. 메모리가 너무 빡빡한 경우가 아니라면, 태스크를 유지하는 것이 더 좋은 방법이 될 것이고요.
[시분할을 끄는 것을 고려해야 한다.]
RTOS 스케줄러는 항상 가장 높은 우선순위로 준비 상태에 있는 태스크를 실행시킨다는 것을 얘기했었는데요. 그러나 더 높은 우선순위를 가지는 다른 준비 상태의 태스크는 없고, 두 개 내지 여러 개의 준비 상태의 태스크가 같은 우선순위를 가졌다면 어떤 문제가 일어날 수 있는지 생각해봅시다. 대부분의 RTOS가 제공하는 한 가지 옵션은 마이크로프로세서를 매우 짧은 시간, 일반적으로 몇 개의 클럭 틱 동안 한 태스크에 주고 나서, 다른 태스크로 전환시켜서 같은 시간 동안 실행시키고 하는 것을 반복하는 시분할(Time-Slicing) 기법이 있습니다.
RTOS는 또한 프로그래머가 이 옵션을 끌 수 있도록 허용합니다. 많은 시스템에서 이렇게 하는 것을 고려해야만 합니다. 또 정말로 같은 우선순위를 갖는 두 개의 태스크를 갖기를 원하는지 또는 그냥 하나의 태스크로 만들길 원하는지 또한 생각해 봐야 합니다.
n명의 사용자들이 하나의 컴퓨터 시스템에서 계산 량이 많은 프로그램을 실행시킬 때는 시분할 방식이 가장 적절합니다. 시스템이 시분할 방식을 지원한다면, 각각의 프로그램은 정해진 양의 마이크로프로세서의 시간을 얻을 것이고, 각 사용자는 프로그램이 돌아가는 것을 보게 될 것입니다. 각 사용자의 프로그램이 같은 양만큼의 시간을 얻을 것이고, 컴퓨터 자원 할당은 공정하다고 생각될 겁니다. 그러나 임베디드 시스템에서는 공정한 할당은 중요한 문제가 아닙니다. 중요한 것은 정해진 시간에 응답을 해야 한다는 것입니다. 임베디드 시스템은 하나 이상 계산량이 많은 태스크를 가지고 있는 경우가 드물고, 가지고 있다고 해도 1, 각 태스크는 똑같이 급하게 처리해야 할 필요가 없기 때문에 서로 다른 우선순위를 갖거나 2, 같은 중요성을 갖기 때문에 어떤 태스크가 먼저 끝내야 되는 지를 신경 쓸 필요가 없는 두 가지로 분류됩니다. 두 경우 모두 시분할 방식이 그리 도움되지 않습니다.
또한, 시분할 장식은 태스크 전환이 더 자주 일어나기 때문에 시스템의 작업 처리량을 감소시킵니다. 간단히 보자면 우유 저장 창고 감시 시스템이 하나의 창고에 있는 우유 양을 계산하는데 10초가 걸린다고 생각해봅시다. 가령 6개의 창고가 우유 양을 계산하는 6 개의 태스크를 가지고 있고, RTOS가 각 태스크의 실행을 마치고 다음 태스크로 전환을 한다면 10초마다 각 창고의 우유 양을 알 수 있고, 60초가 지나면 전체 창고들의 우유 양을 알 수 있을 겁니다. 한 태스크에서 순차적으로 각 창고의 우유 양을 계산하도록 하더라도 같은 결과를 얻을 수 있을 겁니다. 다른 한편으로 RTOS가 시분할 방식으로 실행하도록 하면, 60초보다는 더 지나서 모든 결과를 얻게 될 겁니다. 계산을 위한 60초와 태스크를 전환하면서 소모하는 시간이 더해집니다. 참 바람직하지 않은 결과죠.
적은 수의 임베디드 시스템은 시분할 방식을 이용해서 덕을 볼 수도 있습니다. 하지만, 시스템에서 시분할 방식을 사용해야만 하는 정확한 이유가 없는 한, 시분할 방식을 사용하지 않는 것이 더 좋습니다. 지금까지의 리눅스 커널 버전의 경우도 사용자 프로그램들은 선점형 시분할 방식으로 구동되지만, 커널 모드 프로그램들은 시분할 방식으로 구동되지 않고, 한 작업이 끝나면 다른 작업을 하도록 되어 있습니다.
[RTOS 사용을 제한하는 것을 고려해야 한다.]
대부분의 RTOS들은 심지어 매우 작은 것들조차도 시스템에서 필요한 것보다는 훨씬 더 많은 서비스를 제공합니다. 많은 알토스들이 설정에 의해서 사용하지 않는 서비스를 제거할 수 있도록 하고 있기 때문에 사용하려는 시스템에 필요한 RTOS의 서비스들을 파악하고 단지 그것들만 사용하게 해서 메모리를 절약할 수 있습니다. 가령 시스템이 7개의 파이프와 하나의 큐를 사용한다면 파이프 코드와 큐 코드를 포함시켜야 합니다. 큐를 파이프로 대체해서 파이프만 8개를 사용한다면 알토스에서 큐를 지원하는 코드를 제거할 수 있습니다.
참고)
임베디드 시스템 설계 기본이론1 - 일반 동작과 짧은 루틴
다양한 분야의 임베디드 시스템 설계에 적용되는 내용들에 대해서 얘기해보겠습니다. [보통의 작동] 일반적으로 임베디드 시스템은 어떤 시간이 되거나 응답이 필요한 외부 이벤트가 발생할 때
ppojjaknews.tistory.com
임베디드 시스템 설계 기본이론2 - 태스크의 수, 우선순위 그리고 캡슐화
[태스크는 몇 개가 적당할까?] 임베디드 시스템 설계에서의 1번째 문제는 시스템의 작업들을 RTOS의 태스크로 분배하는 겁니다. 그러면 궁금증이 생기는데 "더 많은 태스크가 좋은 것인가, 더 적��
ppojjaknews.tistory.com
'IT > 임베디드 시스템' 카테고리의 다른 글
임베디드 시스템 설계 예제2 - RTOS 사용 결정과 태스크로 작업 분배 (0) | 2020.06.18 |
---|---|
임베디드 시스템 설계 예제1 - 설계의 절차와 타이밍 문제 해결 (0) | 2020.06.13 |
임베디드 시스템 설계 기본이론2 - 태스크의 수, 우선순위 그리고 캡슐화 (0) | 2020.06.11 |
임베디드 시스템 설계 기본이론1 - 일반 동작과 짧은 루틴 (0) | 2020.06.11 |
RTOS를 이용한 기본적인 임베디드 시스템 설계 - 개요 (0) | 2020.06.09 |
댓글