부팅 시간 최적화

이 문서에서는 특정 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 클럭을 확인하고 병렬 처리(멀티코어 지원 필요)를 고려합니다.

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 프로세스의 일부이므로 부팅 중에 setproperty를 호출하면 init가 내장 명령어에서 사용 중인 경우 지연 시간이 길어질 수 있습니다.

스케줄러 미세 조정 사용

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

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

  • 초기 단계에서는 실행 확인이 필요하지 않은 파티션(예: system/, vendor/)만 마운트하고 부팅 애니메이션 서비스와 그 종속 항목(예: servicemanager, surfaceflinger)을 시작합니다.
  • 두 번째 단계에서는 실행 확인이 필요한 파티션(예: data/)을 마운트합니다.

부팅 애니메이션은 fsck와 상관없이 훨씬 빠르게 그리고 일정한 시간에 시작됩니다.

깔끔한 마무리

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

SELinux 최적화

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

  • 깔끔한 정규 표현식(정규식)을 사용합니다. 형식이 잘못된 정규식은 file_contexts에서 sys/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를 삭제하여 매번 데이터를 수집하는 것을 방지합니다.

bootChart가 작동하지 않고 bootchart.png가 존재하지 않는다는 오류가 발생하면 다음 안내를 따르세요.
  1. 다음 명령어를 실행합니다.
          sudo apt install python-is-python3
          cd ~/Documents
          git clone https://github.com/xrmx/bootchart.git
          cd bootchart/pybootchartgui
          mv main.py.in main.py
        
  2. pybootchartgui의 로컬 사본(~/Documents/bootchart/pybootchartgui.py에 위치)을 가리키도록 $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh를 업데이트합니다.

Systrace

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

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

  • frameworks/native/cmds/atrace/atrace.rc에서 변경할 내용은 다음과 같습니다.
      write /sys/kernel/debug/tracing/tracing_on 0
      write /sys/kernel/tracing/tracing_on 0

    위 내용을 아래와 같이 변경합니다.

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/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          // This stops tracing on boot complete
    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