Optymalizacja czasu rozruchu

Na tej stronie znajduje się zestaw wskazówek, spośród których możesz skorzystać, aby skrócić czas uruchamiania systemu.

Usuń symbole debugowania z modułów

Podobnie jak symbole debugowania są usuwane z jądra na urządzeniu produkcyjnym, upewnij się, że usuwasz także symbole debugowania z modułów. Usunięcie symboli debugowania z modułów pomaga w czasie uruchamiania, skracając następujące elementy:

  • Czas potrzebny na odczytanie plików binarnych z pamięci flash.
  • Czas potrzebny na dekompresję ramdysku.
  • Czas potrzebny na załadowanie modułów.

Usunięcie symbolu debugowania z modułów może zaoszczędzić kilka sekund podczas uruchamiania.

Usuwanie symboli jest domyślnie włączone w kompilacji platformy Android, ale aby je jawnie włączyć, ustaw BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES w konfiguracji specyficznej dla urządzenia w sekcji urządzenie/ vendor / device .

Użyj kompresji LZ4 dla jądra i ramdysku

Gzip generuje mniejszy skompresowany plik wyjściowy w porównaniu do LZ4, ale LZ4 dekompresuje szybciej niż Gzip. W przypadku jądra i modułów bezwzględne zmniejszenie rozmiaru pamięci w wyniku użycia Gzipa nie jest aż tak znaczące w porównaniu z korzyścią w postaci czasu dekompresji, jaką zapewnia LZ4.

Do platformy Android w wersji BOARD_RAMDISK_USE_LZ4 dodano obsługę kompresji ramdysku LZ4. Możesz ustawić tę opcję w konfiguracji specyficznej dla urządzenia. Kompresję jądra można ustawić za pomocą defconfig jądra.

Przejście na LZ4 powinno zapewnić szybszy czas uruchamiania od 500 ms do 1000 ms.

Unikaj nadmiernego logowania się do sterowników

W ARM64 i ARM32 wywołania funkcji, które znajdują się w większej odległości od miejsca wywołania, wymagają tabeli skoku (zwanej tablicą łączenia procedur, w skrócie PLT), aby móc zakodować adres pełnego skoku. Ponieważ moduły są ładowane dynamicznie, tabele skoków muszą zostać naprawione podczas ładowania modułów. Wywołania wymagające relokacji nazywane są wpisami relokacji z wyraźnymi dodatkami (lub w skrócie RELA) wpisami w formacie ELF.

Jądro Linuksa dokonuje pewnej optymalizacji rozmiaru pamięci (takiej jak optymalizacja trafień w pamięci podręcznej) podczas alokacji PLT. W przypadku tego zatwierdzenia w górę schemat optymalizacji ma złożoność O(N^2), gdzie N jest liczbą RELA typu R_AARCH64_JUMP26 lub R_AARCH64_CALL26 . Zatem posiadanie mniejszej liczby RELA tego typu jest pomocne w skracaniu czasu ładowania modułu.

Jednym z powszechnych wzorców kodowania, który zwiększa liczbę RELA R_AARCH64_CALL26 lub R_AARCH64_JUMP26 , jest nadmierne logowanie sterownika. Każde wywołanie printk() lub innego schematu rejestrowania zazwyczaj dodaje wpis CALL26 / JUMP26 RELA. W tekście zatwierdzenia w zatwierdzeniu poprzedzającym zwróć uwagę, że nawet po optymalizacji załadowanie sześciu modułów zajmuje około 250 ms — to dlatego, że te sześć modułów było sześcioma modułami o największej liczbie rejestrowań.

Ograniczenie rejestrowania może zaoszczędzić około 100–300 ms czasu uruchamiania, w zależności od tego, jak nadmierne jest istniejące rejestrowanie.

Włącz sondowanie asynchroniczne selektywnie

Po załadowaniu modułu, jeśli obsługiwane przez niego urządzenie zostało już pobrane z DT (drzewa urządzeń) i dodane do rdzenia sterownika, wówczas sprawdzenie urządzenia odbywa się w kontekście wywołania module_init() . Po zakończeniu sondy urządzenia w kontekście module_init() moduł nie może zakończyć ładowania, dopóki sonda nie zakończy się. Ponieważ ładowanie modułów jest w większości serializowane, urządzenie, którego sprawdzenie zajmuje stosunkowo dużo czasu, spowalnia czas rozruchu.

