부팅 시간 최적화

이 페이지에서는 부팅 시간을 개선하기 위해 선택할 수 있는 일련의 팁을 제공합니다.

모듈에서 디버그 기호 제거

프로덕션 장치의 커널에서 디버그 기호가 제거되는 방식과 유사하게 모듈에서 디버그 기호도 제거해야 합니다. 모듈에서 디버그 기호를 제거하면 다음을 줄여 부팅 시간을 돕습니다.

  • 플래시에서 바이너리를 읽는 데 걸리는 시간입니다.
  • 램디스크의 압축을 푸는 데 걸리는 시간입니다.
  • 모듈을 로드하는 데 걸리는 시간입니다.

모듈에서 디버그 기호를 제거하면 부팅하는 동안 몇 초를 절약할 수 있습니다.

기호 제거는 Android 플랫폼 빌드에서 기본적으로 활성화되어 있지만 명시적으로 활성화하려면 device/ vendor / device 아래의 장치별 구성에서 BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES 를 설정합니다.

커널 및 램디스크에 LZ4 압축 사용

Gzip은 LZ4에 비해 더 작은 압축 출력을 생성하지만 LZ4는 Gzip보다 더 빠르게 압축 해제합니다. 커널 및 모듈의 경우 Gzip을 사용하여 절대적인 저장소 크기 감소는 LZ4의 압축 해제 시간 이점에 비해 그다지 중요하지 않습니다.

BOARD_RAMDISK_USE_LZ4 를 통해 Android 플랫폼 빌드에 BOARD_RAMDISK_USE_LZ4 램디스크 압축 지원이 추가되었습니다. 장치별 구성에서 이 옵션을 설정할 수 있습니다. 커널 압축은 커널 defconfig를 통해 설정할 수 있습니다.

LZ4로 전환하면 부팅 시간이 500ms에서 1000ms 빨라집니다.

드라이버에 과도한 로그인을 피하십시오

ARM64 및 ARM32에서 호출 사이트에서 특정 거리 이상 떨어진 함수 호출에는 전체 점프 주소를 인코딩할 수 있는 점프 테이블(프로시저 연결 테이블 또는 PLT라고 함)이 필요합니다. 모듈은 동적으로 로드되기 때문에 이러한 점프 테이블은 모듈 로드 중에 수정되어야 합니다. 재배치가 필요한 호출을 ELF 형식의 명시적 추가 항목(줄여서 RELA)이 있는 재배치 항목이라고 합니다.

Linux 커널은 PLT를 할당할 때 일부 메모리 크기 최적화(예: 캐시 적중 최적화)를 수행합니다. 이 업스트림 커밋 으로 최적화 체계는 O(N^2) 복잡성을 가지며 여기서 N은 R_AARCH64_JUMP26 또는 R_AARCH64_CALL26 유형의 RELA 수입니다. 따라서 이러한 유형의 RELA가 적으면 모듈 로드 시간을 줄이는 데 도움이 됩니다.

R_AARCH64_CALL26 또는 R_AARCH64_JUMP26 RELA의 수를 증가시키는 일반적인 코딩 패턴 중 하나는 드라이버에 과도한 로그인입니다. printk() 또는 기타 로깅 체계에 대한 각 호출은 일반적으로 CALL26 / JUMP26 RELA 항목을 추가합니다. 업스트림 커밋의 커밋 텍스트에서 최적화를 적용하더라도 6개 모듈을 로드하는 데 약 250ms가 소요된다는 점에 유의하십시오.

로깅을 줄이면 기존 로깅의 과도에 따라 부팅 시간을 약 100~300ms 절약할 수 있습니다.

선택적으로 비동기식 프로빙 활성화

모듈이 로드될 때 지원하는 장치가 이미 DT(장치 트리)에서 채워지고 드라이버 코어에 추가된 경우 장치 프로브는 module_init() 호출 컨텍스트에서 수행됩니다. module_init() 컨텍스트에서 장치 프로브가 완료되면 프로브가 완료될 때까지 모듈 로드를 완료할 수 없습니다. 모듈 로딩은 대부분 직렬화(serialized)되기 때문에 프로브하는 데 비교적 오랜 시간이 걸리는 장치는 부팅 시간이 느려집니다.

