본문 바로가기
IT/임베디드 시스템

임베디드 시스템 설계 예제3 - 시스템/공유데이터 처리 그리고 결론

by 뽀짝뉴스 2020. 6. 18.

 

예제의 마지막 파트입니다. 남은 내용의 볼륨이 크지 않아서 평소보다 짧으니 빠르게 함께 달려봅시다. 마찬가지로 같은 예제에서 이어져진행됩니다.

 

 

[시스템의 진행에 대하여]

 

임베디드 시스템이 어떤 것을 처리하는 가장 일반적인 방식은 인터럽트 루틴이 태스크에게 어떤 일을 하도록 알리는 시그널을 보내는 것이라고 말한적이 있었죠. 이 시스템에서는 이런 것이 어떤 식으로 동작할까요?

 

사용자가 버튼을 누를 때마다, 버튼 하드웨어는 마이크로프로세서에게 인터럽트를 발생시킵니다. 버튼 인터럽트 루틴은 메시지를 버튼 처리 태스크에 보내고, 태스크는 메시지를 해석해서 필요하면 디스플레이 태스커와 프린터 태스크에 메시지를 전달합니다.

 

자꾸 얘기했다고 말하지만 어쩔 수 없이 계속 끄집어 오자면 시스템이 부표의 레벨을 읽고 넘치는 지를 조사해야 할 시간이 되었을 때, 넘침 감지 태스크에 명령을 내리기 위해서 시스템은 타이머를 가지고 있어야 합니다.

 

타이머는 인터럽트를 발생시킬 것이고, 타이머 인터럽트 루틴은 작업을 시작하도록 메시지를 넘침 감지 태스크에 전달할 것입니다.

 

사용자가 보고서를 인쇄하길 원할 때, 인쇄 양식 태스크는 보고서의 첫 번째 줄을 프린터에 보냅니다. 그 후에 프린터가 각 줄의 인쇄를 마쳤을 때 프린터는 인터럽트를 발생시킵니다. 그러면 인터럽트 루틴은 다음 줄을 프린터 하드웨어에 보냅니다. 모든 줄의 인쇄가 끝났을 때, 인터럽트 루틴은 메시지를 다시 인쇄 양식 태스크에 보내서 프린터가 다음 보고서를 인쇄할 준비가 되었음을 알리는 것이고요.

 

태스크가 부표의 레벨을 읽을 필요가 있을 때마다 태스크는 하드웨어를 설정해야 합니다. 부표의 레벨을 읽었을 때 하드웨어 인터럽트를 발생시킵니다. 인터럽트 루틴은 새로운 부표의 레벨 값을 필요한 태스크에 보내줍니다.

 

 

[공유 데이터의 처리는?]

 

가솔린의 레벨 데이터는 몇몇의 태스크들 사이에서 공유됩니다. 레벨 계산 태스크는 가솔린의 레벨 계산을 수행하고 유출을 감시하기 위해 레벨 데이터를 사용하고, 디스플레이 태스크는 사용자에게 알리기 위해서 레벨 데이터를 읽고, 인쇄 양식 태스크는 인쇄하기 위해서 보고서를 만들 때 그 데이터를 사용합니다. 공유 데이터를 보호하기 위해서 세마포어를 사용해야 할까요 아니면 별도의 태스크를 만들어 공유데이터의 일관성을 유지하도록 관리해야 할까요. 물어본 것에 대한 두 가지 핵심 질문은 얼마나 오랫동안 태스크가 세마포어를 가질 수 있는가와 다른 태스크들이 그 시간 동안 기다릴 수 있는가가 되겠죠. 첫번째 질문에 대해서 생각해볼까요. 레벨 계산 태스크는 새로운 레벨로 데이터를 갱신하고 유출이 발생했는지를 결정합니다. 매우 느린 마이크로프로세서를 사용한다고 하더라도 새로운 레벨로 갱신하는 것은 수 마이크로세컨드면 충분합니다. 전에 얘기한 것처럼 유출 감지는 수 밀리세컨드 정도 소요됩니다. 태스크가 세마포어를 필요로하는 시간은 결국 수 밀리세컨드 정도일테니까요. 디스플레이 태스크는 단지 하나의 탱크의 레벨을 필요로 합니다. 다시 말해서 수 마이크로세컨드면 충분하죠. 인쇄 양식 태스크만 약간 오래 세마포어를 필요로 할겁니다. 만약 문제가 발생한다는 것이 밝혀지면 필요한 데이터를 미리 복사합니다. 이렇게 하는 건 거의 시간이 걸리지 않거든요, 그리고 보고서를 만드는 동안에 세마포어를 풀어줄 수 있습니다. 그러므로 첫 번째 질문에 대한 답은 그리 오래걸리지 않는다! + 아마도 대부분 1~2 밀리세컨드일 것임 이 될 겁니다. 태스크들은 수 밀리 세컨드 정도는 기다리는게 가능하기 때문에 두 번째 질문에 대한 답은 그렇다 입니다. 그래서 복잡한 추가 태스크를 사용할 필요 없이 세마포어로 공유 데이터를 처리할 수 있습니다. 하지만 이 논의는 데이터를 모두 전역 변수로 만들어서 시스템에 있는 어떤 코드도 직접 사용하라는 말은 아닙니다. 별도의 분리된 모듈로 데이터를 숨겨서 각 태스크가 데이터를 추가하거나 이용하기 위해서 기 모듈의 함수를 호출 하도록 하는 것이 여전히 좋은 소프트웨어 설계라고 할 수 있겠죠. 특히 그런 함수들은 세마포어를 사용할 필요가 있기 때문에 그에 맞는 별도의 모듈을 만들어야만합니다.

 

