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

세마포어의 용도/종류/문제 그리고 공유데이터 보호 방법

by 뽀짝뉴스 2020. 6. 3.

 

앞의 내용에 바로 이어서 세마포어의 다양한 면에 대해서 얘기해보겠습니다.

 

 

[다중 세마포어]

 

세마포어 함수들이 초기화되거나, 내려가거나, 올라간 세마포어를 인식하기 위해서 인자 값을 사용하는 것을 알게 되었을 거라고 생각합니다. 대부분의 RTOS는 원하는 만큼 세마포어를 가질 수 있도록 하기 때문에 각각의 세마포어 함수들은 사용하려는 세마포어를 인식해야만 합니다. 모든 세마포어들은 서로 독립적입니다. 한 태스크가 세마포어 A를 가지고 있다면 다른 태스크는 세마포어 B를 기다리지 않고 가질 수 있습니다. 같은 맥락에서 한 태스크가 세마포어 C를 기다리고 있다고 하면 다른 태스크가 세마포어 D를 풀어 줘도 여전히 기다려야 하는 것이지요.

 

다중 세마포어를 사용하는 장점은 뭘까요? 태스크가 세마포어를 가질 때마다, 잠재적으로 같은 세마포어를 사용하는 다른 태스크의 응답이 느려집니다. 하나의 세마포어만 사용하는 시스템에서 가장 낮은 우선순위의 태스크가 온도들의 공유 배열에서 데이터를 변경하기 위해 세마포어를 가지고 있다면 가장 높은 우선순위의 태스크는 온도에 대해서는 신경 쓰지 않고 에러의 개수를 변경시키려고 해도 그 세마포어를 기다려야만 합니다. 한 세마포어는 온도를 보호하고 다르 세마포어는 에러 개수를 보호하도록 해서, 가장 낮은 태스크가 온도를 보호하기 위해 세마포어를 가지고 있는 동안에도 가장 높은 태스크가 에러 개수를 변경할 수 있도록 만들 수 있습니다. 다른 종류의 세마포어는 다른 종류의 공유 자원에 대응할 수 있습니다.

 

RTOS는 어떻게 어떤 세마포어가 어떤 데이터를 보호하는지 알 수 있을까요? 정답은 알 수 없다입니다. 무책임한 말 같지만 다중 세마포어를 사용한다면, 어떤 세마포어가 어떤 데이터에 대응하는지 기억하는 것은 프로그래머가 전적으로 담당해야 할 책임입니다. 에러 개수를 변경시키는 태스크는 그에 대응하는 세마포어를 반드시 가져야 합니다. 프로그래머는 각각의 세마포어들이 어떤 공유 데이터를 보호하는지 결정해야만 하고요.

 

 

[신호를 전달하는 장치로서의 세마포어]

 

또 다른 세마포어의 용도는 한 태스크에서 다른 태스크로 또는 인터럽트 루틴에서 태스크로의 간단한 통신을 하는 것입니다. 가령 출력될 보고서의 형태를 만드는 태스크가 고정된 메모리 버퍼에 보고서를 만들고 있다고 생각해봅시다. 또한 프린터는 각각의 줄이 끝나는 시점에서 인터럽트가 걸리고, 프린터 인터럽트 루틴은 인터럽트가 걸릴 때마다 프린터에게 다음 줄을 공급한다고 가정해볼게요. 그런 시스템에서는 한 보고서를 고정된 버퍼에 형태를 맞추어 만든 후에 인터럽트 루틴이 그 보고서를 다 출력할 때까지 태스크는 다음 보고서를 만드는 것을 기다려야만 합니다.

 

이렇게 하는 쉬운 방법 중에 하나는 태스크가 각각의 보고서를 만든 후에 세마포어를 기다리는 겁니다. 인터럽트 루틴은 보고서를 프린터에 모두 넘겨줬을 때, 세마포어를 풀어줘서 태스크에게 신호를 보냅니다.

 

 

[세마포어 문제]

 

