6 minute read

4. ZygoteInit 클래스의 기능

지금까지 가상 머신을 생성하고 ZygoteInit 클래스를 로딩했다. ZygoteInit 클래스의 기능을 요약하면 다음과 같다.

1.png

인사이드 안드로이드 책에서는 ZygoteInit.java의 메인 함수에서 위와 같은 방식으로 동작한다고 적혀있다. 하지만 필자가 실행중인 AOSP 10의 코드를 살펴보니 위와 약간 다른 방식으로 진행되는 양상이 보인다.

1
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

먼저 ZygoteInit.java는 위 경로에서 찾을 수 있었다.

3.PNG

847번째 줄에 보면 startSystemServer = false;라는 줄로 변수를 선언하고 있다. 그리고 851번째 줄에서 for문을 돌면서 넘겨받은 인자들을 하나씩 검증해나가고 있다. 그래서

  • 전달받은 인자가 start-system-server라면 startSystemServer을 true로 만들어주고

  • 전달받은 인자가 –enable-lazy-preload라면 enableLazyPreload 를 true로 만들어주고 만약

  • 전달받은 인자가 ABI_LIST_ARG로 시작한다면 abiList에 해당 인자에서 ABI_LIST_ARG를 추출해서 넣어주고

  • 전달받은 인자가 SOCKET_NAME_ARG와 같다면 SOCKET_NAME_ARG를 zygoteSocketName에 넣어준다.

4.PNG

여기서 ABI_LIST_ARG와 SOCKET_NAME_ARG는 위 그림과 같이 –abi-list와 –socket-name이다.

7.PNG

먼저 865번째 줄에서 zygoteSocketName이 PRIMARY_SOCKET_NAME인지 확인하여 맞다면 true를 아니라면 False를 isPrimaryZygote 변수에 넣어주고 있다. 내 생각엔 이 부분이 아마 소켓을 바인딩하는 부분이 아닐까 싶다.

23.PNG

PRIMARY_SOCKET_NAME은 Zygote.java에서 찾을 수 있었다.


4.1 애플리케이션 프레임워크에 속한 클래스와 리소스의 preload

그리고 877번째 줄이 preload하는 코드이다. 해당 메소드를 자세히 살펴보면

8.PNG

위 코드로 이루어져 있다. 여기서 눈여겨 볼 것은 141번째 줄의 preloadClasses(); 와 147번째 줄의 preloadResources(); 이다. 따라서 맨위의 과정에서 소켓바인딩을 제외한 다음 과정인 애플리케이션 실행시 사용될 class들과 resource들을 미리 load하는 과정을 수행하는 것을 확인할 수 있다.

preloadClasses()와 preloadResources() 메서드는 이름에서 알 수 있듯이 각 메서드는 안드로이드 애플리케이션 프레임워크에 포함되는 클래스와 아이콘, 이미지, 문자열 등의 자원을 미리 메모리에 로딩하고 로딩한 클래스와 자원에 대한 연결 정보를 생성한다. 이후 새로 생성되는 안드로이드 애플리케이션에서는 미리 로딩한 클래스나 자원을 이용할 때 새로 연결 정보를 생성하지 않고 그대로 이용한다.

  • 클래스

새로운 애플리케이션을 실행할 때 미리 메모리에 로딩된 클래스를 사용하기 때문에 애플리케이션의 시작 속도가 빠르다고 하였다. 그렇다면 사용할 클래스를 메모리에 미리 로딩하는 것이 얼마만큼 효과가 있을까?

애플리케이션이 생성될 때 애플리케이션에서 필요한 클래스가 메모리에 로딩돼 있지 않다면 가상 머신은 필요한 클래스를 메모리로 로딩해야 한다. 이 때마다 저장 장치에서 해당 클래스 정보를 찾아 메모리에 로딩하고 로딩된 클래스에 대한 연결 정보를 구성하는 작업을 수행해야 할 것이다.

따라서 필요한 클래스가 메모리에 하나도 로딩되어 있지 않은 최악의 상황에서는preloadClasses() 메서드에서 로딩하는 데 걸린 시간보다 훨씬 더 많은 시간이 소요될 것이다. preloaded-classes 파일에 기술된 클래스를 로딩하는 데 걸리는 시간은 logcat 명령을 통해 확인할 수 있다.

10.PNG

그래서 본인도 에뮬레이터가 켜져있는 상태에서 logcat -d 명령어를 쳐보았다.

9.PNG

뭐가 엄청나게 많이 나온 후에 결과가 나와야 하는데 나오진 않았다. 이 사실로부터 알 수 있는 건 logcat -d가 어떤 정보들을 보여주는데 그게 preload되지 않았을 때 나오는 classes일 수도 있다는 것이다. 그리고 이런 classes들이 미리 메모리에 로드되지 않았을 때 우리는 새로운 애플리케이션을 실행할 때마다 logcat -d 명령어가 수행되는 시간만큼 더 시간이 걸려서 실행될 수도 있다는 것이다. 물론 정확히 얼마나 느려지는지는 알 수가 없었다.

  • 리소스