태스크들의 생성 이유를 정리해서 살펴봅시다.

 

- 레벨 계산 태스크 : 낮은 순위, 다른 작업은 이 태스크 보다 우선 순위가 더 높아야 하고, 이 계산은 마이크로 프로세서를 소진시킴.

- 넘침 감지 태스크 : 높은 순위, 이 태스크는 탱크가 넘치는지를 결정함. 이 태스크는 재빠르게 수행되어야 하는 것이 중요함.

- 버튼 처리 태스크 : 높은 순위, 이 태스크는 사용자 인터페이스를 처리하기 위해 스테이트머신을 제어해야함. 복잡한 버튼 인터럽트 루틴을 처리하지만, 응답은 빨라야함

- 디스플레이 태스크 : 높은 순위, 다양한 태스크들이 디스플레이를 사용하기 때문에, 이 태스크는 교통정리를 해야 함.

- 인쇄 양식 태스크 : 중간 순위, 인쇄 양식 처리는 버튼 응답에 영향을 줄 정도로 시간이 걸리는 작업임. 또한, 별도의 분리된 태스크로 인쇄 큐의 처리를 단순화 시킴.

 

 

[그래서 결과적으로]

 

이 시스템을 위해서 만든 태스크들을 열거하고 각 태스크의 존재 이유 또한 열거해봤습니다. 태스크 하드웨어, 인터럽트 루틴들 사이의 메시지 흐름을 보여주고 이 시스템이 가지고 있는 약간의 추가적인 중요한 모듈을 나타내는 그림이 있다고 가정해봅시다. 연이은 내용에서 디버깅을 위한 설께와 코딩에 관해 논의해보고 이 시스템에 대한 코드를 또 볼 수 있을겁니다.

 

예제를 말할 때 봤듯이, 이 설계는 시스템을 위해 가능한 유일무이한 설계가 아닙니다. 어떤 변경에 대한 주장도 가능하고 여러가지 주어진 상황에 대해 더하거나 빼는 등 어떤 변경에 대한 의견을 내도 합당할 수 있습니다. 가령 버튼 처리 태스크와 디스플레이 태스크를 하나로 통합 하면 좋지 않다는 것이 무조건 맞다고 하긴 힘듭니다. 두 태스크는 같은 우선 순위가 할당되어 있고, 버튼 처리 스테이트 머신이 RTOS의 메시지 큐를 사용하지 않고 직접 디스플레이에 접근하면 아마 코드가 더 간단해질 가능성도 있죠. 반대로 분리된 채로 놔두는 것이 더 낫다는 주장도 당연히 가능하고요. RTOS를 설계하는 것은 상식과 좋은 소프트웨어 경험으로 된 주문을 외우면서 다양한 것들을 우려내는 것과 유사합니다.

댓글