Optymalizacja czasu uruchamiania

Na tej stronie znajdziesz wskazówki, które pomogą Ci skrócić czas uruchamiania.

Usuń symbole debugowania z modułów

Podobnie jak w przypadku symboli debugowania usuwanych z jądra na urządzeniu produkcyjnym, musisz też usunąć symbole debugowania z modułów. Usuwanie symboli debugowania z modułów skraca czas uruchamiania, ponieważ zmniejsza:

  • Czas potrzebny na odczyt plików binarnych we Flashu.
  • Czas potrzebny na rozpakowanie pliku ramdisk.
  • Czas potrzebny na załadowanie modułów.

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

Usuwanie symboli jest domyślnie włączone w kompilacji platformy Android, ale aby włączyć je wyraźnie, ustaw parametr BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES w konfiguracji dotyczącej konkretnego urządzenia w folderze device/vendor/device.

Kompresja LZ4 dla jądra i ramdisk

Gzip generuje mniejsze skompresowane dane wyjściowe niż LZ4, ale LZ4 kompresuje się szybciej niż Gzip. W przypadku jądra i modułów bezwzględna pamięć masowa rozmiar pliku z programu Gzip nie jest tak istotny w porównaniu korzyści czasu dekompresji LZ4.

Do tworzenia na platformie Android dodano obsługę kompresji LZ4 dla dysku RAM (BOARD_RAMDISK_USE_LZ4). Możesz ją skonfigurować w konfiguracji dotyczącej konkretnego urządzenia. Kompresję jądra można ustawić za pomocą defconfig jądra.

Przejście na LZ4 powinno przyspieszyć rozruch od 500 do 1000 ms.

Unikaj nadmiernego zapisywania sterowników

W procesorach ARM64 i ARM32 wywołania funkcji wykraczające poza konkretną odległość od witryna do rozmów wymaga tabeli łączenia procedur (PLT). w celu zakodowania pełnego adresu skoku. Ponieważ moduły są ładowane dynamicznie, te tabele przekierowań muszą być naprawiane podczas wczytywania modułów. Połączenia, które wymagają relokacja są nazywane wpisami relokacji z jawnymi dodatkami (w skrócie RELA) w formacie ELF.

Podczas przydzielania PLT jądro Linuxa wykonuje optymalizację rozmiaru pamięci (np. optymalizację trafień do pamięci podręcznej). W przypadku tego elementu nadrzędnego zatwierdzenia, schemat optymalizacji ma złożoność O(N^2), gdzie N to liczba RELA typu R_AARCH64_JUMP26 lub R_AARCH64_CALL26. Mniej takich treści pomagają skrócić czas wczytywania modułów.

Jednym z częstych wzorców kodowania, który zwiększa liczbę R_AARCH64_CALL26 lub R_AARCH64_JUMP26 RELA, jest nadmierne rejestrowanie w sterowniku. Każde wywołanie funkcji printk() lub dowolnego innego schematu rejestrowania zwykle powoduje dodanie wpisu CALL26/JUMP26 RELA. W tekście zatwierdzenia w elemencie nadrzędnym zatwierdzenia, Nawet po optymalizacji 6 modułów zajmuje ok. 250 ms. ponieważ wszystkie te 6 modułów było 6 pierwszymi modułami z z największym zapisywaniem danych.

Zmniejszenie ilości logowania może pozwolić zaoszczędzić 100–300 ms czasu uruchamiania w zależności od tego, jak nadmierne jest obecne logowanie.

Włącz sondowanie asynchroniczne, selektywnie

Gdy wczytany moduł obsługuje urządzenie, które zostało już wypełnione z poziomu DT (drzewo urządzenia) i dodane do rdzenia sterownika, wówczas badanie urządzenia jest wykonywane w kontekście wywołania module_init(). Gdy module_init() przeprowadza skanowanie urządzenia, moduł nie może zakończyć wczytywania, dopóki skanowanie nie zostanie ukończone. Ponieważ wczytywanie modułów jest w większości serializowane, urządzenie, które potrzebuje stosunkowo dużo czasu na przeprowadzenie sondowania, wydłuża czas uruchamiania.