느린 부팅 시간을 방지하려면 장치를 검색하는 데 시간이 걸리는 모듈에 대해 비동기식 검색을 활성화하십시오. 스레드를 분기하고 프로브를 시작하는 데 걸리는 시간이 장치를 프로브하는 데 걸리는 시간만큼 길 수 있으므로 모든 모듈에 대해 비동기식 프로빙을 활성화하는 것은 유익하지 않을 수 있습니다.

I2C와 같은 느린 버스를 통해 연결된 장치, 프로브 기능에서 펌웨어 로드를 수행하는 장치, 하드웨어 초기화를 많이 수행하는 장치는 타이밍 문제를 유발할 수 있습니다. 이러한 일이 발생하는 시기를 식별하는 가장 좋은 방법은 모든 드라이버에 대한 프로브 시간을 수집하고 정렬하는 것입니다.

모듈에 대한 비동기 검색을 활성화하려면 드라이버 코드에서 PROBE_PREFER_ASYNCHRONOUS 플래그만 설정하는 것만으로는 충분 하지 않습니다 . 모듈의 경우, 커널 명령줄에 module_name .async_probe=1 을 추가하거나 modprobe 또는 insmod 를 사용하여 모듈을 로드할 때 async_probe=1 을 모듈 매개변수로 전달해야 합니다.

비동기식 프로빙을 활성화하면 하드웨어/드라이버에 따라 부팅 시간이 약 100 - 500ms를 절약할 수 있습니다.

가능한 한 빨리 CPUfreq 드라이버를 조사하십시오.

CPUfreq 드라이버가 더 일찍 프로브할수록 부팅하는 동안 CPU 주파수를 최대(또는 열적으로 제한된 최대)로 더 빨리 확장할 ​​수 있습니다. CPU가 빠를수록 부팅이 빨라집니다. 이 지침은 DRAM, 메모리 및 상호 연결 주파수를 제어하는 devfreq 드라이버에도 적용됩니다.

모듈의 경우 로드 순서는 initcall 수준과 드라이버의 컴파일 또는 링크 순서에 따라 달라질 수 있습니다. 별칭 MODULE_SOFTDEP() 를 사용하여 cpufreq 드라이버가 로드할 처음 몇 개의 모듈 중 하나인지 확인하십시오.

모듈을 일찍 로드하는 것 외에도 CPUfreq 드라이버를 조사하기 위한 모든 종속성도 조사했는지 확인해야 합니다. 예를 들어, CPU의 주파수를 제어하기 위해 클럭 또는 레귤레이터 핸들이 필요한 경우 먼저 프로브해야 합니다. 또는 부팅하는 동안 CPU가 너무 뜨거워질 수 있는 경우 CPUfreq 드라이버보다 먼저 열 드라이버를 로드해야 할 수 있습니다. 따라서 CPUfreq 및 관련 devfreq 드라이버가 가능한 한 빨리 프로브하도록 할 수 있는 일을 하십시오.

CPUfreq 드라이버를 조기에 검색하여 절약할 수 있는 비용은 얼마나 일찍 검색할 수 있는지와 부트로더가 CPU를 남겨두는 빈도에 따라 매우 작거나 클 수 있습니다.

모듈을 두 번째 단계 init, vendor 또는 vendor_dlkm 파티션으로 이동

첫 번째 단계의 init 프로세스는 직렬화되기 때문에 부팅 프로세스를 병렬화할 기회가 많지 않습니다. 첫 번째 단계 초기화를 완료하는 데 모듈이 필요하지 않은 경우 공급업체 또는 vendor_dlkm 파티션에 배치하여 모듈을 두 번째 단계 초기화로 이동합니다.

첫 번째 단계 초기화는 두 번째 단계 초기화에 도달하기 위해 여러 장치를 조사할 필요가 없습니다. 정상적인 부팅 흐름에는 콘솔 및 플래시 스토리지 기능만 필요합니다.

다음 필수 드라이버를 로드합니다.

  • 지키는 개
  • 초기화
  • cpufreq

