부팅 시간 최적화

이 문서에서는 특정 Android 기기의 부팅 시간을 개선하기 위한 파트너 안내를 제공합니다. 기기를 사용하려면 사용자가 부팅이 완료될 때까지 기다려야 하기 때문에 부팅 시간은 시스템 성능의 중요한 구성요소입니다. 콜드 부팅이 더 자주 발생하는 자동차와 같은 기기의 경우 부팅 시간이 빠른 것이 매우 중요합니다. 아무도 내비게이션 목적지를 입력하기 위해 수십 초를 기다리지는 않기 때문입니다.

Android 8.0은 다양한 구성요소에서 여러 개선사항을 지원하여 부팅 시간을 단축합니다. 다음 표에는 Google Pixel 및 Pixel XL 기기에서 측정된 성능 개선사항이 요약되어 있습니다.

구성요소 개선사항
부트로더
  • UART 로그를 삭제하여 1.6초 단축
  • GZIP에서 LZ4로 변경해 0.4초 단축
기기 커널
  • 사용하지 않은 커널 구성을 제거하고 드라이버 크기를 줄여 0.3초 단축
  • dm-verity 미리 가져오기 최적화로 0.3초 단축
  • 드라이버에서 불필요한 대기/테스트를 제거하여 0.15초 단축
  • CONFIG_CC_OPTIMIZE_FOR_SIZE를 삭제하여 0.12초 단축
I/O 조정
  • 일반 부팅 시 2초 단축
  • 처음 부팅 시 25초 단축
init.*.rc
  • init 명령을 병렬 처리하여 1.5초 단축
  • zygote를 초기에 시작하여 0.25초 단축
  • cpuset 조정으로 0.22초 단축
부팅 애니메이션
  • fsck를 트리거하지 않고 부팅 시 2초 먼저 시작, fsck 트리거 부팅 시에는 훨씬 단축됨
  • 부팅 애니메이션을 즉시 종료해 Pixel XL에서 5초 단축
SELinux 정책 genfscon으로 0.2초 단축

부트로더 최적화

부팅 시간 개선을 위해 부트로더를 최적화하려면:

  • 로깅의 경우:
    • 많은 양의 로깅에 긴 시간이 소요될 수 있으므로 UART에 로그 쓰기를 사용 중지합니다. Google Pixel 기기에서는 부트로더가 1.5초 느려졌습니다.
    • 오류 상황만 로깅하고 다른 정보는 별도의 검색 메커니즘으로 메모리에 저장할 것을 고려합니다.
  • 커널 압축 해제의 경우 현대 하드웨어에 GZIP(예: 패치) 대신 LZ4 사용 고려. 커널 압축 옵션마다 로드 시간과 압축 해제 시간이 다를 수 있으며 일부 옵션은 다른 옵션에 비해 특정 하드웨어에서 더 잘 작동할 수 있습니다.
  • 디바운싱/특수 모드 전환을 위한 불필요한 대기 시간을 확인하고 최소화합니다.
  • 부트로더에서 소요된 부팅 시간을 커널에 cmdline으로 전달합니다.
  • 커널 로딩과 I/O 초기화를 위해 CPU 클럭을 확인하고 병렬 처리(멀티코어 지원 필요)를 고려합니다.

커널 최적화

다음 도움말을 사용하여 커널을 최적화해 부팅 시간을 개선하세요.

기기 defconfig 최소화

