안드로이드 부팅 프로세스

안드로이드 부팅 프로세스

2025년 1월 8일

ref #

https://m.blog.naver.com/jyoun/221691860304
https://community.nxp.com/t5/i-MX-Processors-Knowledge-Base/The-Android-Booting-process/ta-p/1129182
플래시 이미지


부팅 프로세스 #

905a10c7-ffaa-4051-be4a-11ab6992881e

Verified Boot(VB) #

안드로이드는 부팅 과정에서 소프트웨어의 무결성을 보장하기 위해 부팅 과정 중 각 단계에서 다음 단계의 서명을 먼저 검증한 후 이상이 없으면 로딩하는 방식으로 동작한다.

oem unlock을 하게 되면, VB 검증을 수행하지만 사용자의 의지로 커스텀 롬을 올릴 수 있도록 허용하는 것이다. 하지만 VB검증은 여전히 수행돼서 SafetyNet이나 서명이 맞지 않는 이미지라면 화면에 경고가 표시될수는 있다.


Step 1: Power On and System Startup #

시스템에 전원이 공급되면 미리 정의된 하드웨어 회로의 프로세스에 따라 SoC ROM 에 저장된 Boot ROM 코드가 정해진 위치부터 실행된다.

Boot ROM 코드는 하드웨어 사양에 따라 eMMC, UFS 같은 SoC 외부 플래시 메모리에 있는 bootloader를 SRAM에 올려 실행시킨다.

eMMC, UFS 같은 플래시 메모리는 PC로 따지면 hdd, ssd 같은 드라이브 역할을 하는것이고 AOSP를 플래시 할때 각 파티션에 맞춰서 플래시된다.


Secure boot #

보통 부트로더가 올라가기 이전에 부트로더를 검사하고, TrustZone의 TEE, TA 초기화 로직이 실행된다. SoC 회사마다 다르며, 복잡한 구조를 갖고 있다.
TrustZone 정리
secure boot 정리


Step 2: Bootloader #

https://android.googlesource.com/platform/bootable/bootloader/legacy/+/refs/heads/main 옛날 안드로이드의 레거시 usb 부트로더 코드. init.S, main.c 코드를 확인하면 된다.

사실 BootROM도 부트로더 역할을 하기 때문에 primary bootloader라고 부르고, LK 같은 외부 플래시 메모리의 부트로더는 secondary bootloader라고 부른다.

bootloader에는 보통 fastboot, recovery 모드가 포함되어 있기 때문에 bootloader를 플래시하다가 실패했을때 이 모드들이 잘못된 경우 살리기 어려울 수 있다. 일부 긴급복구 모드를 지원하는 기기가 있으며 없다면 JTAG나 칩오프 등으로 직접 플래시 하는 방법밖에 없다.
그래서 보통 제조업체는 이곳에 oem lock을 걸고, 해제해야 부트로더나 부트이미지의 플래시를 가능하게 해주지만, Knox Warranty Bit 가 영구적으로 트립되어 깨지며 메인보드를 바꾸기 전까지 보증이나 서비스에 제한이 걸린다.

퀄컴에서는 LK Little Kernel)나 스냅드래곤 800 이후 XBL(eXtensible Bootloader)를 사용하고, 미디어텍에서는 uBoot(Universal Boot Loader)를 주로 사용한다. bootloader는 AOSP의 범위가 아니며, 기기 혹은 SoC 제조사에서 구현한다.

uBoot #

sram은 초기화 없이 사용할 수 있는 메모리이지만, uboot를 올리기엔 너무 작기 때문에 BootROM 코드가 uboot spl 을 실행해서 uboot 본체를 올리게 된다.

  • 1단계(spl) : DRAM 초기화 후 uboot 본체를 로드한다.
  • 2단계(본체) : 실질적인 부트로더의 역할(하드웨어 초기화 등)을 수행한다. fastboot, recovery가 구현된 위치이며, 진입 조건(키 조합)에 따라 해당 모드로 진입하거나 정상부팅으로 진입하도록 도와준다. 정상 부팅에서는 커널과 램디스크(initrd)를 DRAM에 로드하고 파라미터를 전달해 실행시킨다.

