RTOS가 태스크와 태스크 간의 마이크로프로세서 전환과 인터럽트 같은 실행 순서의 변경으로 인해 어떻게 새로운 종류의 공유 데이터 문제를 일으킬 수 있는지 말해봤었죠. 문제가 있다면 해결법이 있는 법. RTOS는 이런 문제를 해결할 수 있는 새로운 툴 역시 제공합니다. 그 중 하나가 세마포어입니다.
세마포어의 어원을 찾아 올라가면 오래된 때로 올라가야 합니다. 과거 철도에서는 기차들이 서로 충돌하면 사업에 대단히 안좋을 것임이 명백하기 때문에 걱정이 쌓여갔습니다. 그래서 철도의 까치발 신호기로 신호를 사용했는데 이것의 이름이 세마포어입니다. 1번 기차가 가운데 영역에 들어오면 바로 전에 있는 세마포어가 자동으로 내려갑니다. 2번 기차가 도착할 때 기차의 승무원은 세마포어가 내려가 있는 것을 보고 세마포어가 올라갈 때까지 기차를 정지시킵니다. 1번 기차가 가운데 영역을 벗어날 경우 세마포어는 올라가고 2번 기차의 승무원은 앞으로 진입해도 안전하다는 것을 알게 됩니다. 그런경우 2번 기차가 1번 기차와 충돌할 위험이 없어집니다. RTOS에서도 세마포어에 대한 개념은 철도에서 역할과 같습니다.
기차들은 세마포어로 두 가지 일을 수행합니다. 첫번째는 기차가 보호된 영역을 벗어 날 때 세마포어를 올립니다. 둘째는 기차가 세마포어가 있는 곳까지 왔을 때 세마포어가 올라갈 때까지 기다리거나 올라와 있으면 통과하고 세마포어를 내립니다. RTOS에 있는 일반적인 세마포어도 동일한 일을 한다고 봐야합니다.
[RTOS에서의 세마포어]
세마포어라는 용어 자체는 독특한 개념에서 따온 것이긴 한데, 지금의 임베디드 시스템의 세상에서는 대단히도 파악하기 힘든 것 중의 하나가 되었습니다. 이 용어는 소프트웨어 엔지니어의 수 만큼, 또는 적어도 RTOS의 수 만큼 서로 다른 것들을 의미하는 것 같습니다. 심지어 어떤 RTOS는 한 종류 이상의 세마포어를 가지고 있을 정도라고 말할 수 있죠. 게다가 어떤 RTOS도 기차에서 처럼 올린다 내린다와 같은 용어를 사용하지 않습니다. 대신에 얻다 주다 가져오다 풀어주다 대기하다 게시하다 p, v, 기다리다 등 기타 셀 수 없이 많은 조합을 사용한다는 장점이 있습니다. 물론 복잡하지만요. 여기서는 내린다와 올린다를 가져오다와 풀어주다로 사용하겠습니다. 먼저 다룰 것은 철도 세마포어와 가장 비슷한 이진 세마포어라고 불리는 종류입니다.
일반적인 RTOS 에서의 이진 세마포어는 이렇게 동작합니다. 태스크들은 Take Semaphore(이하 TS)와 ReleaseSemaphore(이하 RS) 두 개의 함수를 호출합니다. 한 태스크가 세마포어를 가져오기 위해서 TS를 호출하고 풀어주는 함수 RS를 호출하지 않는 경우에는 다른 태스크들이 TS를 호출하면 첫 번째 태스크가 RS를 호출해서 세마포어를 풀어주기 전까지 기다려야만 합니다. 한번에 오직 한 태스크만 세마포어를 가질 수 있다는 것이죠.
레밸태스크는 구조체에 있는 데이터를 갱신하기 전에 세마포어를 가져오기 위해서 TS를 호출합니다. 만약 레벨 태스크가 데이터를 갱신하고 있고 여전히 세마포어를 가지고 있다면 사용자가 버튼을 누를 때 이어지는 이벤트가 순차적으로 발동하게 됩니다.
1) RTOS는 이전과 같이 레밸 태스크를 준비 상태로 만들고 버튼 태스크로 전환함
2) 버튼 태스크가 TS를 호출해서 세마포어를 가지려고 하면, 레벨 태스크가 이미 세마포어를 가지고 있기 때문에 버튼 태스크는 세마포어가 풀어질 때까지 기다려야함
3) RTOS는 실행할 다른 태스크를 찾아볼 때, 레밸 태스크가 여전히 준비 상태인 것을 발견한다. 버튼 태스크가 대기 상태이기 때문에, 레벨 태스크는 실행되고 결국 세마포어를 풀어줌
4) 레벨 태스크가 RS를 호출해서 세마포어를 풀어주면 버튼 태스크는 더 이상 기다릴 필요가 없고, RTOS는 다시 버튼 태스크로 전환됨
이럴 때는 각각의 태스크 내부에서 C 구문의 순차적인 실행이 나타납니다. 이런 순차 실행의 결과로 레밸 태스크는 버튼 태스크가 데이터를 사용하기 전에 갱신을 마칠 수 있습니다. 버튼 태스크가 반만 바뀌었을 때 데이터를 읽는 경우는 발생하지 않고요.
원자로 감시 시스템을 예시로 들어보겠습니다. 인터럽트 루틴에서 온도를 읽지 않고 태스크에서 온도를 읽게했습니다. OS라는 이름으로 시작하는 함수나 데이터 구조는 C/OS에서 사용되는 것들입니다. OSSemPost와 OSSemPend 함수는 세마포어를 올리거나 내립니다. OSSemCreate 함수는 세마포어를 초기화하고, 앞의 두 함수가 사용되기 전에 호출되어야 합니다. OS_EVENT 구조체는 세마포어를 나타내는 데이터를 저장하고 있고 RTOS가 구조체 전체를 관리한다. OSSemPend 함수의 WATI_FOREVER 인자는 호출하는 태스크가 영원히 세마포어를 기다린다는 것을 의미합니다. 이에 대한 자세한 얘기는 또 할 기회가 있을겁니다. OSTimeDly 함수는 vReadTemperatureTask 가 약 1/4초 동안 기다리게 만듭니다. 단순히 정해진 시간이 지나면 대기에서 해제되고요. 따라서 이 태스크는 매 1/4초 마다 깨어나서, 두 온도들을 읽고, 배열에 그 값을 넣는겁니다. 그 동안, vControlTask는 계속 두 곳의 온도가 같은지 점검합니다.
[세마포어의 초기화]
버그는 vReadtemperatureTask가 세마포어를 사용하기 위해서 OSSemPend를 호출하기 전에 반드시 먼저 실행되어야 하는 OSSemCreare 를 호출 할 때 발생합니다. 진짜로 OSSemCreate가 먼저 실행된다고 생각한다면 착각입니다. vReadtemperatureTask가 OSSemPend를 호출하기 전에 처음에 OSTimeDly를 호출하기 때문에 vControlTask는 OSSemCreate를 호출할 충분한 시간을 가지고 있다고 주자하고 싶은 것이겠지요. 그렇게 말하는 것을 자유긴하지만 그런 것에 의존하는 임베디드 코드를 작성항다면 일하는 생활 대부분을 알지도 못하는 버그를 찾는데 보낼 가능성이 큽니다. vReadtemperatureTask의 모든 지연 시간을 차지해 버리는 높은 우선 순위의 어떤 태스크가 현재 존재하지 않거나 앞으로도 존재하지 않을 것이라고 어떻게 확신할 수 있을까요.
또 다른 대안으로는 vControlTask를 vReadtemperatureTask 보다 우선 순위를 높게 주어서 확실히 동작하도록 하는 것이 가능하다고 주장할지도 모릅니다. 이건 어떤 이유로 vReadtemperatureTask를 vControlTask보다 더 높은 순위를 줘야만 해서 현재 위와 같은 시한 폭탄이 코드에 숨겨져 있다는 것을 모르는 누군가가 코드를 건들이지 않고 놔둘 때까지만 맞는 한정적인 해답입니다. 알게됐다면 이제 모른척 하지맙시다 세마포어를 초기화 시키는 OSSemCreate에 대한 호출을 가장 먼저 실행한다고 보장된 초기 실행 코드에 위치시킵시다. OSStart를 호출 하기 전 가장 적당한 장소이니까요.
'IT > 임베디드 시스템' 카테고리의 다른 글
메시지큐 / 메일박스 : 또 다른 수단 (0) | 2020.06.06 |
---|---|
세마포어의 용도/종류/문제 그리고 공유데이터 보호 방법 (0) | 2020.06.03 |
태스크의 독자적인 데이터 구조에 대해서 (0) | 2020.06.03 |
알토스의 기본단위 태스크(Task)를 알아보자 (0) | 2020.06.03 |
RTOS에 대한 기본적인 이해와 개요 (0) | 2020.06.03 |
댓글