Na tej stronie znajdziesz wskazówki, które pomogą Ci skrócić czas uruchamiania.
Usuwanie symboli debugowania z modułów
Podobnie jak w przypadku usuwania symboli debugowania z jądra na urządzeniu produkcyjnym, usuń też symbole debugowania z modułów. Usunięcie symboli debugowania z modułów pomaga skrócić czas uruchamiania, ponieważ zmniejsza:
- czas potrzebny na odczytanie plików binarnych z pamięci flash;
- czas potrzebny na rozpakowanie ramdysku;
- czas potrzebny na wczytanie modułów.
Usunięcie symboli debugowania z modułów może skrócić czas uruchamiania o kilka sekund.
Usuwanie symboli jest domyślnie włączone w kompilacji platformy Android, ale
aby je włączyć, ustaw
BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES w konfiguracji urządzenia
w folderze device/vendor/device.
Używanie kompresji LZ4 w przypadku jądra i ramdysku
Gzip generuje mniejszy skompresowany wynik niż LZ4, ale LZ4 rozpakowuje się szybciej niż Gzip. W przypadku jądra i modułów bezwzględne zmniejszenie rozmiaru pamięci masowej dzięki użyciu Gzip nie jest tak znaczące w porównaniu z korzyściami z czasu rozpakowywania LZ4.
Obsługa kompresji ramdysku LZ4 została dodana do kompilacji platformy Android za pomocą BOARD_RAMDISK_USE_LZ4. Tę opcję możesz ustawić w konfiguracji urządzenia. Kompresję jądra można ustawić za pomocą defconfig jądra.
Przełączenie na LZ4 powinno skrócić czas uruchamiania o 500–1000 ms.
Unikanie nadmiernego rejestrowania w sterownikach
W ARM64 i ARM32 wywołania funkcji, które znajdują się w odległości większej niż określona od miejsca wywołania, wymagają tabeli skoków (zwanej tabelą łączenia procedur lub PLT), aby można było zakodować pełny adres skoku. Ponieważ moduły są ładowane dynamicznie, te tabele skoków muszą zostać naprawione podczas ładowania modułu. Wywołania, które wymagają przeniesienia, są nazywane wpisami przeniesienia z jawnymi składnikami (lub w skrócie RELA) w formacie ELF.
Jądro systemu Linux przeprowadza optymalizację rozmiaru pamięci (np. optymalizację trafień w pamięci podręcznej) podczas przydzielania PLT. W przypadku tego zatwierdzenia upstream,
schemat optymalizacji ma złożoność O(N^2), gdzie N to liczba
RELA typu R_AARCH64_JUMP26 lub R_AARCH64_CALL26. Dlatego mniejsza liczba RELA tych typów pomaga skrócić czas wczytywania modułu.
Jednym z typowych wzorców kodowania, który zwiększa liczbę RELA R_AARCH64_CALL26 lub R_AARCH64_JUMP26, jest nadmierne rejestrowanie w sterowniku. Każde wywołanie printk() lub innego schematu rejestrowania zwykle dodaje wpis RELA CALL26/JUMP26. W tekście zatwierdzenia w zatwierdzeniu upstream zauważ, że nawet po optymalizacji wczytanie 6 modułów zajmuje około 250 ms. Dzieje się tak, ponieważ te 6 modułów to 6 modułów z największą ilością rejestrowania.
Ograniczenie rejestrowania może skrócić czas uruchamiania o 100–300 ms w zależności od tego, jak nadmierne jest obecne rejestrowanie.
Selektywne włączanie sondowania asynchronicznego
Gdy moduł jest ładowany, a urządzenie, które obsługuje, zostało już wypełnione z DT (devicetree) i dodane do jądra sterownika, sondowanie urządzenia odbywa się w kontekście wywołania module_init(). Gdy sondowanie urządzenia odbywa się w kontekście module_init(), moduł nie może zakończyć ładowania, dopóki nie zakończy się sondowanie. Ponieważ ładowanie modułu jest w większości szeregowe, urządzenie, którego sondowanie trwa stosunkowo długo, spowalnia czas uruchamiania.
Aby uniknąć dłuższego czasu uruchamiania, włącz sondowanie asynchroniczne w przypadku modułów, których sondowanie urządzeń trwa dłużej. Włączenie sondowania asynchronicznego w przypadku wszystkich modułów może nie być korzystne, ponieważ czas potrzebny na rozwidlenie wątku i rozpoczęcie sondowania może być tak długi jak czas potrzebny na sondowanie urządzenia.
Problemy z czasem mogą powodować urządzenia podłączone przez wolną magistralę, taką jak I2C, urządzenia, które ładują oprogramowanie sprzętowe w funkcji sondowania, oraz urządzenia, które wykonują wiele inicjowania sprzętu. Najlepszym sposobem na zidentyfikowanie, kiedy to się dzieje, jest zebranie czasu sondowania dla każdego sterownika i posortowanie go.
Aby włączyć sondowanie asynchroniczne w przypadku modułu, nie wystarczy tylko
ustawić flagi PROBE_PREFER_ASYNCHRONOUS
w kodzie sterownika. W przypadku modułów musisz też 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 skrócić czas uruchamiania o 100–500 ms w zależności od sprzętu i sterowników.
Sondowanie sterownika CPUfreq tak wcześnie, jak to możliwe
Im wcześniej sterownik CPUfreq przeprowadzi sondowanie, tym szybciej możesz zwiększyć częstotliwość procesora do maksymalnej (lub ograniczonej termicznie maksymalnej) podczas uruchamiania. Im szybszy procesor, tym szybsze uruchamianie. Ta wskazówka dotyczy też sterowników devfreq, które kontrolują częstotliwość DRAM, pamięci i połączeń.
W przypadku modułów kolejność ładowania może zależeć od poziomu initcall oraz kolejności kompilowania lub łączenia sterowników. Użyj aliasu MODULE_SOFTDEP(), aby sterownik cpufreq był jednym z pierwszych modułów do załadowania.
Oprócz wczesnego załadowania modułu musisz też upewnić się, że wszystkie zależności dotyczące sondowania sterownika CPUfreq zostały już sondowane. Jeśli na przykład do kontrolowania częstotliwości procesora potrzebujesz zegara lub regulatora, upewnij się, że zostały one sondowane jako pierwsze. Może też być konieczne załadowanie sterowników termicznych przed sterownikiem CPUfreq, jeśli podczas uruchamiania procesory mogą się zbytnio nagrzewać. Dlatego zrób wszystko, aby sterowniki CPUfreq i odpowiednie sterowniki devfreq sondowały się jak najwcześniej.
Oszczędności wynikające z wczesnego sondowania sterownika CPUfreq mogą być bardzo małe lub bardzo duże w zależności od tego, jak wcześnie można je sondować i z jaką częstotliwością program rozruchowy pozostawia procesory.
Przenoszenie modułów do drugiego etapu inicjowania, partycji vendor lub vendor_dlkm
Ponieważ proces inicjowania pierwszego etapu jest szeregowy, nie ma wielu możliwości równoległego uruchamiania. Jeśli moduł nie jest potrzebny do zakończenia inicjowania pierwszego etapu, przenieś go do drugiego etapu inicjowania, umieszczając go w partycji vendor lub vendor_dlkm.
Inicjowanie pierwszego etapu nie wymaga sondowania kilku urządzeń, aby przejść do drugiego etapu inicjowania. Do normalnego uruchamiania potrzebne są tylko możliwości konsoli i pamięci flash.
Załaduj te niezbędne sterowniki:
watchdogresetcpufreq
W przypadku trybu przywracania i przestrzeni użytkownika fastbootd inicjowanie pierwszego etapu wymaga sondowania większej liczby urządzeń (np. USB) i wyświetlania. Zachowaj kopię tych modułów w ramdysku pierwszego etapu oraz w partycji vendor lub vendor_dlkm. Dzięki temu można je załadować w pierwszym etapie inicjowania w przypadku przywracania lub uruchamiania fastbootd. Nie ładuj jednak modułów trybu przywracania w pierwszym etapie inicjowania podczas normalnego uruchamiania. Moduły trybu przywracania można odłożyć do drugiego etapu inicjowania, aby skrócić czas uruchamiania. Wszystkie inne moduły, które nie są potrzebne w pierwszym etapie inicjowania, należy przenieść do partycji vendor lub vendor_dlkm.
Na podstawie listy urządzeń liściowych (np. UFS lub szeregowych)
dev needs.sh
skrypt znajduje wszystkie sterowniki, urządzenia i moduły potrzebne do sondowania zależności lub
dostawców (np. zegarów, regulatorów lub gpio).
Przenoszenie modułów do drugiego etapu inicjowania skraca czas uruchamiania w te sposoby:
- Zmniejszenie rozmiaru ramdysku.
- Umożliwia to szybsze odczytywanie z pamięci flash, gdy program rozruchowy ładuje ramdysk (szeregowy etap uruchamiania).
- Umożliwia to szybsze rozpakowywanie, gdy jądro rozpakowuje ramdysk (szeregowy etap uruchamiania).
- Drugi etap inicjowania działa równolegle, co ukrywa czas ładowania modułu z pracą wykonywaną w drugim etapie inicjowania.
Przeniesienie modułów do drugiego etapu może skrócić czas uruchamiania o 500–1000 ms w zależności od tego, ile modułów możesz przenieść do drugiego etapu inicjowania.
Logistyka ładowania modułów
Najnowsza kompilacja Androida zawiera konfiguracje płyty, które określają, które moduły są kopiowane na każdy etap i które moduły są ładowane. Ta sekcja skupia się na tym podzbiorze:
BOARD_VENDOR_RAMDISK_KERNEL_MODULES– lista modułów do skopiowania do ramdysku.BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD– lista modułów do załadowania w pierwszym etapie inicjowania.BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD– lista modułów do załadowania, gdy z ramdysku zostanie wybrany tryb przywracania lubfastbootd.BOARD_VENDOR_KERNEL_MODULES– lista modułów do skopiowania do partycji vendor lubvendor_dlkmw katalogu/vendor/lib/modules/.BOARD_VENDOR_KERNEL_MODULES_LOAD– lista modułów do załadowania w drugim etapie inicjowania.
Moduły uruchamiania i przywracania w ramdysku muszą też zostać skopiowane do partycji vendor lub vendor_dlkm w katalogu /vendor/lib/modules. Skopiowanie tych modułów do partycji vendor gwarantuje, że moduły nie będą niewidoczne podczas drugiego etapu inicjowania, co jest przydatne do debugowania i zbierania modinfo na potrzeby raportów o błędach.
Duplikacja powinna zajmować minimalną ilość miejsca w partycji vendor lub vendor_dlkm, o ile zestaw modułów uruchamiania jest zminimalizowany. Upewnij się, że plik modules.list dostawcy zawiera przefiltrowaną listę modułów w katalogu /vendor/lib/modules.
Przefiltrowana lista gwarantuje, że czas uruchamiania nie będzie zależeć od ponownego ładowania modułów (co jest kosztownym procesem).
Upewnij się, że moduły trybu przywracania są ładowane jako grupa. Moduły trybu przywracania można ładować w trybie przywracania lub na początku drugiego etapu inicjowania w każdym procesie uruchamiania.
Do wykonania tych działań możesz użyć plików Board.Config.mk urządzenia, jak 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 przedstawia łatwiejszy w zarządzaniu podzbiór BOOT_KERNEL_MODULES i RECOVERY_KERNEL_MODULES, który można określić lokalnie w plikach konfiguracyjnych płyty. Poprzedni skrypt znajduje i wypełnia każdy z modułów podzbioru z wybranych dostępnych modułów jądra, pozostawiając pozostałe moduły na drugi etap inicjowania.
W przypadku drugiego etapu inicjowania zalecamy uruchamianie ładowania modułu jako usługi, aby nie blokowało ono procesu uruchamiania. Użyj skryptu powłoki do zarządzania ładowaniem modułu, aby w razie potrzeby można było zgłaszać (lub ignorować) inne kwestie logistyczne, takie jak obsługa błędów i ich ograniczanie czy zakończenie ładowania modułu.
Możesz zignorować błąd ładowania modułu debugowania, który nie występuje w kompilacjach użytkownika.
Aby zignorować ten błąd, ustaw właściwość vendor.device.modules.ready, aby uruchomić późniejsze etapy skryptu init rc procesu uruchamiania i przejść do ekranu uruchamiania. Jeśli masz ten kod w pliku /vendor/etc/init.insmod.sh, zapoznaj się z tym przykładowym skryptem, if you have the following code
in
#!/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 sprzętowym rc usługę one shot można określić za pomocą tego kodu:
service insmod-sh /vendor/etc/init.insmod.sh /vendor/etc/init.insmod.<hw>.cfg
class main
user root
group root system
Disabled
oneshot
Po przeniesieniu modułów z pierwszego do drugiego etapu można wprowadzić dodatkowe optymalizacje. Możesz użyć funkcji blokowania modprobe, aby podzielić proces uruchamiania drugiego etapu i uwzględnić odroczone ładowanie modułów nieistotnych. Ładowanie modułów używanych wyłącznie przez określony HAL można odłożyć, aby ładować moduły tylko wtedy, gdy HAL jest uruchomiony.
Aby skrócić czas uruchamiania, możesz wybrać w usłudze ładowania modułów moduły, które lepiej się ładują po ekranie uruchamiania. Możesz na przykład jawnie opóźnić ładowanie modułów dekodera wideo lub Wi-Fi po wyczyszczeniu procesu uruchamiania init (np. sygnał właściwości Androida sys.boot_complete). Upewnij się, że HAL-e dla modułów z opóźnionym ładowaniem blokują się wystarczająco długo, gdy sterowniki jądra nie są obecne.
Możesz też użyć polecenia wait<file>[<timeout>] init w skrypcie rc procesu uruchamiania, aby poczekać, aż wybrane sysfs pokażą, że moduły sterownika zakończyły operacje sondowania. Przykładem jest czekanie na zakończenie ładowania sterownika wyświetlacza w tle przywracania lub fastbootd przed wyświetleniem grafiki menu.
Inicjowanie częstotliwości procesora do rozsądnej wartości w programie rozruchowym
Nie wszystkie układy SoC i produkty mogą uruchamiać procesor z najwyższą częstotliwością ze względu na problemy z temperaturą lub zasilaniem podczas testów pętli uruchamiania. Upewnij się jednak, że program rozruchowy ustawia częstotliwość wszystkich procesorów online na najwyższą możliwą wartość dla układu SoC lub produktu. Jest to bardzo ważne, ponieważ w przypadku w pełni modułowego jądra rozpakowywanie ramdysku init odbywa się przed załadowaniem sterownika CPUfreq. Jeśli więc program rozruchowy pozostawi procesor na niższym końcu jego częstotliwości, czas rozpakowywania ramdysku może być dłuższy niż w przypadku statycznie skompilowanego jądra (po uwzględnieniu różnicy rozmiaru ramdysku), ponieważ częstotliwość procesora będzie bardzo niska podczas wykonywania zadań intensywnie wykorzystujących procesor (rozpakowywanie). To samo dotyczy częstotliwości pamięci i połączeń.
Inicjowanie częstotliwości procesora dużych procesorów w programie rozruchowym
Zanim zostanie załadowany sterownik CPUfreq, jądro nie zna częstotliwości procesora i nie skaluje pojemności harmonogramu procesora do jego 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 wydajne jak małe procesory w przypadku częstotliwości, z jaką program rozruchowy je pozostawia. Jeśli na przykład duży procesor jest 2 razy wydajniejszy niż mały procesor przy tej samej częstotliwości, ale program rozruchowy ustawia częstotliwość małego procesora na 1,5 GHz, a dużego procesora na 300 MHz, 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 z częstotliwością 750 MHz, zrób to, nawet jeśli nie planujesz go jawnie używać.
Sterowniki nie powinny ładować oprogramowania sprzętowego w pierwszym etapie inicjowania
Mogą wystąpić nieuniknione przypadki, w których oprogramowanie sprzętowe trzeba załadować w pierwszym etapie inicjowania. Ogólnie jednak sterowniki nie powinny ładować oprogramowania sprzętowego w pierwszym etapie inicjowania, zwłaszcza w kontekście sondowania urządzenia. Ładowanie oprogramowania sprzętowego w pierwszym etapie inicjowania powoduje zatrzymanie całego procesu uruchamiania, jeśli oprogramowanie sprzętowe nie jest dostępne w ramdysku pierwszego etapu. Nawet jeśli oprogramowanie sprzętowe jest obecne w ramdysku pierwszego etapu, nadal powoduje niepotrzebne opóźnienie.