LK #

크기가 작은 경량 부트로더라서 SRAM에 바로 로드해서 실행시킬 수 있다. 하드웨어 초기화, 커널과 램디스크를 DRAM에 로드 후 실행 하는 역할까지 바로 수행할 수 있다.


Step 3: Kernel + ramdisk #

안드로이드 커널이 시작되면 리눅스 커널과 비슷하게 여러 시스템 설정을 마치고 메모리상에 로드된 램디스크 이미지를 찾아 tmpfs로 압축을 풀어 임시 rootfs로 사용하며 첫번째 유저 프로세스로 init 프로세스를 실행시킨다.

램디스크에는 init 바이너리, init.rc 파일들 등 부팅에 필요한 기본적인 파일들만 포함되어 있고, 전통적인 커널구조에서는 벤더의 드라이버도 포함되어 있었지만, GKI에서는 드라이버가 vendor 파티션으로 옮겨졌다.

커널은 유저 프로세스의 cpu 관리를 담당하고, 필요할때만 cpu를 점유하게된다. 하드웨어 인터럽트나 시스템콜이 발생하는 경우, 스케줄러가 활성화돼서 프로세스 상태가 변경돼야 하는 경우, 드라이버 코드를 실행하는 경우 등이 있다.

  • 캐시 설정
  • 메모리 보호 설정
  • cpu 스케줄러 초기화
  • SELinux 설정
  • 파일시스템 마운트
  • 드라이버 로드

기존 설정들도 안드로이드에 맞게 배터리나 리소스 최적화위주로 변경되고 안드로이드 커널에서 추가된 기능도 있다.

  • binder : 안드로이드에 특화된 프로세스간 통신(IPC) 방식
  • ashmem : posix의 shm처럼 안드로이드용 파일기반 익명 공유 메모리 할당자이다.
  • pmem : 하드웨어와 유저스페이스간 메모리 공유를 위한 물리메모리 할당자이다.
  • wakelock : 절전모드를 방지하는 기능
  • logger : 로그캣의 커널지원
  • oom 처리 : 메모리가 부족하면 실행중인 프로세스를 종료하도록 변경
  • adb 가젯 드라이버 : adb를 위한 가젯
  • yaffs2 : 초기 안드로이드에서 사용하던 플래시메모리 파일시스템
  • RAM_CONSOLE : 커널 printk메시지를 RAM 버퍼에 저장하고 커널 패닉나면 다음 커널 호출때 출력하도록 함

GKI(Generic Kernel Image) #

안드로이드의 커널은 기기, SoC 별로 포팅해서 빌드됐기 때문에 커널 업데이트를 하려고 해도 각 커널에 다시 적용해야하는 문제가 있었다.
기본 커널은 단일화하고, 벤더나 기기별 특화 코드는 커널 모듈로 분리해서 필요한 시점에 로드하는 형태로 새로 아키텍쳐링을 하고있는 것이 GKI이다.

안드로이드 12 (커널 5.4) 부터 GKI 1.0 을 강제화 하기 시작했고, 안드로이드 13 (커널 5.10, 5.15) 부터는 GKI 2.0을 도입하기 시작했다.

공통 커널은 zImage + initramfs 파일이 GKI 규칙에 맞게 빌드되어 구글이 제공해주고,
SoC나 OEM 맞춤 드라이버, 하드웨어 종속 로직은 커널 모듈(.ko)로 분리되어 퀄컴 미디어텍 같은 하드웨어 제조사나 삼성/샤오미 같은 OEM 업체들이 빌드해서 안드로이드에 포함시킨다.

