OPRoS Manual
Open Platform for Robotic Services ④
OPRoS 프레임워크, 컴포넌트 실행엔진
지난 2007년 지능형로봇 개발을 위한 공통기반 플랫폼 기술개발의 과제로 시작된 오프로스(OPRoS; Open Platform for Robotic Services)는 로봇소프트웨어를 컴포넌트화하여 재사용과 동일기능을 하는 컴포넌트끼리 교체가 가능하도록 해 코드의 재사용성을 극대화하고, 빠른 로봇소프트웨어 개발이 가능하도록 개발도구를 개발하는 것을 목적으로 한다. 본문은 컴포넌트를 관리하고 실행하는 역할을 수행하는 OPRoS 컴포넌트 실행엔진에 대해서 살펴보도록 한다.
OPRoS 컴포넌트 실행엔진, 범용 운영체제에서 동작하도록 구현
▲ 그림1. OPRoS 컴포넌트 실행엔진 구조
OPRoS 컴포넌트 실행엔진은 프레임워크 부분에서 가장 중요한 부분으로 컴포넌트 컴포우저에서 만들어진 컴포넌트들의 구성 파일을 활용하여 로봇에 필요한 소프트웨어 컴포넌트들을 실행하고 컴포넌트간 데이터 및 서비스 교환을 도와주는 SW 모듈이다. 컴포넌트 실행엔진은 실시간 운영체제 및 윈도우즈와 리눅스와 같은 범용 운영체제에서 동작할 수 있도록 구현되어 있어, 하나의 로봇의 동작 특성에 맞도록 다양한 운영체제가 사용될 때 쉽게 활용할 수 있도록 도와준다. 뿐만 아니라 물리적으로 분산되어 있는 경우에도 통신을 통하여 하나의 로봇처럼 동작할 수 있도록 지원하여 준다.
컴포넌트 실행엔진은 컴포넌트를 관리하고 실행하는 역할을 수행한다. 컴포넌트 실행엔진이 스레드(Thread) 관리, 자원 할당, 컴포넌트 상태 관리, 컴포넌트 실행, 실시간성 등의 다양한 서비스를 제공하여, 컴포넌트 개발자가 컴포넌트 핵심 알고리즘 개발에만 집중할 수 있도록 한다. <그림1>은 OPRoS 컴포넌트 실행엔진의 구조를 나타낸 것이다.
컴포넌트 배포기는 컴포넌트 컴포저에서 전달받은 로봇 응용 패키지를 지역 저장소에 저장한다. 그 후 응용 프로파일에 기술된 정보를 기반으로 해당 응용에 속하는 컴포넌트를 이를 컴포넌트 관리자에 등록한다. 컴포넌트 컴포저는 컴포넌트 모니터를 통해 컴포넌트의 실행상태를 모니터링 할 수 있다.
컴포넌트 관리자는 컴포넌트 프로파일 해석, 컴포넌트 인스턴스 생성, 컴포넌트 간 포트 연결, 컴포넌트 실행 등의 기능을 담당한다. 컴포넌트는 주기적 혹은 비주기적으로 실행될 수 있으며, 이러한 컴포넌트 실행 정보는 컴포넌트 프로파일에 기술되어 있다.
컴포넌트는 실행기에 의해 실행되며, 실행기는 논리적인 스레드로 간주될 수 있다. 실행기를 두는 이유는 각 컴포넌트가 자신의 스레드를 가지며 독립적으로 수행하면 부하가 너무 커지기 때문이다. 그룹으로 수행이 되거나, 수행의 방법이 비슷하다면(예를 들어 같은 주기에 의한 실행) 동일한 실행기에 의해서 관리되는 것이 효율적이다. 주기적 컴포넌트는 실행기에 의해 컴포넌트 프로파일에 기술된 주기에 따라 주기적으로 실행된다. 같은 주기를 갖는 컴포넌트는 동일한 실행기에 할당되어 실행되므로 불필요한 스레드 컨텍스트 교환(Thread Context Switching)이 방지되어 실행 성능이 향상된다. 실행기 관리자는 실행기를 생성하고 컴포넌트를 실행기에 할당하는 역할을 수행한다. <그림2>는 실행기와 컴포넌트와의 관계를 나타낸 것이다.
실행기와 컴포넌트 관계
◀그림2. 실행기와 컴포넌트의 1:N 관계
<그림2>에서 실행기1과 실행기2가 각각 2개 및 3개의 컴포넌트들을 관리하고 있는 모습을 나타내고 있다. 실행기1은 컴포넌트 1과 2를, 실행기2는 컴포넌트 3, 4, 5를 관리하고 있다. 하나의 실행기에는 다수의 컴포넌트가 참여 가능한 반면에, 하나의 컴포넌트 인스턴스(Instance)가 다수의 실행기에 참여하는 것은 허락되지 않는다. 하나의 컴포넌트가 다수의 실행기에 참여하기 위해서는 별도의 컴포넌트 인스턴스를 생성하여 참여시켜야 한다.
로봇을 포함한 제어시스템의 소프트웨어에서 흔히 쓰이는 설계 패턴으로 주기적 데이터의 처리와 비주기적 데이터 처리 등이 있다. OPRoS의 실행기는 이런 설계 패턴을 지원하는데, 주기적 혹은 비주기적으로 컴포넌트를 실행시키는 기능을 담당한다. 이러한 실행 방법은 서로 독립적으로 하나의 컴포넌트 안에서 적용될 수 있다. 예를 들면, 실시간 시스템들이 흔히 주기적 샘플링에 의한 데이터의 처리 방법을 사용하여 동작하고 있다. 주기적 실행 방법은 컴포넌트를 주기적으로 정의된 순서에 의해 실행시키게 된다. 출력 값을 내는 컴포넌트는 그 출력 값을 사용하는 컴포넌트보다 먼저 계산되어야 한다. 또한, 출력 값만 있는 컴포넌트를 가장 먼저 수행해야 한다. 실행기에는 다수의 컴포넌트를 정해진 순서대로 컴포넌트들을 수행시킨다. 동시에 수행하고 있는 컴포넌트들이 동일한 변수들을 접근하는 경우도 있을 수 있다. 이때 컴포넌트들은 주어진 시간 내에 이런 변수들을 체계적으로 처리해야 한다. 즉 다른 컴포넌트들이 실행되기 전에 이런 변수 값들이 바뀌어선 안 된다. 이를 위해 실행기는 컴포넌트의 실행을 두 단계로 나누어서 수행한다. 첫 번째 단계는 순서대로 각 컴포넌트의 onExecute() 함수를 호출하고, 두 번째 단계는 onUpdated() 함수를 수행한다. onExcute()에서 주요한 계산을 수행하고, 계산량이 많은 부분이나 상태에 변화를 가하는 것은 onUpdated()에서 수행한다.
분산 환경에서의 컴포넌트간 통신
▲ 그림3. 분산 환경에서의 컴포넌트간 통신
<그림3>은 분산되어 있는 컴포넌트들 간의 통신을 제공하는 OPRoS 컴포넌트 실행엔진의 예를 보여준다. <그림3>은 2개의 다른 노드(Node)들의 실행엔진에서 동작하는 컴포넌트간 통신 예와 한 노드에서 같이 동작하는 컴포넌트간 통신 예를 보여준다.
컴포넌트 실행엔진은 컴포넌트들을 실행시키는 실행기를 가지며, 컴포넌트는 외부와의 통신을 위해서 포트를 가진다. 그리고 각 포트와 포트를 연결하기 위해서는 커넥터가 필요하다. 특히 서로 다른 컴포넌트 실행엔진에 있는 컴포넌트 사이의 통신을 위해서 어댑터가 지원된다. 컴포넌트 실행엔진 내부에서의 컴포넌트 간 통신은 어댑터를 거치지 않는다. 커넥터는 어댑터를 거치는 것(커넥터 2)과 거치지 않는 것(커넥터 1) 두 종류가 있다. 실행엔진이 실행된 후 어댑터가 생성된다. 그리고 실행엔진이 해당 컴포넌트들을 실행시키고, 커넥터를 생성하여 각 컴포넌트간의 포트를 연결한다. 실행엔진은 실행될 때 각 컴포넌트들의 속성을 파악하여 필요한 만큼의 실행기를 실행하고 해당 컴포넌트들을 할당한다. 컴포넌트들의 노드에 대한 할당은 컴포넌트 컴포우저에 의해 할당되지만 컴포넌트들의 실행기에 대한 할당은 컴포넌트의 주기에 의해서 자동적으로 할당될 수 있다.
로봇은 여러 개의 노드로 구성될 수 있다. 로봇 내부의 구성에 따라 전체 시스템 구성도 달라지게 된다. <그림4>는 하나의 노드에 모든 컴포넌트가 구성된 경우를 나타낸 것이다.
컴포넌트 ‘가’, ‘나’, ‘다’는 해당 노드에서 구동되는 컴포넌트 실행엔진에 의해 모두 실행된다. 모든 컴포넌트가 같은 노드에 존재하므로 컴포넌트 간의 통신은 어댑터를 통하여 이루어지지 않고 바로 커넥터 간에 이루어진다. 컴포넌트 간의 데이터 전송이 빠른 시간 내에 이루어져야 할 경우 이러한 구성이 바람직하다.
▲ 그림5. 2개의 노드로 구성된 환경
<그림5>는 컴포넌트들이 2개의 노드로 분산되어 구성된 환경을 나타낸다.
컴포넌트 ‘가’, ‘나’는 노드 1에 있는 컴포넌트 실행엔진에 의해 관리되고, 컴포넌트 ‘다’는 노드 2에 있는 컴포넌트 실행엔진에 의해 관리된다. 노드 1에 있는 컴포넌트와 노드 2에 있는 컴포넌트 간의 통신은 각 노드의 컴포넌트 실행엔진 내에 존재하는 어댑터에 의해 이루어진다. 로봇내의 노드들뿐만 아니라 로봇 외부의 PC와도 구성이 가능하다. 예를 들어 외부 PC가 로봇을 원격에서 제어해야 할 경우 외부 PC와 로봇과의 연결이 가능하다. 이 경우에 외부 PC에도 컴포넌트 실행엔진을 설치하여, 로봇 내부 노드 간의 통신과 동일한 방식으로 통신이 가능하다.
컴포넌트 실행엔진의 오류 처리
로봇 서비스는 환경 변화에 따라 즉각적이고 정해진 시간 안에 처리되어야 하는 실시간성을 요구하는 경우가 많이 있다. OPRoS 플랫폼은 개발자가 실시간성에 대한 신경을 쓰지 않더라도 로봇에게 필요한 실시간성을 제공할 수 있도록 한다.
특히 OPRoS 적용 대상인 서비스 로봇이 범용 운영체제를 사용하는 경우가 많으므로, 윈도우 및 리눅스와 같은 범용 운영체제에서 개발자가 실시간성에 대한 신경을 쓰지 않더라도 운영체제에서 제공하는 최대한의 실행 성능을 제공할 수 있도록 한다.
윈도우즈의 경우 컴포넌트 실행엔진은 윈도우즈 타이머의 하나인 큐 타이머를 이용한 실행기를 제공한다. 윈도우즈는 전체적인 처리의 효율성을 중시하는 범용 운영체제이므로 다른 프로세스의 영향을 받을 수밖에 없다. 따라서 타이머 큐 타이머가 최대 1msec의 해상도를 지원한다고 하지만, 최대 해상도 1msec를 갖도록 설정한 경우라도 다른 프로세스의 영향을 받아 1msec의 해상도를 지원하지 못하고 동작 환경에 따라 그보다 높은 10msec까지의 해상도만을 지원한다.
이에 대한 성능이 <그림6>에 있다. <그림6>을 보면 어느 정도 주기를 지키고 있지만 1000번에 2번 정도 약 ±0.22ms내로 흔들리는 것(지터)을 알 수 있다. 그러나 이정도의 지터는 윈도우즈의 성능을 생각해보면 작은 것으로 생각되며, 충분히 사용할 수 있음을 알 수 있다.
리눅스 커널 버전 2.6 이후에는 시스템 타이머와는 별개의 고해상도 타이머(HRT; High Resolution Timer)가 커널에 내장되어 있다.
그리고 nanosleep() 함수가 고해상도 타이머의 지원을 받도록 변경되어 미세한 sleep이 가능하게 되었다. OPRoS 컴포넌트 실행 엔진의 리눅스 버전은 이런 미세한 해상도를 갖는 nanosleep()을 이용해서 컴포넌트를 주기적으로 실행시킬 수 있도록 개발되었다.
고해상도 타이머를 이용한 경우, 대부분의 연성 실시간 시스템에서 만족할 정도의 실시간성을 제공할 수 있지만, 보다 세밀한 시간 해상도가 필요한 경우, 커널을 선점형으로 만들고 높은 우선순위 태스크에게 보다 우수한 타이머 성능을 제공하는 Realtime Preemption 패치를 사용할 수도 있다. 이에 대한 성능이 <그림7>에 있다. 5ms 주기를 사용하더라고 지터가 약 ±0.1ms보다 적음을 알 수 있다. 리눅스 타이머의 특징으로 누적 오차를 줄이기 때문에 실제로 지터가 발생하더라도 이러한 지터가 누적되는 경우는 거의 없다.
컴포넌트 실행 중 오류가 발생할 수 있으며, 컴포넌트 실행엔진은 이러한 오류를 적절히 처리하여야 한다. 컴포넌트 실행엔진은 컴포넌트 내의 콜백 함수 실행 결과, 컴포넌트 실행 시간, 컴포넌트 상태 등을 모니터링하고, 이러한 정보를 기반으로 오류가 발생하였는지 감지한다. 만약 오류가 발생하였을 경우 이를 기록하고 오류 복구를 시도한다. 복구 방법은 해당 컴포넌트의 실행을 중지한 후, 해당 컴포넌트의 상태를 에러 상태로 변경하고 컴포넌트의 onError() 메소드를 호출하여 사용자에게 에러가 발생했음을 알린다. 그리고 가능하다면 해당 컴포넌트를 재시작한다.
필 자
한국전자통신연구원
정승욱, 장철수, 송병열, 김성훈
▲ 그림6. 윈도우즈 큐타이머를 사용한 실행엔진 성능
▲ 그림7. 리눅스 고해상도 타이머를 사용한 실행엔진 성능
▲ 그림5. 2개의 노드로 구성된 환경