복구 및 사용자 공간 fastbootd 모드의 경우 첫 번째 단계 초기화에는 USB와 같은 조사 및 표시에 더 많은 장치가 필요합니다. 이러한 모듈의 복사본을 첫 번째 단계 ramdisk와 vendor 또는 vendor_dlkm 파티션에 보관하십시오. 이를 통해 복구 또는 fastbootd 부팅 흐름을 위한 첫 번째 단계 초기화에서 로드할 수 있습니다. 그러나 정상적인 부팅 흐름 중에 첫 번째 단계 초기화에서 복구 모드 모듈을 로드하지 마십시오. 복구 모드 모듈은 부팅 시간을 줄이기 위해 두 번째 단계 초기화로 연기될 수 있습니다. 첫 번째 단계 초기화에서 필요하지 않은 다른 모든 모듈은 vendor 또는 vendor_dlkm 파티션으로 이동해야 합니다.

리프 장치(예: UFS 또는 직렬) 목록이 주어지면 dev needs.sh 스크립트는 종속성 또는 공급자(예: 시계, 조정기 또는 gpio )가 조사하는 데 필요한 모든 드라이버, 장치 및 모듈을 찾습니다.

모듈을 두 번째 단계 init로 이동하면 다음과 같은 방식으로 부팅 시간이 단축됩니다.

  • 램디스크 크기 축소.
    • 이것은 부트로더가 램디스크를 로드할 때 더 빠른 플래시 읽기를 생성합니다(직렬화된 부트 단계).
    • 커널이 램디스크를 압축 해제할 때 압축 해제 속도가 빨라집니다(직렬화된 부팅 단계).
  • 두 번째 단계 init는 병렬로 작동하므로 두 번째 단계 init에서 수행되는 작업과 함께 모듈의 로딩 시간을 숨깁니다.

모듈을 두 번째 단계로 이동하면 두 번째 단계 초기화로 이동할 수 있는 모듈의 수에 따라 부팅 시간이 500 - 1000ms를 절약할 수 있습니다.

모듈 로딩 물류

최신 Android 빌드에는 각 단계에 복사할 모듈과 로드할 모듈을 제어하는 ​​보드 구성이 있습니다. 이 섹션에서는 다음 하위 집합에 중점을 둡니다.

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES . 램디스크에 복사할 모듈 목록입니다.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD . 첫 번째 단계 init에서 로드할 모듈 목록입니다.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD . 램디스크에서 복구 또는 fastbootd 를 선택할 때 로드할 모듈 목록입니다.
  • BOARD_VENDOR_KERNEL_MODULES /vendor/lib/modules/ 디렉토리의 vendor_dlkm 파티션 또는 공급업체에 복사할 모듈 목록입니다.
  • BOARD_VENDOR_KERNEL_MODULES_LOAD . 두 번째 단계 init에서 로드할 모듈 목록입니다.

ramdisk의 부팅 및 복구 모듈도 /vendor/lib/modules 에 있는 vendor_dlkm 파티션 또는 벤더에 복사해야 합니다. 이러한 모듈을 공급업체 파티션에 복사하면 두 번째 단계 초기화 중에 모듈이 보이지 않게 되며, 이는 버그 보고를 위한 modinfo 를 디버깅하고 수집하는 데 유용합니다.

복제는 부트 모듈 세트가 최소화되는 한 공급업체 또는 vendor_dlkm 파티션에서 최소한의 공간을 차지해야 합니다. 공급업체의 modules.list 파일에 /vendor/lib/modules 에 필터링된 모듈 목록이 있는지 확인하십시오. 필터링된 목록은 부팅 시간이 모듈을 다시 로드하는 데 영향을 받지 않도록 합니다(비용이 많이 드는 프로세스).

복구 모드 모듈이 그룹으로 로드되는지 확인합니다. 복구 모드 모듈 로드는 복구 모드에서 수행하거나 각 부팅 흐름의 두 번째 단계 초기화 시작 시 수행할 수 있습니다.

다음 예와 같이 장치 Board.Config.mk 파일을 사용하여 이러한 작업을 수행할 수 있습니다.