이전에는 구글이 커널을 패치하면 벤더가 커널을 포크한 뒤 호환성을 맞추면서 머지하는데 시간이 오래걸렸다면, 지금은 GKI 커널이 패치되더라도 ABI만 동일하다면 커널 모듈은 새로 빌드할 필요가 없다는 장점이 생겼다.


Step 4: init process #

커널에 의해 가장 먼저 실행되는 유저프로세스이며 유저공간의 초기화나 시스템이 실행되기 위한 초기 작업들을 담당하고, init.rc를 파싱하면서 추가적인 초기화 동작을 수행한다.

init 프로세스 #

<android_source>/init/init.cpp

4f3ce8dc-cb8b-4ae6-b5b4-8b8189e132c0

  • system 파티션에 대한 무결성 검증
  • /dev, /sys, /proc 등 가상 파일 시스템을 부팅 초기에 마운트
    • init.rc에서 early-init 액션으로 마운트하다가 최근 안드로이드에서는 first_stage_init 에서 미리 마운트하기도 함
  • SELinux 설정, property 초기화
  • 서비스를 시작할 수 있도록 준비해준다.
  • 자식프로세스 종료처리 등
  • init.rc 과 init<machine_name>.rc 스크립트를 파싱하고 실행한다.
    • 여기에서 나머지 파티션도 마운트한다. (system, vendor 등)
    • 안드로이드 9 이상 버전에서 SAR을 지원하면서 system을 /system이 아닌 / 로 마운트 하도록 변경됐다.
    • 필수 디렉터리 생성 및 권한 설정
    • SELinux, 환경변수 등 설정
    • 초기 필수 서비스(데몬)들의 실행 : zygote(app_process), adbd, surfaceflinger, bluetoothd, mediaserver…

init.rc #

init에서는 property, SELinux, 서비스 등의 작업을 수행할 수 있도록 유저 영역을 초기화해주고,
init.rc에서는 초기화 된 상태에서 자체적인 스크립트를 통해 어떤 서비스를 시작할지, 프로퍼티의 초기값은 어떤 값을 사용할지, 환경변수는 어떻게 세팅할지 등 init이 실행할 동작에 대한 추상적인 명령을 작성한 스크립트이다.

init.rc 포맷

여기까지 진행하면 화면에서 안드로이드 로고를 볼 수 있다.


Step 5: Zygote and Dalvik/ART #

일반적인 Java 앱에서는 앱마다 별도의 가상머신(런타임환경)이 메모리에 올라가서 메모리가 많이 중복된다. 하지만 한정적인 리소스를 가진 안드로이드에서는 이 방법이 맞지 않아서 코드가 공유되는 Zygote를 사용한다.

Zygote는 핵심 라이브러리와 클래스를 미리 로드하고 초기화된 상태에서 대기하고, 앱을 새로 실행할때 zygote가 fork되며 메모리상에서 이미 초기화된 자신의 이미지를 복사해서 새로운 앱 프로세스를 띄운다.
이미 초기화됐기 때문에 빠르게 띄울 수 있으며 여러 자원들을 공유할 수 있다는 장점이 있다.

zygote는 개발자가 만들어낸 유저앱 뿐만 아니라 벤더 앱, 시스템 프로세스 등 대부분의 안드로이드 상의 Java기반 앱을 실행할 때 fork해서 실행된다. (네이티브 앱이나 예외적으로 독립된 런타임 환경에서 실행되도록 설계된 Java앱은 제외한다)

처음에 init.rc에 의해 zygote가 실행되면 여러가지 초기화 과정 이후 fork 해서 system_server 프로세스(안드로이드 프레임워크를 관리하는 프로세스)를 system(1000) 권한으로 띄우고 앱 실행 요청이 들어오는 것을 루프를 돌며 기다린다.

여기서부터는 부팅 애니메이션을 볼 수 있다.

ZygotePreload #