커널 구성을 최소화하면 커널 크기가 줄어들어 압축 해제 및 초기화 로드 시간이 단축되고 공격 범위가 줄어 듭니다. 기기 defconfig를 최적화하려면 다음 단계를 따르세요.

  • 사용하지 않는 드라이버 식별. /dev/sys 디렉터리를 검토하고 일반 SELinux 라벨이 있는 노드를 찾습니다. 이러한 노드는 사용자 공간에서 액세스할 수 있도록 구성되어 있지 않습니다. 이러한 노드가 발견되면 삭제하세요.
  • 사용하지 않는 CONFIG 설정 해제. 커널 빌드가 생성한 구성 파일을 검토하여 기본적으로 사용 설정되어 있으나 사용하지 않는 CONFIG를 명시적으로 설정 해제합니다. 예를 들어 Google Pixel에서 사용하지 않는 다음 CONFIG를 삭제했습니다.
        CONFIG_ANDROID_LOGGER=y
        CONFIG_IMX134=y
        CONFIG_IMX132=y
        CONFIG_OV9724=y
        CONFIG_OV5648=y
        CONFIG_GC0339=y
        CONFIG_OV8825=y
        CONFIG_OV8865=y
        CONFIG_s5k4e1=y
        CONFIG_OV12830=y
        CONFIG_USB_EHCI_HCD=y
        CONFIG_IOMMU_IO_PGTABLE_FAST_SELFTEST=y
        CONFIG_IKCONFIG=y
        CONFIG_RD_BZIP2=y
        CONFIG_RD_LZMA=y
        CONFIG_TI_DRV2667=y
        CONFIG_CHR_DEV_SCH=y
        CONFIG_MMC=y
        CONFIG_MMC_PERF_PROFILING=y
        CONFIG_MMC_CLKGATE=y
        CONFIG_MMC_PARANOID_SD_INIT=y
        CONFIG_MMC_BLOCK_MINORS=32
        CONFIG_MMC_TEST=y
        CONFIG_MMC_SDHCI=y
        CONFIG_MMC_SDHCI_PLTFM=y
        CONFIG_MMC_SDHCI_MSM=y
        CONFIG_MMC_SDHCI_MSM_ICE=y
        CONFIG_MMC_CQ_HCI=y
        CONFIG_MSDOS_FS=y
        # CONFIG_SYSFS_SYSCALL is not set
        CONFIG_EEPROM_AT24=y
        # CONFIG_INPUT_MOUSEDEV_PSAUX is not set
        CONFIG_INPUT_HBTP_INPUT=y
        # CONFIG_VGA_ARB is not set
        CONFIG_USB_MON=y
        CONFIG_USB_STORAGE_DATAFAB=y
        CONFIG_USB_STORAGE_FREECOM=y
        CONFIG_USB_STORAGE_ISD200=y
        CONFIG_USB_STORAGE_USBAT=y
        CONFIG_USB_STORAGE_SDDR09=y
        CONFIG_USB_STORAGE_SDDR55=y
        CONFIG_USB_STORAGE_JUMPSHOT=y
        CONFIG_USB_STORAGE_ALAUDA=y
        CONFIG_USB_STORAGE_KARMA=y
        CONFIG_USB_STORAGE_CYPRESS_ATACB=y
        CONFIG_SW_SYNC_USER=y
        CONFIG_SEEMP_CORE=y
        CONFIG_MSM_SMEM_LOGGING=y
        CONFIG_IOMMU_DEBUG=y
        CONFIG_IOMMU_DEBUG_TRACKING=y
        CONFIG_IOMMU_TESTS=y
        CONFIG_MOBICORE_DRIVER=y
        # CONFIG_DEBUG_PREEMPT is not set
        
  • 부팅 시마다 불필요한 테스트 실행으로 이어지는 CONFIG 제거. 이러한 구성(예: CONFIG_IOMMU_IO_PGTABLE_FAST_SELFTEST)은 개발 시에는 유용하지만 프로덕션 커널에서는 제거해야 합니다.

드라이버 크기 최소화

커널 크기를 더욱 줄이기 위해 함수를 사용하지 않는 경우 기기 커널의 일부 드라이버를 제거할 수 있습니다. 예를 들어 WLAN이 PCIe를 통해 연결된 경우 SDIO 지원은 사용되지 않으며 컴파일하는 동안 제거해야 합니다. 자세한 내용은 Google Pixel kernel: net: wireless: cnss: add option to disable SDIO support를 참조하세요.

크기에 대한 컴파일러 최적화 삭제