Aby uniknąć wolniejszego czasu uruchamiania, włącz sondowanie asynchroniczne dla modułów, których sondowanie urządzeń zajmuje trochę czasu. Włączenie sondowania asynchronicznego dla wszystkich modułów może nie być korzystne, ponieważ czas potrzebny na rozwidlenie wątku i uruchomienie sondy może być tak samo długi, jak czas potrzebny na sondowanie urządzenia.

Urządzenia podłączone za pośrednictwem wolnej magistrali, takiej jak I2C, urządzenia ładujące oprogramowanie sprzętowe w ramach funkcji sondy oraz urządzenia, które wykonują dużo inicjalizacji sprzętu, mogą prowadzić do problemów z taktowaniem. Najlepszym sposobem na określenie, kiedy tak się dzieje, jest zebranie czasu sondy dla każdego kierowcy i posortowanie go.

Aby włączyć sondowanie asynchroniczne dla modułu, nie wystarczy ustawić w kodzie sterownika jedynie flagę PROBE_PREFER_ASYNCHRONOUS . W przypadku modułów musisz także dodać module_name .async_probe=1 w wierszu poleceń jądra lub przekazać async_probe=1 jako parametr modułu podczas ładowania modułu za pomocą modprobe lub insmod .

Włączenie sondowania asynchronicznego może zaoszczędzić około 100–500 ms czasu uruchamiania, w zależności od sprzętu/sterowników.

Sprawdź sterownik CPUfreq tak wcześnie, jak to możliwe

Im wcześniej sterownik CPUfreq sonduje, tym szybciej będzie można skalować częstotliwość procesora do maksimum (lub jakiegoś maksimum ograniczonego termicznie) podczas rozruchu. Im szybszy procesor, tym szybszy rozruch. Ta wytyczna dotyczy również sterowników devfreq , które kontrolują pamięć DRAM, pamięć i częstotliwość połączeń.

W przypadku modułów kolejność ładowania może zależeć od poziomu initcall oraz kolejności kompilacji lub łączenia sterowników. Użyj aliasu MODULE_SOFTDEP() , aby upewnić się, że sterownik cpufreq znajduje się wśród kilku pierwszych modułów do załadowania.

Oprócz wcześniejszego załadowania modułu, musisz także upewnić się, że wszystkie zależności potrzebne do sprawdzenia sterownika CPUfreq zostały również sprawdzone. Na przykład, jeśli potrzebujesz zegara lub uchwytu regulatora do kontrolowania częstotliwości procesora, upewnij się, że zostały one najpierw sprawdzone. Lub może być konieczne załadowanie sterowników termicznych przed sterownikiem CPUfreq, jeśli możliwe jest, że procesory zbyt mocno się nagrzeją podczas uruchamiania. Zrób więc wszystko, co w Twojej mocy, aby upewnić się, że sterowniki CPUfreq i odpowiednie sterowniki devfreq zostaną sprawdzone tak wcześnie, jak to możliwe.

Oszczędności wynikające z wcześniejszego sprawdzenia sterownika CPUfreq mogą być od bardzo małych do bardzo dużych, w zależności od tego, jak wcześnie można je sprawdzić i z jaką częstotliwością program ładujący pozostawia procesory.

Przenieś moduły do ​​partycji init drugiego etapu, dostawcy lub dostawcy_dlkm

Ponieważ proces inicjowania pierwszego etapu jest serializowany, nie ma zbyt wielu możliwości zrównoleglenia procesu rozruchu. Jeśli moduł nie jest potrzebny do zakończenia pierwszego etapu init, przenieś moduł do drugiego etapu init, umieszczając go na partycji sprzedawca lub vendor_dlkm .

Init pierwszego stopnia nie wymaga sondowania kilku urządzeń, aby przejść do init drugiego etapu. Do normalnego przebiegu rozruchu potrzebne są jedynie funkcje konsoli i pamięci flash.

Załaduj następujące niezbędne sterowniki:

  • pies podwórzowy
  • Resetowanie
  • częstotliwość procesora

W przypadku odzyskiwania i trybu fastbootd przestrzeni użytkownika pierwszy etap init wymaga większej liczby urządzeń do sondowania (takich jak USB) i wyświetlania. Zachowaj kopię tych modułów na ramdysku pierwszego stopnia oraz na partycji sprzedawca lub vendor_dlkm . Umożliwia to załadowanie ich do pierwszego etapu inicjalizacji w celu odzyskania lub uruchomienia w trybie fastbootd . Nie ładuj jednak modułów trybu odzyskiwania w pierwszym etapie inicjalizacji podczas normalnego procesu rozruchu. Moduły trybu odzyskiwania można odłożyć do drugiego etapu inicjacji, aby skrócić czas rozruchu. Wszystkie inne moduły, które nie są potrzebne w pierwszym etapie inicjalizacji, należy przenieść na partycję dostawcy lub vendor_dlkm .

Biorąc pod uwagę listę urządzeń liściastych (na przykład UFS lub serial), skrypt dev needs.sh wyszukuje wszystkie sterowniki, urządzenia i moduły potrzebne do sprawdzenia zależności lub dostawców (na przykład zegary, regulatory lub gpio ).

Przeniesienie modułów do drugiego etapu init skraca czas uruchamiania w następujący sposób:

  • Zmniejszenie rozmiaru dysku Ramdysk.
    • Zapewnia to szybsze odczyty pamięci flash, gdy program ładujący ładuje ramdysk (serializowany krok rozruchu).
    • Zapewnia to większą prędkość dekompresji, gdy jądro dekompresuje ramdysk (serializowany etap rozruchu).
  • Init drugiego etapu działa równolegle, co ukrywa czas ładowania modułu z pracą wykonywaną w drugim etapie init.

Przeniesienie modułów do drugiego etapu może zaoszczędzić 500–1000 ms czasu uruchamiania, w zależności od tego, ile modułów możesz przenieść do drugiego etapu inicjowania.

Logistyka załadunku modułów

Najnowsza wersja Androida zawiera konfiguracje płytek, które kontrolują, które moduły są kopiowane do każdego etapu i które moduły są ładowane. W tej sekcji skupiono się na następującym podzbiorze:

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES . Ta lista modułów do skopiowania na ramdysk.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD . Ta lista modułów, które mają zostać załadowane w pierwszym etapie init.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD . Ta lista modułów do załadowania po wybraniu z ramdysku odzyskiwania lub fastbootd .
  • BOARD_VENDOR_KERNEL_MODULES . Ta lista modułów, które mają zostać skopiowane do partycji dostawcy lub vendor_dlkm w katalogu /vendor/lib/modules/ .
  • BOARD_VENDOR_KERNEL_MODULES_LOAD . Ta lista modułów, które mają zostać załadowane w drugim etapie init.

Moduły rozruchu i odtwarzania z ramdysku należy również skopiować na partycję dostawcy lub vendor_dlkm w lokalizacji /vendor/lib/modules . Kopiowanie tych modułów na partycję dostawcy zapewnia, że ​​moduły nie będą niewidoczne podczas drugiego etapu inicjalizacji, co jest przydatne do debugowania i zbierania modinfo do raportów o błędach.

Duplikacja powinna kosztować minimalną ilość miejsca na partycji dostawcy lub vendor_dlkm , o ile zestaw modułów rozruchowych jest zminimalizowany. Upewnij się, że plik modules.list dostawcy zawiera przefiltrowaną listę modułów w /vendor/lib/modules . Filtrowana lista gwarantuje, że ponowne ładowanie modułów nie będzie miało wpływu na czas rozruchu (co jest kosztownym procesem).

Upewnij się, że moduły trybu odzyskiwania ładują się jako grupa. Ładowanie modułów trybu odzyskiwania można przeprowadzić w trybie odzyskiwania lub na początku drugiego etapu init w każdym procesie rozruchu.

Możesz użyć plików Board.Config.mk urządzenia, aby wykonać te czynności, jak pokazano w poniższym przykładzie:

# 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)))

Ten przykład pokazuje łatwiejszy w zarządzaniu podzbiór BOOT_KERNEL_MODULES i RECOVERY_KERNEL_MODULES , który należy określić lokalnie w plikach konfiguracyjnych płyty. Powyższy skrypt znajduje i wypełnia każdy z podzbiorów modułów z wybranych dostępnych modułów jądra, pozostawiając pozostałe moduły do ​​drugiego etapu inicjalizacji.

W przypadku inicjalizacji drugiego etapu zalecamy uruchomienie ładowania modułu jako usługi, aby nie blokowało to przepływu rozruchu. Użyj skryptu powłoki do zarządzania ładowaniem modułów, aby w razie potrzeby można było zgłaszać (lub ignorować) inne logistyki, takie jak obsługa błędów i łagodzenie ich lub zakończenie ładowania modułów.

Możesz zignorować błąd ładowania modułu debugowania, który nie występuje w kompilacjach użytkowników. Aby zignorować to niepowodzenie, ustaw właściwość vendor.device.modules.ready tak, aby uruchamiała późniejsze etapy uruchamiania skryptu init rc w celu kontynuowania działania na ekranie startowym. Jeśli masz następujący kod w /vendor/etc/init.insmod.sh , odwołaj się do poniższego przykładowego skryptu:

#!/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

W sprzętowym pliku rc usługę one shot można określić za pomocą:

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

Dodatkowe optymalizacje można wprowadzić po przejściu modułów z pierwszego do drugiego etapu. Możesz użyć funkcji listy bloków modprobe, aby podzielić przepływ rozruchu drugiego etapu, aby uwzględnić odroczone ładowanie modułów nieistotnych. Ładowanie modułów używanych wyłącznie przez określoną warstwę HAL można odłożyć w celu załadowania modułów dopiero po uruchomieniu warstwy HAL.

Aby skrócić pozorny czas uruchamiania, możesz w usłudze ładowania modułów wybrać moduły, które lepiej sprzyjają ładowaniu po ekranie startowym. Na przykład można jawnie opóźnić ładowanie modułów dekodera wideo lub Wi-Fi po wyczyszczeniu początkowego przepływu rozruchu (na przykład sys.boot_complete sygnał właściwości Androida). Upewnij się, że warstwy HAL dla modułów ładujących się późno blokują się wystarczająco długo, gdy nie ma sterowników jądra.

Alternatywnie możesz użyć polecenia init wait<file>[<timeout>] w skrypcie rc przepływu rozruchu, aby poczekać, aż wybrane wpisy sysfs pokażą, że moduły sterownika zakończyły operacje sondy. Przykładem tego jest oczekiwanie na zakończenie ładowania sterownika ekranu w tle odzyskiwania lub fastbootd , przed wyświetleniem grafiki menu.

Zainicjuj częstotliwość procesora do rozsądnej wartości w programie ładującym

Nie wszystkie SoC/produkty mogą być w stanie uruchomić procesor z najwyższą częstotliwością ze względu na problemy termiczne lub energetyczne podczas testów pętli rozruchowej. Upewnij się jednak, że program ładujący ustawia częstotliwość wszystkich procesorów online na tak wysoką, jak to możliwe bezpiecznie dla SoC/produktu. Jest to bardzo ważne, ponieważ przy w pełni modułowym jądrze initowa dekompresja dysku RAM ma miejsce przed załadowaniem sterownika CPUfreq. Tak więc, jeśli program ładujący pozostawi procesor na dolnej granicy jego częstotliwości, czas dekompresji ramdysku może trwać dłużej niż w przypadku jądra skompilowanego statycznie (po uwzględnieniu różnicy w wielkości ramdysku), ponieważ częstotliwość procesora byłaby bardzo niska podczas wykonywania operacji intensywnie obciążających procesor praca (dekompresja). To samo dotyczy częstotliwości pamięci/połączeń.

Zainicjuj częstotliwość procesora dużych procesorów w bootloaderze

Przed załadowaniem sterownika CPUfreq jądro nie jest świadome małych i dużych częstotliwości procesora i nie skaluje zaplanowanej wydajności procesorów dla ich bieżącej częstotliwości. Jądro może migrować wątki do dużego procesora, jeśli obciążenie małego procesora jest wystarczająco duże.

Upewnij się, że duże procesory są co najmniej tak samo wydajne jak małe procesory pod względem częstotliwości, z jaką pozostawia je program ładujący. Na przykład, jeśli duży procesor jest 2 razy wydajniejszy niż mały procesor przy tej samej częstotliwości, ale program ładujący ustawia częstotliwość małego procesora do 1,5 GHz, a częstotliwość dużego procesora do 300 MHz, wówczas wydajność uruchamiania spadnie, jeśli jądro przeniesie wątek do dużego procesora. W tym przykładzie, jeśli można bezpiecznie uruchomić duży procesor przy 750 MHz, powinieneś to zrobić, nawet jeśli nie planujesz go jawnie używać.

Sterowniki nie powinny ładować oprogramowania sprzętowego w pierwszym etapie inicjacji

Mogą zaistnieć pewne nieuniknione przypadki, w których konieczne będzie załadowanie oprogramowania sprzętowego w pierwszym etapie inicjalizacji. Ogólnie rzecz biorąc, sterowniki nie powinny ładować żadnego oprogramowania sprzętowego w pierwszym etapie inicjalizacji, szczególnie w kontekście sondowania urządzenia. Ładowanie oprogramowania sprzętowego w pierwszym etapie init powoduje zatrzymanie całego procesu uruchamiania, jeśli oprogramowanie sprzętowe nie jest dostępne na dysku ramdysku pierwszego etapu. Nawet jeśli oprogramowanie układowe znajduje się na ramdysku pierwszego stopnia, nadal powoduje niepotrzebne opóźnienie.