[인사이드 안드로이드]
Chapter5 - Zygote
Zygote란 무엇인가?
Zygote의 사전적 의미는 ‘분할 전의 세포나 수정란’이다. 쉽게 말하면 개체가 생성되기 이전의 불완전한 상태.
안드로이드 시스템에서 새로운 애플리케이션을 실행하면 실행에 필요한 요소들을 미리 준비해 둔 Zygote 프로세스와 새로운 애플리케이션이 결합되서 실행된다.
Zygote 프로세스는 실행되면서 달빅(Dalvik) 가상 머신을 초기화하고 구동시킨다. 안드로이드 애플리케이션은 자바로 작성돼 있어 리눅스 상에서 네이티브 프로세스로 실행될 수 없으며 달빅 가상 머신에서 동작한다. 각 안드로이드 애플리케이션은 독립적인 가상 머신 위에서 동작하는데, 실행될 때 마다 자신이 동작할 가상 머신을 초기화하고 실행하는 과정에는 많은 시간이 소요되며 애플리케이션의 실행을 느리게 하는 요인이 된다. 때문에 안드로이드에서 Zygote 프로세스는 애플리케이션이 실행되기 전에 실행된 가상 머신의 코드 및 메모리 정보를 공유함으로써 애플리케이션이 실행되는 시간을 단축시킬 수 있다. 여기에 더해 안드로이드 프레임워크에서 동작하는 애플리케이션이 사용할 클래스와 자원을 미리 메모리에 로딩해 두고 이러한 자원에 대한 연결 정보를 구성한다.
Zygote 프로세스를 사용하는 목적으로는 애플리케이션의 생성 속도를 빠르게 해서 한정된 자원을 효율적으로 사용해 사용 시간을 늘리기 위함이다.
Zygote를 통한 프로세스의 생성
Zygote 프로세스는 init 프로세스가 시스템 구동에 필요한 각종 데몬을 실행하고 난 뒤 실행된다. Zygote 프로세스가 실행된 이후에는 안드로이드 서비스 및 애플리케이션은 Zygote 프로세스를 통해 실행된다.
일반적인 리눅스 시스템에서 새로운 애플리케이션을 실행하는 과정은 위와 같다.
부모 프로세스 A는 fork() 시스템 콜을 호출하여 새로운 자식 프로세스 A’를 생성한다. 새로 생성된 프로세스 A’는 부모 프로세스인 프로세스 A의 메모리 구성 정보 및 공유 라이브러리에 대한 링크 정보를 공유한 상태이다. 다음으로 자식 프로세스 A’는 exec(‘B’) 시스템 콜을 호출해 새로운 프로세스 B의 코드를 메모리로 로딩한다. 이때 부모 프로세스 A의 메모리 정보는 지워지고 로딩된 B를 실행하는 데 필요한 메모리를 새롭게 구성한 후 프로세스 B가 사용한 공유 라이브러리에 대한 링크 정보를 새로 구성한다.
안드로이드에서는 COW(Copy On Write)를 통해 기존에 이미 메모리 상에서 동작중인 프로세스의 재사용성을 극대화하고 공유 라이브러리를 통해 메모리 사용량(foot print)을 최소화한다.
COW(Copy On Write)란 프로세스를 생성할 때 새로 생성된 자식 프로세스는 부모 프로세스와 메모리 공간을 공유하는데, 메모리 복사를 하는 것은 오버헤드가 매우 크기 때문에 생성된 자식 프로세스가 부모의 메모리 공간을 참조만 할 경우에는 이를 복사하지 않고 부모의 메모리 공간을 공유하게 된다. 공유하는 메모리 정보를 자식 프로세스가 수정하는 시점에서 부모 프로세스의 메모리 정보를 자신의 메모리 공간으로 복사하는 것이 바로 COW 기법이다.
Zygote 프로세스는 fork() 시스템 콜을 호출해 자식 프로세스인 Zygote’ 프로세스를 생성한다. 생성된 Zygote’ 프로세스는 부모인 Zygote 프로세스의 코드 영역과 링크 정보를 공유한다. 새로운 안드로이드 애플리케이션 A는 fork()를 통해 생성도니 프로세스의 코드 영역을 새롭게 로딩하는 것이 아니라, 복제된 달빅 가상 머신 위에 동적으로 로딩된다. 이후에 zygote’ 프로세스는 애플리케이션 A 클래스의 메서드로 실행 흐름을 넘겨 안드로이드 애플리케이션이 동작하게 된다. 새로 생성된 애플리케이션 A는 기존의 Zygote 프로세스가 구성해 놓은 라이브러리 및 리소스에 대한 링크정보를 그대로 사용하기에 빠르게 실행된다.
다음은 Zygote가 실행되고 나서 새로운 안드로이드 애플리케이션 A가 실행되기까지의 과정을 나타낸다.
Zygote는 달빅 가상 머신을 초기화하고, 필요한 클래스와 자원을 메모리에 로딩한다. fork()를 통해 새로 생성된 Zygote’ 프로세스는 마지막 단계에서 새로 실행되는 안드로이드 애플리케이션 A를 동적으로 로딩하고 실행한다. 실행되는 애플리케이션 A는 Zygote가 미리 초기화하고 실행한 달빅 가상 머신의 코드를 그대로 사용하며, 미리 메모리에 로딩해둔 클래스와 자원을 사용함으로써 빠르게 실행된다.
app_process로부터 ZygoteInit class 실행
Zygote는 자바로 작성돼 있으므로 다른 네이티브 서비스나 데몬과 같이 init 프로세스에서 바로 실행할 수 없다. 자바로 작성돼 있는 Zygote 클래스가 동작하려면 달빅 가상 머신이 생성돼야 하고, 생성된 가상 머신 위에서 Zygoteinit 클래스를 로딩하고 실행해야 한다. 이러한 작업을 수행하는 프로세스가 바로 app_process다.
/System/bin/app_process의 소스 코드에 있는 main() 함수에서 AppRuntime 객체를 생성한 후 main() 함수에 전달된 인자 값을 분석해서 AppRuntime 객체에 전달한다. 그러고 나서 달빅 가상 머신을 초기화하고 생성한 다음 마지막으로 ZygoteInit 클래스의 main() 메서드를 호출한다.
ZygoteInit 클래스의 기능
그림은 ZygoteInit::main() 메서드의 기능을 요약했다.
main() 메서드에서는 새로운 안드로이드 애플리케이션을 실행하기 위한 요청을 수신하는 데 쓸 소켓을 바인딩한다. 안드로이드 애플리케이션 프레임워크에서 사용할 클래스와 리소스를 로딩하고 SystemServer를 실행한다. 시스템 서버는 안드로이드 플랫폼에 필요한 주요 네이티브 서비스들을 실행하게 된다. 이후에 등록된 UDS를 모니터링하고 있다가 새로운 안드로이드 애플리케이션의 생성 요청을 받으면 이를 처리하는 루프로 진입한다.
ZygoteInit 클래스는 /dev/socket/zygote에 생성된 유닉스 도메인 소켓을 사용해서 ActivityManager로부터 전달되는 새 안드로이드 애플리케이션의 생성 요청 메시지를 수신한다. 이 소켓은 init 부팅 과정에서 프로세스에 의해 생성되며, init.rc에 기술돼 있다.
또한 ZygoteInit 클래스는 preloadClasses()와 preloadResources() 메서드를 호출한다. 각 메서드는 안드로이드 애플리케이션 프레임워크에 포함되는 클래스와 아이콘, 이미지, 문자열 등의 자원을 미리 메모리에 로딩하고 로딩한 클래스와 자원에 대한 연결 정보를 생성한다. 이후 새로 생성되는 안드로이드 애플리케이션에서는 미리 로딩한 클래스나 자원을 이용할 때 새로 연결정보를 생성하지 않고 그대로 이용한다.
그렇다면 사용할 클래스를 메모리에 미리 로딩하는 것이 얼마만큼의 효과가 있을까? logcat 명령어를 통해 확인하면 1942개의 클래스를 로딩하는 데 18202ms가 걸렸다. 만약 미리 로딩하는 과정이 없을 경우, 18202ms의 추가시간이 걸리게 된다.
Zygote에서 달빅 가상 머신을 구동한 이후, 시스템 서버(SystemServer)라는 자바 서비스를 실행하기 위해 새로운 달빅 가상 머신 인스턴스를 생성한다. 시스템 서버는 필요한 네이티브 서비스를 실행하고, 안드로이드 프레임워크의 서비스들을 시작한다. 이때 시작되는 서비스로는 안드로이드 애플리케이션의 액티비티를 관리하는 액티비티 매니저(ActivityManager), 애플리케이션을 설치, 관리하는 패키지 매니저(PackageManager)등이 있다.
다음 그림은 ZygoteInit 클래스가 새로운 프로세스를 생성하는 과정을 나타낸다.