CONFIG_CC_OPTIMIZE_FOR_SIZE에 대한 커널 구성을 삭제합니다. 이 플래그는 원래 작은 코드 크기로 인해 핫 캐시 적중이 발생할 수 있다고 가정하는 경우(따라서 속도가 더 빨라짐) 사용되었습니다. 그러나 현대 모바일 SoC가 더욱 강력해짐에 따라 이 가정은 더 이상 유효하지 않습니다.

또한 이 플래그를 제거하면 CONFIG_CC_OPTIMIZE_FOR_SIZE 플래그가 있을 때 Linux 커널에서 억제된 초기화되지 않은 변수에 대한 컴파일러 경고를 사용 설정할 수 있습니다.이러한 변경만으로 일부 Android 기기 드라이버에서 의미 있는 여러 버그를 발견할 수 있습니다.

초기화 지연

부팅 중에 많은 프로세스가 실행되지만 핵심 경로의 구성요소(부트로더 > 커널 > 초기화 > 파일 시스템 마운트 > zygote > 시스템 서버)만 부팅 시간에 직접적인 영향을 줍니다. 커널 부팅 중에 initcall을 프로파일링해 느리거나 init 프로세스를 시작하는 데 중요하지 않은 주변 기기/구성요소를 식별한 다음, 로드 가능한 커널 모듈로 이동하여 부팅 프로세스의 후반부까지 이러한 주변 기기/구성요소의 실행을 지연시킵니다. 비동기 기기/드라이버 프로브로 이동하면 커널 > 초기화 핵심 경로에서 느린 구성요소를 병렬 처리하는 데 도움이 될 수 있습니다.

    BoardConfig-common.mk:
        BOARD_KERNEL_CMDLINE += initcall_debug ignore_loglevel

    driver:
        .probe_type = PROBE_PREFER_ASYNCHRONOUS,
    

참고: 드라이버 종속성은 EPROBEDEFER 지원을 추가해 주의해서 확인해야 합니다.

I/O 효율성 최적화

부팅 속도를 높이려면 I/O 효율성을 높이는 것이 중요하고, 불필요한 항목 읽기는 부팅 이후까지 지연시켜야 합니다(Google Pixel에서는 부팅 시 약 1.2GB의 데이터가 읽힘).

파일 시스템 미세 조정

파일을 처음부터 읽을 때 또는 블록을 순차적으로 읽을 때 Linux 커널 미리 읽기가 시작되므로 부팅을 위한 I/O 스케줄러 매개변수를 미세 조정해야 합니다. 이는 일반적인 애플리케이션과는 워크로드 특성이 다릅니다.

원활한 (A/B) 업데이트를 지원하는 기기는 처음 부팅 시 파일 시스템 미세 조정에서 큰 이점을 얻습니다(예: Google Pixel의 경우 20초). 예를 들어 Google Pixel의 경우에는 다음 매개변수를 미세 조정했습니다.

    on late-fs
      # boot time fs tune
        # boot time fs tune
        write /sys/block/sda/queue/iostats 0
        write /sys/block/sda/queue/scheduler cfq
        write /sys/block/sda/queue/iosched/slice_idle 0
        write /sys/block/sda/queue/read_ahead_kb 2048
        write /sys/block/sda/queue/nr_requests 256
        write /sys/block/dm-0/queue/read_ahead_kb 2048
        write /sys/block/dm-1/queue/read_ahead_kb 2048

    on property:sys.boot_completed=1
        # end boot time fs tune
        write /sys/block/sda/queue/read_ahead_kb 512
        ...
    

기타

  • 커널 구성 DM_VERITY_HASH_PREFETCH_MIN_SIZE(기본 크기: 128)를 사용하여 dm-verity 해시 미리 가져오기 크기를 사용 설정합니다.
  • 파일 시스템의 안정성을 높이기 위해 그리고 부팅할 때마다 실행되지만 중단된 강제 검사에 대해서는 BoardConfig.mk에서 TARGET_USES_MKE2FS를 설정하여 새 ext4 생성 도구를 사용합니다.