# All kernel modules
KERNEL_MODULES := $(wildcard $(KERNEL_MODULE_DIR)/*.ko)
KERNEL_MODULES_LOAD := $(strip $(shell cat $(KERNEL_MODULE_DIR)/modules.load)

# First stage ramdisk modules
BOOT_KERNEL_MODULES_FILTER := $(foreach m,$(BOOT_KERNEL_MODULES),%/$(m))

# Recovery ramdisk modules
RECOVERY_KERNEL_MODULES_FILTER := $(foreach m,$(RECOVERY_KERNEL_MODULES),%/$(m))
BOARD_VENDOR_RAMDISK_KERNEL_MODULES += \
     $(filter $(BOOT_KERNEL_MODULES_FILTER) \
                $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))

# ALL modules land in /vendor/lib/modules so they could be rmmod/insmod'd,
# and modules.list actually limits us to the ones we intend to load.
BOARD_VENDOR_KERNEL_MODULES := $(KERNEL_MODULES)
# To limit /vendor/lib/modules to just the ones loaded, use:
# BOARD_VENDOR_KERNEL_MODULES := $(filter-out \
#     $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))

# Group set of /vendor/lib/modules loading order to recovery modules first,
# then remainder, subtracting both recovery and boot modules which are loaded
# already.
BOARD_VENDOR_KERNEL_MODULES_LOAD := \
        $(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
        $(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))
BOARD_VENDOR_KERNEL_MODULES_LOAD += \
        $(filter-out $(BOOT_KERNEL_MODULES_FILTER) \
            $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))

# NB: Load order governed by modules.load and not by $(BOOT_KERNEL_MODULES)
BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD := \
        $(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))

# Group set of /vendor/lib/modules loading order to boot modules first,
# then the remainder of recovery modules.
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD := \
    $(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD += \
    $(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
    $(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))

이 예는 보드 구성 파일에서 로컬로 지정되는 BOOT_KERNEL_MODULESRECOVERY_KERNEL_MODULES 의 관리하기 쉬운 하위 집합을 보여줍니다. 앞의 스크립트는 선택된 사용 가능한 커널 모듈에서 각 서브세트 모듈을 찾아서 채우고 두 번째 단계 초기화를 위해 리미닝 모듈을 남겨둡니다.

두 번째 단계 초기화의 경우 부팅 흐름을 차단하지 않도록 모듈 로딩을 서비스로 실행하는 것이 좋습니다. 오류 처리 및 완화 또는 모듈 로드 완료와 같은 기타 물류가 필요한 경우 다시 보고(또는 무시)될 수 있도록 셸 스크립트를 사용하여 모듈 로드를 관리합니다.

사용자 빌드에 없는 디버그 모듈 로드 실패를 무시할 수 있습니다. 이 실패를 무시하려면 vendor.device.modules.ready 속성을 설정하여 시작 화면으로 계속 진행하도록 init rc 스크립팅 부트 흐름의 이후 단계를 트리거합니다. /vendor/etc/init.insmod.sh 에 다음 코드가 있는 경우 다음 예제 스크립트를 참조하십시오.

#!/vendor/bin/sh
. . .
if [ $# -eq 1 ]; then
  cfg_file=$1
else
  # Set property even if there is no insmod config
  # to unblock early-boot trigger
  setprop vendor.common.modules.ready
  setprop vendor.device.modules.ready
  exit 1
fi

if [ -f $cfg_file ]; then
  while IFS="|" read -r action arg
  do
    case $action in
      "insmod") insmod $arg ;;
      "setprop") setprop $arg 1 ;;
      "enable") echo 1 > $arg ;;
      "modprobe") modprobe -a -d /vendor/lib/modules $arg ;;
     . . .
    esac
  done < $cfg_file
fi

하드웨어 rc 파일에서 one shot 서비스는 다음과 같이 지정할 수 있습니다.

service insmod-sh /vendor/etc/init.insmod.sh /vendor/etc/init.insmod.<hw>.cfg
    class main
    user root
    group root system
    Disabled
    oneshot

모듈이 첫 번째 단계에서 두 번째 단계로 이동한 후 추가 최적화를 수행할 수 있습니다. modprobe 차단 목록 기능을 사용하여 두 번째 단계 부팅 흐름을 분할하여 불필요한 모듈의 지연된 모듈 로드를 포함할 수 있습니다. 특정 HAL에서 독점적으로 사용하는 모듈의 로드는 HAL이 시작될 때만 모듈을 로드하도록 지연될 수 있습니다.

명백한 부팅 시간을 개선하기 위해 시작 화면 이후에 로드하는 데 더 도움이 되는 모듈 로드 서비스에서 모듈을 구체적으로 선택할 수 있습니다. 예를 들어, 초기화 부팅 흐름이 지워진 후 비디오 디코더 또는 Wi-Fi용 모듈을 명시적으로 늦게 로드할 수 있습니다(예: sys.boot_complete Android 속성 신호). 커널 드라이버가 없을 때 늦게 로드하는 모듈에 대한 HAL이 충분히 오래 차단되는지 확인합니다.

또는 부트 흐름 rc 스크립팅에서 init의 wait<file>[<timeout>] 명령을 사용하여 드라이버 모듈이 프로브 작업을 완료했음을 표시하기 위해 선택 sysfs 항목을 기다릴 수 있습니다. 이것의 예는 메뉴 그래픽을 표시하기 전에 디스플레이 드라이버가 복구 또는 fastbootd 의 백그라운드에서 로드를 완료하기를 기다리는 것입니다.

부트로더에서 CPU 주파수를 적당한 값으로 초기화

모든 SoC/제품이 부팅 루프 테스트 중 열 또는 전력 문제로 인해 최고 주파수에서 CPU를 부팅할 수 있는 것은 아닙니다. 그러나 부트로더가 모든 온라인 CPU의 주파수를 SoC/제품에 대해 가능한 한 안전하게 높게 설정해야 합니다. 이것은 완전 모듈식 커널에서 CPUfreq 드라이버가 로드되기 전에 init ramdisk 압축 해제가 발생하기 때문에 매우 중요합니다. 따라서 CPU가 부트로더에 의해 주파수의 하단에 남아 있으면 ramdisk 압축 해제 시간은 정적으로 컴파일된 커널(ramdisk 크기 차이를 조정한 후)보다 오래 걸릴 수 있습니다. 왜냐하면 CPU 집약적인 작업을 수행할 때 CPU 주파수가 매우 낮기 때문입니다. 작업(감압). 메모리/상호 연결 주파수에도 동일하게 적용됩니다.

부트로더에서 큰 CPU의 CPU 주파수 초기화

CPUfreq 드라이버가 로드되기 전에 커널은 크고 작은 CPU 주파수를 인식하지 못하고 현재 주파수에 대해 CPU의 스케쥴 용량을 확장하지 않습니다. 작은 CPU에서 로드가 충분히 높은 경우 커널은 스레드를 큰 CPU로 마이그레이션할 수 있습니다.

큰 CPU가 부트로더가 남겨두는 주파수에 대해 작은 CPU만큼 성능이 좋은지 확인하십시오. 예를 들어 큰 CPU가 동일한 주파수에 대해 작은 CPU보다 2배 더 성능이 좋지만 부트로더가 작은 CPU의 주파수를 1.5GHz로, 큰 CPU의 주파수를 300MHz로 설정하면 커널이 스레드를 큰 CPU로 옮기면 부팅 성능이 떨어집니다. 이 예에서 대용량 CPU를 750MHz로 부팅하는 것이 안전하다면 명시적으로 사용할 계획이 없더라도 그렇게 해야 합니다.

드라이버는 첫 번째 단계 초기화에서 펌웨어를 로드해서는 안 됩니다.

펌웨어를 첫 번째 단계 초기화에서 로드해야 하는 불가피한 경우가 있을 수 있습니다. 그러나 일반적으로 드라이버는 특히 장치 프로브 컨텍스트에서 첫 번째 단계 초기화에서 펌웨어를 로드해서는 안 됩니다. 첫 번째 단계 init에서 펌웨어를 로드하면 첫 번째 단계 ramdisk에서 펌웨어를 사용할 수 없는 경우 전체 부팅 프로세스가 중단됩니다. 그리고 펌웨어가 1단계 램디스크에 있더라도 여전히 불필요한 지연이 발생합니다.