안드로이드 애플리케이션 프레임워크에서 사용되는 문자열, 색, 이미지 파일, 사운드 파일 등은 리소스라는 이름으로 관리된다. 리소스는 애플리케이션에 의해 직접 참조될 수 없으며 안드로이드 개발툴에서 자동으로 생성해주는 R 클래스를 통해 접근할 수 있다. R 클래스를 통해 사용 가능한 리소스에 대한 구성 정보는 XML로 기술된다. 안드로이드 리소스는 크게 두 가지 종류가 있다.

Drawable 리소스 - 배경화면이나 사진, 아이콘 등 말 그대로 화면에 그려지는 리소스를 의미한다. preload Resource에서는 버튼 이미지나 라디오 그룹 등의 기본 테마 이미지를 로딩한다.

XML로 관리되는 리소스 - XML로 관리되는 리소스는 문자열을 저장하는 strings.xml, 문자열 배열을 저장하는 arrays xml, 색상 값을 저장하는 colors.xml 등이 있으며, 그 외에도 애니메이션, 레이아웃 등의 리소스가 XML 파일로 관리된다.

클래스를 로딩할 때와 마찬가지로 logcat을 통해 리소스 로딩에 소요되는 시간과 로딩된 리소스의 개수를 알 수 있다고 책에 나오는데 구체적인 명령어를 제시하지 않고 있어서 알 수가 없었다.

13.PNG

위 코드에서 preloaded될 class들의 경로를 알 수 있고 resource들을 zygote init 과정중에 preload할 건지에 대한 변수도 찾을 수 있었다.


4.2 SystemServer 실행

지금까지 달빅 가상 머신을 초기화하고 실행하였다. 애플리케이션 생성 요청 메세지를 수신하기 위한 소켓을 바인딩하는 작업은 현 시스템에서는 확인할 수 없었다. 아울러 애플리케이션 프레임워크에 포함된 클래스와 리소스를 메모리에 로딩했다. 이로써 ZygoteInit 클래스는 요청에 따라 새로운 애플리케이션을 생성해서 실행할 준비가 됐다. 그런데 ZygoteInit 클래스가 애플리케이션의 생성 요청 메시지를 처리하기 전에 해야할 작업이 하나 있는데 바로 ‘SystemServer’를 실행하는 것이다.

SystemServer

11.png

그리고 쭉 내려와서 902번째 줄에서 startSystemServer가 falsy한 값이 아니라면 forkSystemServer함수를 사용하여 systemserver를 그간 받아왔던 abiList, zygoteSocketName, zygoteServer 변수를 인자들과 함께 fork하여 실행시키고 있다. 이 부분이 SystemServer를 구동시키는 부분이다.

다음은 SystemServer가 구동되는 대략적인 과정이다.

1.jpg

init프로세스에서 app_process를 실행시켜서 가상 머신을 만든 후 Zygote프로세스를 구동시킨 후에 Zygote프로세스에서는 ZygoteInit의 main메소드를 실행한다. 그러면 ZygoteInit에서는 시스템 서버(SystemServer)라는 자바 서비스를 실행하기 위해 새로운 달빅 가상 머신 인스턴스를 생성한다. 그리고 시스템 서버를 실행하고 시스템 서버는 Audio Flinger와 Surface Flinger라는 네이티브 서비스를 실행한다. 필요한 네이티브 서비스가 실행되고 나면 시스템 서버는 안드로이드 프레임워크의 서비스(자바 서비스)들을 시작한다. 이때 시작되는 서비스로는 액티비티 매니저, 패키지 매니저등이 있다.

다시 코드로 돌아가서 903번째 줄에서 forkSystemServer 함수를 호출하는데 이는 다른 애플리케이션을 실행할 때와는 다른 함수이다. 일반적인 안드로이드 애플리케이션이라면 프로세스를 생성한 다음 프로세스 생성이 성공했는지 검사하지 않지만, 시스템 서버는 반드시 실행되야 하기 때문에 forkSystemServer() 메서드에서 생성한 시스템 서버 프로세스의 동작 여부를 확인한다. 그렇게 SystemSever를 시작하면 SystemServer.java라는 코드가 동작한다.

1
./frameworks/base/services/java/com/android/server/SystemServer.java

SystemServer.java 코드는 위 경로에서 찾을 수 있었다.

14.PNG

15.PNG

16.PNG

17.PNG

SystemServer.java에서는 main에 들어가면 SystemServer라는 객체의 run함수를 실행시키는데 그 함수 안에 있는 System.loadLibrary함수에서 android_servers라는 인자를 전달받아서 네이티브 서비스(Surface Flinger, Audio Flinger)를 초기화한다.

18.PNG

그리고 여러 과정을 거친뒤 시스템 서비스를 시작시킨다.

12.png

모든 서비스가 무사히 실행되고 나면 다시 ZygoteInit.java로 돌아와서 917번째 줄의runSelectLoop 메소드를 이용하여 zygoteServer를 시작시키고 있다. 이 runSelectLoop메소드는 시스템 서버가 실행되고 나면 바인딩한 소켓으로 들어오는 요청을 처리하기 위한 루프이다. 이 메서드는 zygote 프로세스가 종료될 때까지 반환되지 않는다.