I/O 분석

부팅 중 I/O 활동을 파악하려면 (systrace에서도 사용하는) 커널 ftrace 데이터를 사용합니다.

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

각 파일에 대한 파일 액세스를 분석하려면 커널을 다음과 같이 변경합니다(개발 커널에만 해당, 프로덕션 커널에는 사용 안 함).

    diff --git a/fs/open.c b/fs/open.c
    index 1651f35..a808093 100644
    --- a/fs/open.c
    +++ b/fs/open.c
    @@ -981,6 +981,25 @@
     }
     EXPORT_SYMBOL(file_open_root);

    +static void _trace_do_sys_open(struct file *filp, int flags, int mode, long fd)
    +{
    +       char *buf;
    +       char *fname;
    +
    +       buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
    +       if (!buf)
    +               return;
    +       fname = d_path(&filp-<f_path, buf, PAGE_SIZE);
    +
    +       if (IS_ERR(fname))
    +               goto out;
    +
    +       trace_printk("%s: open(\"%s\", %d, %d) fd = %ld, inode = %ld\n",
    +                     current-<comm, fname, flags, mode, fd, filp-<f_inode-<i_ino);
    +out:
    +       kfree(buf);
    +}
    +
    long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
     {
     	struct open_flags op;
    @@ -1003,6 +1022,7 @@
     		} else {
     			fsnotify_open(f);
     			fd_install(fd, f);
    +			_trace_do_sys_open(f, flags, mode, fd);
    

다음 스크립트를 사용하면 부팅 성능을 분석하는 데 도움이 됩니다.

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py 부팅 프로세스의 주요 단계를 분석하여 부팅 시간을 측정합니다.
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace 각 파일에 대한 액세스 정보를 제공합니다.
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace 시스템 수준의 분석을 제공합니다.

init.*.rc 최적화

Init은 프레임워크가 구축될 때까지 커널에서부터 시작되는 브리지이며, 일반적으로 기기는 여러 init 단계에서 몇 초를 보냅니다.

병렬로 작업 실행

현재 Android init은 거의 단일 스레드 프로세스이지만 일부 작업은 병렬로 수행할 수 있습니다.

  • 셸 스크립트 서비스에서 느린 명령을 실행하고 특정 속성을 기다려서 나중에 이러한 명령을 조인합니다. Android 8.0은 새로운 wait_for_property 명령어를 사용해 이 사용 사례를 지원합니다.
  • init에서 느린 작업을 식별합니다. 시스템은 init 명령어인 exec/wait_for_prop 또는 시간이 오래 걸리는 작업을 로깅합니다(Android 8.0의 경우 50ms 이상 걸리는 모든 명령어). 예:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    이 로그를 검토하면 개선 기회를 마련할 수 있습니다.

  • 서비스를 시작하고 핵심 경로에서 주변 기기를 초기에 사용 설정할 수 있습니다. 예를 들어, 일부 SOC의 경우 SurfaceFlinger를 시작하기 전에 보안 관련 서비스를 시작해야 합니다. ServiceManager가 '서비스 대기'를 반환하면 시스템 로그를 검토하세요. 이는 일반적으로 종속 서비스를 먼저 시작해야 한다는 신호입니다.
  • init.*.rc에서 사용되지 않는 서비스와 명령어를 삭제합니다. 부팅을 완료하려면 초기 단계 init에서 사용되지 않는 모든 항목을 지연시켜야 합니다.

참고: 속성 서비스는 init 프로세스의 일부이므로 init이 내장 명령어에서 사용 중인 경우 부팅 시 setproperty를 호출하면 지연 시간이 길어질 수 있습니다.

스케줄러 미세 조정 사용

초기 부팅에 스케줄러 조정을 사용합니다. Google Pixel의 예:

on init
        # boottime stune
        write /dev/stune/schedtune.prefer_idle 1
        write /dev/stune/schedtune.boost 100
        on property:sys.boot_completed=1
        # reset stune
        write /dev/stune/schedtune.prefer_idle 0
        write /dev/stune/schedtune.boost 0

        # or just disable EAS during boot
        on init
        write /sys/kernel/debug/sched_features NO_ENERGY_AWARE
        on property:sys.boot_completed=1
        write /sys/kernel/debug/sched_features ENERGY_AWARE

일부 서비스는 부팅 중 우선순위 향상이 필요할 수 있습니다. 예:

    init.zygote64.rc:
    service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
        class main
        priority -20
        user root
    ...

초기에 zygote 시작

파일 기반 암호화를 사용하는 기기는 zygote-start 트리거에서 초기에 zygote를 시작할 수 있습니다. 기본적으로 zygote는 zygote-start보다 훨씬 늦게 main 클래스에서 시작됩니다. (cpuset 설정이 잘못된 경우 zygote가 특정 CPU에서 강제로 실행될 수 있으므로) 초기에 zygote를 실행하는 경우 모든 CPU에서 zygote를 실행하도록 허용해야 합니다.

절전 사용 중지

기기 부팅 중 UFS 및/또는 CPU 거버너와 같은 구성요소에 대한 절전 설정을 사용 중지할 수 있습니다.

주의: 효율성을 높이려면 충전기 모드에서 절전 모드를 사용 설정해야 합니다.

    on init
        # Disable UFS powersaving
        write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 0
        write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 0
        write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 0
        write /sys/module/lpm_levels/parameters/sleep_disabled Y
    on property:sys.boot_completed=1
        # Enable UFS powersaving
        write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1
        write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1
        write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1
        write /sys/module/lpm_levels/parameters/sleep_disabled N
    on charger
        # Enable UFS powersaving
        write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1
        write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1
        write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1
        write /sys/class/typec/port0/port_type sink
        write /sys/module/lpm_levels/parameters/sleep_disabled N

중요하지 않은 초기화 지연

ZRAM과 같이 중요하지 않은 초기화는 boot_complete로 지연될 수 있습니다.

    on property:sys.boot_completed=1
       # Enable ZRAM on boot_complete
       swapon_all /vendor/etc/fstab.${ro.hardware}

애니메이션 부팅 최적화

다음 팁을 사용하여 부팅 애니메이션을 최적화하세요.

초기 시작 구성

Android 8.0에서는 userdata 파티션을 마운트하기 전 초기에 부팅 애니메이션 시작을 사용 설정할 수 있습니다. 하지만 Android 8.0에서 새 ext4 도구 체인을 사용하는 경우에는 안전을 위해 fsck가 계속해서 정기적으로 트리거되므로 부팅 애니메이션 서비스를 시작하는 데 지연이 발생합니다.

초기에 부팅 애니메이션을 시작하려면 fstab 마운트를 다음 두 단계로 분할합니다.

  • 초기 단계에서는 확인을 실행할 필요가 없는 파티션(예: system/vendor/)만 마운트한 다음 부팅 애니메이션 서비스 및 종속성(예: servicemanager 및 surfaceflinger)을 시작합니다.
  • 두 번째 단계에서는 확인을 실행해야 하는 파티션(예: data/)을 마운트합니다.

부팅 애니메이션은 fsck와 상관없이 훨씬 빠르게 시작됩니다.

깔끔한 마무리

종료 신호를 수신하면 부팅 애니메이션이 마지막 부분을 재생하는데, 길이가 길면 부팅 시간이 느려질 수 있습니다. 빠르게 부팅되는 시스템에서는 모든 개선사항의 효과가 나타나지 않을 수 있는, 길이가 긴 애니메이션이 필요하지 않습니다. 따라서 반복 루프와 피날레 모두를 짧게 만드는 것이 좋습니다.

SELinux 최적화

다음 팁을 사용하여 부팅 시간을 단축할 수 있도록 SELinux를 최적화하세요.

  • 깔끔한 정규 표현식(정규식)을 사용합니다. file_contextssys/devices에 대한 SELinux 정책과 일치할 때 형식이 잘못된 정규식은 많은 오버헤드를 초래할 수 있습니다. 예를 들어 정규식 /sys/devices/.*abc.*(/.*)? 가 'abc'가 포함된 모든 /sys/devices 하위 디렉터리 스캔을 실수로 강제 실행하여 /sys/devices/abc/sys/devices/xyz/abc 둘 다에 대한 일치 항목을 사용 설정합니다. 이 정규식을 /sys/devices/[^/]*abc[^/]*(/.*)? 로 개선하면 /sys/devices/abc에 대한 일치 항목만 사용 설정됩니다.
  • 라벨을 genfscon으로 이동합니다. 이 기존 SELinux 기능은 파일 일치 접두어를 SELinux 바이너리의 커널에 전달하는데, 여기서 커널은 접두어를 커널 생성 파일 시스템에 적용합니다. 이는 또한 라벨이 잘못 지정된 커널 생성 파일의 수정을 도와 다시 라벨을 지정하기 전 이러한 파일에 액세스하려고 하는 사용자 공간 프로세스 간에 발생할 수 있는 경합 상태를 방지합니다.

도구 및 메서드

다음 도구를 사용하여 최적화 대상에 대한 데이터를 수집할 수 있습니다.

Bootchart

Bootchart는 전체 시스템에 대한 모든 프로세스의 CPU 및 I/O 로드 분석을 제공합니다. 시스템 이미지를 다시 빌드힐 필요가 없으며, systrace를 실행하기 전에 빠른 온전성 검사로 사용할 수 있습니다.

bootchart를 사용하려면 다음 단계를 따르세요.

    adb shell 'touch /data/bootchart/enabled'
    adb reboot
    

부팅 후 다음과 같이 bootchart를 가져옵니다.

$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh

완료되면 /data/bootchart/enabled를 삭제하여 매번 날짜를 수집하지 않도록 합니다.

Systrace

Systrace는 부팅 중 커널과 Android 트레이스를 모두 수집합니다. systrace의 시각화는 부팅하는 동안 특정 문제를 분석하는 데 도움이 됩니다. 하지만 전체 부팅 중에 평균 개수나 누적 개수를 확인하려면 커널 트레이스를 직접 조사하는 편이 더 쉽습니다.

부팅 중에 systrace를 사용 설정하려면 다음 단계를 따르세요.

  • frameworks/native/atrace/atrace.rc에서 다음을
    write /sys/kernel/debug/tracing/tracing_on 0

    아래 내용으로 변경합니다.

    #write /sys/kernel/debug/tracing/tracing_on 0
  • 이렇게 하면 트레이싱이 사용 설정됩니다(기본적으로 사용 중지되어 있음).

  • device.mk 파일에 다음 행을 추가합니다.
        PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
        PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • 기기 BoardConfig.mk 파일에서 다음을 추가합니다.
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • 자세한 I/O 분석을 위해 블록과 ext4 및 f2fs도 추가합니다.

  • 기기별 init.rc 파일에서 다음과 같이 변경합니다.
    • on property:sys.boot_completed=1(부팅 완료 시 트레이싱을 중지함)
    • write /d/tracing/tracing_on 0
    • write /d/tracing/events/ext4/enable 0
    • write /d/tracing/events/f2fs/enable 0
    • write /d/tracing/events/block/enable 0

부팅 후 트레이스를 가져옵니다.

    adb root && adb shell atrace --async_stop -z -c -o /data/local/tmp/boot_trace
    adb pull /data/local/tmp/boot_trace
    $ANDROID_BUILD_TOP/external/chromium-trace/systrace.py --from-file=boot_trace
    

참고: Chrome은 너무 큰 파일을 처리할 수 없습니다. 필요한 부분에 tail , head 또는 grep를 사용하여 boot_trace 파일을 자릅니다. 또한 I/O 분석에는 이벤트가 너무 많아 캡처된 boot_trace를 직접 분석해야 합니다.