처음 세마포어에 대해서 볼 때는 모든 공유 데이터 문제를 해결할 수 있는 것처럼 보입니다. 하지만 그렇지 않다는 것도 금방 알 수 있죠. 솔직히 세마포어를 조금 사용하면 조금 사용할수록 시스템은 더 잘 동작합니다. 프로그래머가 세마포어를 완벽하게 사용할 때만 세마포어는 제대로 작동합니다. 여기가 문제죠, 프로그래머가 완벽하게 사용할 것이라는 보장은 어디에도 없습니다. 세마포어를 혼란스럽게 하는 알려진 방법만 해도 무수히 존재하고요.

 

세마포어를 가지는 것을 잊어버리는 경우. 모든 공유 데이터에 접근하는 태스크는 세마포어를 사용해야만 제대로 동작합니다. 만약 세마포어를 가져오는 것을 잊어버리면, RTOS는 세마포어를 가져오는 것을 잊어버린 태스크로부터 다른 태스크로 전환되고 악랄한 공유 데이터 문제가 발생하게 될 겁니다.

 

세마포어를 풀어주는 것을 잊어버리는 경우. 어떤 태스크가 세마포어를 풀어주는 것을 놓친다면 모든 다른 그 세마포어를 사용하려는 태스크들은 대기 상태로 되고 영원히 그 상태에서 벗어나지 못하게 됩니다.

 

잘못된 세마포어를 가지는 경우. 다중 세마포어를 사용할 때 잘못해서 다른 세마포어를 가지게 되다면 세마포어를 가지는 것을 잊어버리는 것과 같은 일이 발생합니다.

 

너무 오랫동안 세마포어를 가지고 있는 경우. 한 태스크가 세마포어를 가질 때마다, 그 세마포어를 원하는 다른 태스크들은 세마포어가 풀어질 때까지 기다려야만 합니다. 한 태스크가 너무 오랫동안 세마포어를 가지고 있다면 다른 태스크들은 신시간 제한 시간을 어길 수도 있게 됩니다.

 

RTOS가 세마포어를 가지고 있는 낮은 우선순위의 태스크로부터 중간 우선순위의 태스크로 전환하는 경우. 이런 세마포어 문제 중의 한 예가 발생할 수 있습니다. 세마포어를 원하는 높은 우선순위의 태스크는 중간 태스크가 마이크로프로세서를 포기할 때까지 기다려야만 합니다.

 

나중 태스크는 마이크로프로세서를 돌려받기 전까지 세마포어를 풀어줄 수밖에 없습니다. 아무리 주의해서 작성하더라도 중간 태스크는 세마포어를 풀어 줄 수 없게 할 수 있고, 결국 우선적인 태스크는 무작정 기다려야 하게 됩니다. 이런 문제는 우선순위 역전이라고도 불립니다. 어떤 RTOS는 우선순위 계승이라는 방법으로 이런 문제를 해결합니다. 그런 RTOS는 나중 태스크가 세마포어를 가지고 있고 먼저의 태스크가 기다리고 있을 때마다, 임시로 마지막 태스크의 순서를 맨 처음으로 끌어올립니다.

 

 

[변형된 세마포어들]

 

세상에는 많은 다른 종류의 세마포어들이 있습니다. 여기에서 약간이라고 함은 보통의 변형들에 대해서 말합니다. 어떤 시스템은 여러 번 얻어질 수 있는 세마포어를 제공합니다. 필수적으로 그러한 세마포어는 정수로 되어있는데요. 그런 세마포어를 얻는 건 정수를 감소시키고 풀어줄 때마다 정수가 늘어납니다. 정수가 0이 되었다면 태스크가 그 세마포어를 얻으려고 하는 순간 그 태스크는 대기 상태가 될 겁니다. 이런 세마포어는 카운팅 세마포어라고 불리고 세마포어의 원형이었다고 알려져 있습니다.

 

어떤 시스템은 세마포어를 획득한 태스크만이 풀어 줄 수 있는 세마포어를 제공합니다. 이런 세마포어는 공유 데이터 무제에 유용하긴 한데 두 태스크 사이의 통신을 위해서 사용될 수는 없습니다. 이런 세마포어를 종종 리소스 세마포어 or 리소스라고 부릅니다.

 