개발자가 구현한 앱에서 클래스, 리소스, 라이브러리 등을 Preload 하고싶을 수 있다. Zygote에 추가하면 모든 프로세스에 적용돼서 메모리 낭비가 심하게되는데, 이럴땐 안드로이드 10부터 생긴 AppZygote 기능을 사용하면 된다.

이 기능은 isolated 서비스에 대해 미리 원하는 데이터가 로드된 AppZygote를 만들어두고 서비스가 start 될때 Zygote대신 AppZygote를 사용하는 기능이다.
이렇게되면 영향을 받는 서비스들은 같은 데이터가 공유되기 때문에 메모리 낭비를 줄일 수 있고 미리로드한 Zygote를 사용하기 때문에 로드 속도도 빨라진다.

 1<application
 2    android:name=".MyApp"
 3    android:zygotePreloadName="com.example.preload.MyPreloader"  <!-- 여기서 Preloader 클래스 지정 -->
 4    ... >
 5    <service
 6        android:name=".MyIsolatedService"
 7        android:useAppZygote="true"
 8        android:isolatedProcess="true" />
 9    ...
10</application>

클래스나 라이브러리, 리소스 들을 미리 로드하면 이 AppZygote를 사용하는 서비스들에 적용된다.

 1public class MyPreloader implements ZygotePreload {
 2    public MyPreloader() {} // 반드시 기본 생성자 필요
 3    
 4    @Override
 5    public void doPreload(@NonNull PreloadEnvironment env) {
 6        // 여기서 특정 클래스, 리소스, 라이브러리 등을 미리 적재
 7        // 예: Class.forName("com.example.library.HeavyClass");
 8        //     loadCustomNativeLibs();
 9        //     warmUpCaches();
10    }
11}

Step 6: System service #

SystemServer.java

zygote에 의해 system_server 프로세스가 시스템 권한으로 실행되고 이 프로세스가 안드로이드의 핵심 시스템 서비스(AMS, PMS, WMS 등)를 초기화하고 실행시킨다.

system_server 내에서 startService로 실행되지만, 당연하게도 개발자가 구현한 앱처럼 AndroidManifest.xml에서 service 컴포넌트로 동작하는것과는 다르다.

1mActivityManagerService = ActivityManagerService.Lifecycle.startService(
2                mSystemServiceManager, atm);
3mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);
4mPackageManagerService = PackageManagerService.main(
5                mSystemContext, installer, domainVerificationService,
6                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF);
7wm = WindowManagerService.main(context, inputManager, !mFirstBoot,
8                new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
9// ... 기타등등 서비스
  • Power Manager : 전원을 관리하며 화면 밝기, on/off, cpu 슬립, 전원버튼 이벤트 감지 등의 작업을 수행함
  • Activity Manager(AMS) : 앱을 실행시키거나 중단시키고, 앱 프로세스의 4대 컴포넌트의 생명주기를 관리한다.
    안드로이드 프레임워크 API에서 Context.startActivity(), startService()가 실행되면 새 프로세스를 요청하거나 이미 존재하는 프로세스에 컴포넌트를 붙인다.
  • Package Manager(PMS) : 패키지의 설치/제거, 권한관리, 버전관리, AndroidManifest.xml 파일 파싱 후 DB로 유지하는 등의 앱 관리를 담당한다.
  • Window Manager(WMS) : 화면의 모든 윈도우(액티비티, 다이얼로그, 시스템UI 등) 의 배치를 결정하고 Z-Order, 키입력/터치이벤트 등을 다른 프로세스/서비스와 협력해서 관리한다.
  • 그 외에도 Bluetooth, Alarm(브로드캐스트, 콜백), Sensor, Mount(Storage), Nitification(알림채널), Location… 등 여러 서비스가 있다.

여기까지 실행되면 부팅이 완료되고 핸드폰 소유자는 원하는 작업을 하면서 기기를 가지고 놀 수 있다.

comments powered by Disqus