위 작업까지 마치고 나면 비로소 Zygote는 사용자의 요청에 따라 새로운 애플리케이션을 실행할 준비가 완료된다.


4.3 새로운 안드로이드 애플리케이션 실행

19.png

위 그림은 ZygoteInit 클래스가 새로운 프로세스를 생성하는 과정을 나타낸다. 다음 코드를 통해 이를 확인해보자.

먼저 ZygoteServer.java의 runSelectLoop 메소드를 살펴봐야 한다.

1
./frameworks/base/core/java/com/android/internal/os/ZygoteServer.java

ZygoteServer.java는 위 경로에서 찾을 수 있었다.

20.PNG

377번째 줄에서 바인딩한 소켓의 파일 디스크립터를 디스크립터 배열에 추가한다. 추가된 디스크립터는 배열의 0번째 인덱스에 저장되며, 이를 이용해서 외부에서 발생하는 연결 요청을 처리하게 된다.

21.PNG

위 코드는 0번째 인덱스의 소켓 디스크립터에서 발생한 입출력 이벤트를 처리하는 부분이다. 0번째 인덱스에 있는 zygote소켓으로 전달된 새로운 연결 요청을 처리하기 위해 ZygoteConnection 클래스의 객체를 생성한다. ZygoteConnection 클래스의 생성자에서는 입출력 스트림을 생성하고 연결을 요청한 상대방의 접근 권한을 검사하기 위한 Credentials을 생성한다. 생성된 ZygoteConnection 인스턴스의 입출력 이벤트 처리를 위해 소켓 디스크립터의 배열인 socketFDs에 추가한다.

22.PNG

이제 peers에 추가된 peer들을 가져와서 connection을 생성한다. 바로 이곳에서 새로운 안드로이드 애플리케이션을 생성하게 된다.


5. Zygote에서 preload하지 않게 하기

지금까지 안드로이드 OS의 Zygote라는 프로세스의 실행과정과 동작과정을 상세하게 들여다 보았다. 이제는 Zygote 실행과정에서 preload하는 부분을 실행시키지 않고 앱을 구동하게 되면 얼마나 차이가 나게 되는지 알아볼 것이다.

먼저 지금까지 알아본 바로는 Zygote는 preload() 메소드를 통해 미리 사용할 클래스들과 리소스들을 로딩해놓고 애플리케이션 요청을 받을 때마다 미리 로딩해놓은 것들을 불러와서 쓰는 방법으로 애플리케이션 실행속도를 증가시켰다. 그렇다면 preload를 하지 않으려면 어떻게 해야할까?

7.PNG

아까 ZygoteInit.java의 873번째 줄을 보면 enableLazyPreload 변수가 True라면 preload메소드를 실행시키지 않고 그냥 지나친다. 그렇다면 enableLazyPreload는 true로 설정되있나 false로 설정되어있나 알아봐야 한다.

3.PNG

다시 위로 올라가서 854번줄에 보면 옵션으로 전달받은 인자가 –enable-lazy-preload라면 enableLazyPreload를 true로 변환시켜주는 것을 볼 수 있다. 그렇다면 이 인자는 언제 받는 것일까?

이에 대한 해답은 확인할 수가 없었다. 왜냐하면 거의 모든 부분이 app_main.cpp에서 전달받은 인자들을 가지고 ZygoteInit.java를 실행하는데 저번주차 포스트에서 봤듯이 init.rc 파일을 열어서 살펴봐도 인사이드 안드로이드책에 나온

1
/system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

구문은 찾을 수가 없었다. 아마도 업데이트가 계속해서 되면서 init.rc에서 위와 같은 명령어로 옵션들을 전달하는 대신 다른 더 효율적인 방법을 찾아서 os에 적용했기때문에 찾을 수 없는 것같다.

따라서 그냥 위 850번째 줄에서 enableLazyPreload 변수를 “true”로 설정시켜놓고 빌드를 하면 앱이 실행될 때 어마어마하게 많은 시간이 걸릴 것이다.

그래서 해당 enableLazyPreload를 true로 설정하고 빌드를 해보았다.

24.PNG

근데 기이한 현상이 발생되었다. 오히려 빨라진 것이다.

왜 그런지는 모르겠지만 enableLazyPreload가 false로 되어 있을 때는 Gallery 하나를 여는데 10초 가량이 걸렸는데 해당 변수를 true로 설정하고 나니 5초만에 켜졌다. 아마도 enableLazyPreload변수가 true라면 앱을 실행시킬 때 class나 resource등을 로드하지 않고 어떤 애플리케이션에서 특정 class나 resource를 요구할 때마다 가져오는 방식이라서 간단한 앱 실행정도에서는 오히려 빨라진 것 같다. 하지만 차후 실제 기기에 플래싱해서 특정 동작을 할 때 느려지진 않는지 오류가 발생하진 않는지 검증해봐야 할 것 같다.


Refernce

인사이드 안드로이드 - 위키북스

Categories:

Updated: