안드로이드 운영체제에서 실시간 시스템
실시간 시스템의 핵심 요구사항 중 하나는 빠른 대기시간(latency)이 아니라 가장 느린 대기시간을 미리 가늠할 수 있어야(predictability) 한다는 점입니다. 즉, 아무리 짧은 응답시간을 제공하더라도 시스템 부하(load)나 입출력(I/O), 태스크 동기화(synchronization) 등에 의해 느려지거나 최대 응답시간을 예측할 수 없다면 실시간 운영체제가 아닙니다. 물론 응답시간이 가능한 짧으면서 편차가 크지 않으면 더 바랄게 없겠지만, 그렇지 못할 경우라도 운영체제가 대기시간을 어느 정도까지 보장할 수 있느냐가 더 중요합니다. 참고로 여기서 언급하는 대기시간(latency)은 인터럽트, 태스크 스케줄링, I/O 스케줄링 등 여러 문맥에서 다른 의미를 가집니다.
실시간 시스템의 또 하나의 중요한 특징은 실시간 태스크의 스케줄링을 보장하는 일입니다. 즉, 시스템에 어떤 상황이 발생하더라도 미리 지정한 실시간 태스크가 마감시간(deadline)을 지킬 수 있어야 합니다. 이를 위해 전통적인 실시간 운영체제들은 마감시간 스케줄러(deadline scheduler), 스포래딕 서버(sporadic server) 등을 이용해 이를 보장합니다.
최근 리눅스 커널과 관련된 이슈를 살펴보면 위에서 설명한 두 가지 조건을 만족하기 위한 작업이 한창이지만, 이 글에서는 안드로이드(Android) 운영체제가 스마트폰 뿐 아니라 여러 내장 시스템(embedded system)에 도입되기 위한 필수조건을 일단 정리했습니다.
일반적으로 기능적 오류가 아닌 시간적 오류, 즉 안드로이드가 실시간 시스템 설계 명세에서 제한한 마감시간(deadline)을 얼마나 잘 지원하는지를 실험한 결과를 보면[1], 측정을 크게 둘로 나뉘어, 첫번째는 하드웨어 인터럽트 이벤트가 커널 내부의 이벤트 처리 모듈에게 전달되는데까지 소요되는 대기시간(latency)을 측정한 것이고, 두번째는 커널 이벤트 처리 모듈이 이벤트를 안드로이드 달빅(Dalvik) 가상머신 위에서 동작하는 어플리케이션에게 전달하기까지 걸리는 시간을 측정합니다. 더불어 대기시간의 변동량(variation)을 관찰해 실시간 시스템에 적합한 지 여부도 확인해 봅니다. 안드로이드 하부는 리눅스 커널이므로, 이 실험은 결국 안드로이드 플랫폼에 사용되는 리눅스 커널에 대한 실험이기도 합니다.
실험 결과는 흥미로운데, 정상적인 부하가 걸릴때보다(under normal load) 아무 부하도 없을때(under no load) 대기시간이 더 들쑥날쑥 합니다. 그 원인은 아무 작업도 없을 경우 저전력모드(low power mode)로 있다가 인터럽트가 발생하면 그때서야 정상적인 상태로 돌아와 이벤트를 처리하기 때문입니다. 하지만 타이머 인터럽트 주기를 100밀리초에서 1밀리초로 변경하면, 거의 제 시간에 처리하지 못하는 결과가 발생하는 모습을 보여줍니다.
안드로이드 자바 어플리케이션의 각 쓰레드는 리눅스 pthread에 일대일로 대응합니다. 안드로이드에서 자바 쓰레드 우선순위는 10단계로 조정할 수 있는데, 이 값은 리눅스 쓰레드의 nice값으로 변환됩니다. 즉, 리눅스 커널 스케줄링 클래스 중에서 SCHED_OTHER 클래스만 사용하고 실시간 우선순위를 사용하는 SCHED_FIFO, SCHED_RR 클래스는 사용하지 않습니다.[2] 달빅(Dalvik) 가상머신은 고전적인 자바 가상머신이 실시간 시스템에서 고생하는 원인 중 하나로 지목되는 가비지 컬렉션(garbage collection)으로 인한 예측불가능한 프로세스 멈춤(freeze) 현상을 여전히 가지고 있습니다. 자체적으로 구현한 C 라이브러리(bionic)는 SystemV IPC 등과 같은 기존 프로세스간 동기화 메카니즘을 없애고 Binder라는 고유 IPC 메카니즘을 제공하는데, 태스크간 우선순위 역전(priority inversion)을 막기 위한 우선순위 상속(priority inheritance), 우선순위 한계(priority ceiling) 같은 프로토콜을 아직 제공하지 않습니다.
안드로이드는 인터럽트 핸들러가 우선순위가 더 높은 실시간 태스크를 선점하거나 지연하지 않기 위해 반드시 해결되어야 하는 리눅스 커널 인터럽트 처리 방식의 구조적 한계도 그대로 가지고 있습니다. 이를 해결하려면, 사용하는 모든 드라이버의 인터럽트 핸들러를 리눅스 2.6.30 버전부터 추가된 쓰레드 방식으로 바꾸거나 PREEMPT_RT 패치를 적용해야 합니다.
그래서 결론은, 역시 아직 안드로이드 운영체제는 실시간 시스템에 적합하지 않지만, 이를 보완하기 위한 많은 작업과 연구가 더 필요합니다.
[1] Bhupinder S. Mongia, Vijay K. Madisetti, Reliable Real-Time Applications on Android OS [2] Claudio Mia, Luis Nogueira, Evaluating Android OS for Embedded Real-Time Systems