또 어떤 RTOS는 우선순위 역전 문제를 자동으로 해결할 수 있는 종류의 세마포어와 해결할 수 없는 세마포어를 제공합니다. 전자의 세마포어는 뮤 택스 세마포어 또는 뮤 택스라고 불립니다. 다른 RTOS 역시도 뮤택스라고 불리는 세마포어를 제공하기는 하는데 우선순위 역전 문제를 해결하지는 못합니다.

 

만약에 여러 태스크가 세마포어를 기다리고 있을 때 세마포어를 풀어준다면 시스템은 어떤 태스크가 세마포어를 갖게 할지 다양하게 결정합니다. 어떤 시스템은 가장 오래 기다린 태스크에게 세마포어를 줄 것이고 다른 시스템은 가장 우선순위가 높은 태스크에게 세마포어를 줄 것입니다. 또 어떤 시스템은 프로그래머에게 선택권을 주기도 하고요.

 

 

[공유 데이터를 보호하는 방법들]

 

공유 데이터를 보호하는 방법을 두 가지로 정리해보면 1. 인터럽트를 금지시키는 것 2. 세마포어를 사용하는 방법입니다. 여기에 언급할 가치가 있는 세 번째 방법도 있습니다. 태스크 전환을 금지시키는 방법입니다. 대부분의 RTOS는 호출할 수 있는 두 개의 함수를 가지고 있습니다. 하나는 태스크 전환을 금지시키는 것이고 다른 하나는 금지된 태스크 전환을 다시 가능하게 하는 것입니다. 쉽게 눈치채겠지만 공유 데이터를 읽고 쓰는 동안 부적절한 태스크 전환을 금지시켜서 공유 데이터를 보호할 수 있습니다.

 

방법을 비교해보면 다음과 같습니다.

 

1. 인터럽트를 금지시키는 것은 시스템의 모든 인터럽트 루틴과 다른 태스크들의 응답 시간에 영향을 끼치는 가장 극단적인 조치입니다. 다른 측면에서는 인터럽트를 금지시키는 것은 장점도 가지는데요. 첫째, 데이터가 태스크 코드와 인터럽트 루틴 사이에서 공유된다면, 인터럽트 금지가 유일한 방법임. 인터럽트 루틴은 세마포어를 가질 수 없고 태스크 전환을 금지시키는 것은 인터럽트 자체를 금지시킬 수는 없습니다. 둘째, 대부분의 프로세서에서 인터럽트를 금지시키거나 다시 가능하게 하는 것은 한 명령어로 처리됩니다. 모든 RTOS의 함수들은 많은 명령어로 이루어져 있습니다. 만약 태스크가 공유 데이터에 매우 짧은 시간 동안 접근한다고 하면, 변수 하나의 값을 증가시키는 등 때때로 이 방법은 세마포어나 태스크 전환을 금지시킨 것보다 인터럽트 서비스 응답에 더 짧은 시간 동안의 영향을 줍니다.

 

2. 세마포어를 이용하는 것은 같은 세마포어를 원하는 태스크에만 영향을 미치기 때문에 데이터를 보호하는데 가장 정확한 방법입니다. 그 세마포어를 필요로 하지 않는 인터럽트 루틴과 태스크 들은 전혀 영향을 받지 않습니다. 다른 면에서는 세마포어는 대부분의 RTOS에서는 굉장히 작은 시간이지만, 어느 정도 마이크로프로세서의 시간을 소비하기는 합니다. 그리고 인터럽트 루틴에는 적용할 수 없고요.

 

3. 태스크 전환을 금지하는 방법은 어떤 면에서는 두 방법의 중간에 있습니다. 이 방법은 인터럽트 루틴에는 전혀 영향을 끼치지 않는데, 모든 다른 태스크의 응답은 정지시킵니다. 

 

 

참고)

 

RTOS 문제 해결 툴 - 세마포어

RTOS가 태스크와 태스크 간의 마이크로프로세서 전환과 인터럽트 같은 실행 순서의 변경으로 인해 어떻게 새로운 종류의 공유 데이터 문제를 일으킬 수 있는지 말해봤었죠. 문제가 있다면 해결��

ppojjaknews.tistory.com

 

댓글