Aby uniknąć wydłużonego czasu uruchamiania, włącz asynchroniczne sprawdzanie w przypadku modułów, które potrzebują więcej czasu na sprawdzenie urządzeń. Włączanie asynchronicznego sondowania dla wszystkich modułów może nie być korzystne, ponieważ czas potrzebny na rozdzielenie wątku i uruchomienie sondowania może być taki sam jak czas potrzebny na sondowanie urządzenia.

urządzeń połączonych za pośrednictwem wolnej magistrali, np. I2C, oraz urządzeń, które korzystają z sieci wczytywane oprogramowanie układowe w funkcji sondy oraz urządzenia wykonujące wiele zadań sprzętowych. może to spowodować problem z czasem. Najlepszym sposobem na określenie, kiedy to nastąpi, jest zebranie czasu próbkowania dla każdego sterownika i posortowanie go.

Aby włączyć sondowanie asynchroniczne w module, nie wystarczy jedynie ustaw PROBE_PREFER_ASYNCHRONOUS w kodzie kierowcy. W przypadku modułów musisz też dodać module_name.async_probe=1 do wiersza poleceń jądra lub przekazać async_probe=1 jako parametr modułu podczas wczytywania modułu za pomocą modprobe lub insmod.

Włączenie sondowania asynchronicznego pozwala zaoszczędzić około 100–500 ms przy uruchamianiu w zależności od używanego sprzętu i sterowników.

Jak najszybciej sprawdź sterownik CPUfreq.

Im wcześniej sonduje sterownik CPUfreq, tym szybciej można skalować procesor do maksymalnej (lub częściowej lub ograniczonej termicznej) podczas uruchamiania. Im szybszy procesor, tym szybsze uruchamianie. Ta zasada dotyczy również: devfreq sterowniki, które kontrolują częstotliwość DRAM, pamięci i połączeń międzysieciowych.

W przypadku modułów kolejność obciążeń może zależeć od poziomu initcall skompilować lub połączyć sterowniki. Użyj aliasu MODULE_SOFTDEP(), aby sprawdź, czy sterownik cpufreq jest jednym z pierwszych modułów do wczytania.

Oprócz wczesnego załadowania modułu należy również upewnić się, że wszystkie zależności umożliwiające sondowanie sterownika CPUfreq. Jeśli na przykład potrzebujesz uchwytu zegara lub regulatora do sterowania częstotliwością procesora, upewnij się, najpierw są sprawdzani. Może też być konieczne załadowanie sterowników termicznych przed sterownikiem CPUfreq, jeśli istnieje możliwość, że procesory staną się zbyt gorące podczas uruchamiania. Zrób, co w Twojej mocy, aby interfejsy CPUfreq i devfreq sprawdzały się jak najwcześniej.

Oszczędności wynikające z wczesnego sondowania sterownika CPUfreq mogą być od bardzo niewielkich do bardzo dużych. w zależności od tego, jak szybko i z jaką częstotliwością chcesz je zbadać program rozruchowy pozostawia procesory w tym trybie.

Przenoszenie modułów do drugiej fazy inicjalizacji, partycji dostawcy lub partycji vendor_dlkm

Ponieważ proces inicjalizacji pierwszego etapu jest serializowany, nie ma wielu możliwości równoległego uruchamiania procesu uruchamiania. Jeśli moduł nie jest potrzebny do do zakończenia pierwszego etapu, przenieś moduł do inicjowania drugiego etapu, umieszczając go w partycji dostawcy lub vendor_dlkm.

Inicjowanie pierwszego etapu nie wymaga sondowania kilku urządzeń, aby przejść do drugiego etapu init. Potrzebne są tylko możliwości konsoli i pamięci flash podczas uruchamiania.

Załaduj te niezbędne sterowniki:

  • watchdog
  • reset
  • cpufreq

W przypadku trybu odzyskiwania i przestrzeni użytkownika fastbootd pierwsza faza inicjalizacji wymaga więcej urządzeń do sondowania (np. USB) i wyświetlacza. Zachowaj kopie tych modułów w na dysku RAM pierwszego etapu oraz w partycji dostawcy lub vendor_dlkm. Dzięki temu można je załadować na pierwszym etapie inicjalizacji w celu odzyskiwania lub fastbootduruchamiania. Nie wczytuj jednak modułów trybu przywracania w ramach pierwszego etapu inicjalizacji podczas normalnego procesu uruchamiania. Aby skrócić czas uruchamiania, moduły trybu odzyskiwania można odroczyć do drugiej fazy inicjalizacji. Wszystkie inne moduły, które nie są potrzebne na etapie inicjalizacji 1, należy przenieść do partycji dostawcy lub vendor_dlkm.

Na podstawie listy urządzeń końcowych (np. UFS lub seryjnych) skrypt dev needs.sh odnajduje wszystkie sterowniki, urządzenia i moduły potrzebne do sprawdzenia zależności lub dostawców (np. zegarów, regulatorów lub gpio).

Przeniesienie modułów do drugiej fazy inicjalizacji skraca czas uruchamiania w następujący sposób:

  • Zmniejszenie rozmiaru dysku RAM.
    • Dzięki temu wczytywanie z pamięci flash jest szybsze, gdy bootloader wczytuje ramdysk (seryalizacja kroku rozruchu).
    • Przyspiesza to dekompresję, gdy jądro dekompresuje ramdisk (serializowany krok uruchamiania).
  • Drugi etap inicjalizacji działa równolegle, co ukrywa czas wczytywania modułu dzięki pracy wykonywanej na drugim etapie inicjalizacji.

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

Moduł wczytujący informacje związane z logistyką

Najnowsza kompilacja Androida zawiera konfiguracje płyt, które określają, które są kopiowane do poszczególnych etapów oraz które się ładują. Ta sekcja skupia się w następującym podzbiorze:

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES. Lista modułów do skopiowania na dysk RAM.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD Ta lista modułów do załadowania w pierwszym etapie.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD. Lista modułów, które mają być wczytane, gdy z dysku RAM wybrano opcję odzyskiwania lub fastbootd.
  • BOARD_VENDOR_KERNEL_MODULES Ta lista modułów do skopiowania do dostawcy lub partycji vendor_dlkm w katalogu /vendor/lib/modules/.
  • BOARD_VENDOR_KERNEL_MODULES_LOAD Ta lista modułów do załadowania do rozpoczęcia drugiego etapu.

Moduł uruchamiania i moduł odzyskiwania na dysku RAM muszą też zostać skopiowane na partycję dostawcy lub vendor_dlkm w miejscu /vendor/lib/modules. Skopiowanie tych modułów do partycja dostawcy zapewnia, że moduły nie są niewidoczne podczas drugiego etapu, co przydaje się przy debugowaniu i zbieraniu danych modinfo na potrzeby raportów o błędach.

Duplikaty powinny zajmować minimalną ilość miejsca na partycji dostawcy lub vendor_dlkm, o ile zestaw modułów rozruchowych jest zminimalizowany. Upewnij się, że Plik modules.list zawiera przefiltrowaną listę modułów w folderze /vendor/lib/modules. Przefiltrowana lista sprawia, że wczytywanie modułów nie wpływa na czas uruchamiania (co jest kosztowne).

Upewnij się, że moduły trybu odzyskiwania wczytują się jako grupa. Wczytuję moduły trybu przywracania można wykonać w trybie przywracania lub na początku drugiego etapu i inicj przy każdym rozruchu.

Za pomocą plików Board.Config.mk na urządzeniu możesz wykonywać te działania w takiej postaci, w jakiej zostały wykryte w tym 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 elementów BOOT_KERNEL_MODULESRECOVERY_KERNEL_MODULES, który można określić lokalnie w plikach konfiguracji tablicy. Poprzedni skrypt znajduje i wypełnia każdy z modułów podzbioru wybranych dostępnych modułów jądra, a pozostałe zainicjowanie etapu.

Na potrzeby inicjowania drugiego etapu zalecamy uruchomienie modułu jako usługi, dzięki czemu nie blokuje rozruchu. Zarządzanie wczytywaniem modułu za pomocą skryptu powłoki tak aby inne usługi logistyczne, np. obsługa błędów i ich łagodzenie, lub obciążenie modułów realizacji i można je zgłosić ponownie (lub zignorować), w razie potrzeby.

Możesz zignorować błąd wczytywania modułu debugowania, który nie występuje w kompilacji użytkownika. Aby zignorować ten błąd, ustaw we właściwości vendor.device.modules.ready wartość aktywuj późniejsze etapy procesu rozruchowego skryptu init rc, aby kontynuować uruchomienie ekranu. Jeśli w pliku /vendor/etc/init.insmod.sh masz ten kod:

#!/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 pliku rc sprzętu usługa one shot może być określona 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 przeniesieniu modułów z pierwszego etapu na drugi. Za pomocą funkcji modprobe blocklist możesz podzielić drugi etap procesu uruchamiania, aby uwzględnić opóźnione wczytywanie nieistotnych modułów. Wczytywanie modułów używanych wyłącznie przez konkretny interfejs HAL można opóźnić, aby wczytać je dopiero po uruchomieniu interfejsu.

Aby skrócić czas uruchamiania, możesz w usłudze ładowania modułów wybrać konkretne moduły, które są bardziej odpowiednie do ładowania po ekranie uruchamiania. Możesz na przykład wyraźnie opóźnić wczytywanie modułów o z dekodera wideo lub sieci Wi-Fi po wyczyszczeniu procesu inicjującego. (sys.boot_complete sygnał usługi na Androidzie). Upewnij się, że bloki HAL dla ładowanych późno modułów są blokowane na tyle długo, na ile to możliwe, gdy nie ma sterowników jądra.

Możesz też użyć w skrypcie uruchamiania komend wait<file>[<timeout>], aby poczekać na wybrane wpisy sysfs, które pokazują, że moduły sterownika zakończyły operacje sondowania. Przykładem może być oczekiwanie na zakończenie wczytywania sterownika wyświetlacza w tle podczas odzyskiwania lub fastbootd, zanim zostanie wyświetlona grafika menu.

Inicjowanie częstotliwości procesora do rozsądnej wartości w programie rozruchowym

Nie wszystkie układy SOC/produkty mogą uruchamiać procesor z najwyższą częstotliwością z powodu problemów z termostatem lub zasilaniem podczas testów pętli rozruchowej. Upewnij się jednak, że bootloader ustawia częstotliwość wszystkich onlineowych procesorów tak wysoko, jak to jest bezpieczne dla SoC lub produktu. To bardzo ważne, ponieważ dzięki pełnemu jeśli jest to modułowe jądro, inicjowana dekompresja pamięci RAM jest przeprowadzana przed można załadować sterownik. Jeśli więc bootloader pozostawi procesor przy niższej częstotliwości, czas dekompresji dysku RAM może być dłuższy niż w przypadku skompilowanego statycznie jądra (po uwzględnieniu różnicy w rozmiarze dysku RAM), ponieważ częstotliwość procesora byłaby bardzo niska podczas wykonywania prac intensywnie obciążających procesor (dekompresji). To samo dotyczy pamięci i częstotliwości połączeń.

Inicjowanie częstotliwości procesora dużych procesorów w programie rozruchowym

Zanim wczytany zostanie sterownik CPUfreq, jądro nie zna częstotliwości procesora i nie dostosowuje pojemności procesora do bieżącej częstotliwości. Jeśli obciążenie procesora o małej mocy jest wystarczająco duże, jądro może przenieść wątki na procesor o dużej mocy.

Upewnij się, że duże procesory mają co najmniej taką samą wydajność jak małe procesory w przypadku częstotliwości, z jaką działają. Jeśli na przykład duży procesor ma dwukrotnie większą wydajność niż mały procesor przy tej samej częstotliwości, ale bootloader ustawia częstotliwość małego procesora na 1,5 GHz, a dużego na 300 MHz, to wydajność rozruchu spadnie, jeśli jądro przeniesie wątek na duży procesor. W tym przykładzie, jeśli można bezpiecznie uruchomić duże urządzenie procesora o częstotliwości 750 MHz, należy go używać, nawet jeśli nie zamierzasz go bezpośrednio używać.

Sterowniki nie powinny ładować oprogramowania układowego w trakcie inicjowania pierwszego etapu

Mogą wystąpić nieuniknione sytuacje, w których oprogramowanie układowe musi zostać najpierw załadowane. zainicjowanie etapu. Z reguły sterowniki nie powinny ładować oprogramowania w pierwszym etapie zwłaszcza w kontekście sondy urządzenia. Ładowanie oprogramowania układowego w ramach pierwszej fazy inicjalizacji powoduje zatrzymanie całego procesu uruchamiania, jeśli oprogramowanie nie jest dostępne na dysku RAM pierwszej fazy. Nawet jeśli oprogramowanie układowe jest dostępne na pierwszym etapie, powoduje niepotrzebne opóźnienie.