Optymalizacja czasów rozruchu

Ten dokument zawiera wskazówki dla partnerów dotyczące skrócenia czasu uruchamiania określonych urządzeń z systemem Android. Czas rozruchu jest ważnym elementem wydajności systemu, ponieważ użytkownicy muszą poczekać na zakończenie rozruchu, zanim będą mogli korzystać z urządzenia. W przypadku urządzeń takich jak samochody, w których zimne uruchamianie systemu zdarza się częściej, szybki czas rozruchu ma kluczowe znaczenie (nikt nie lubi czekać kilkudziesięciu sekund tylko na wprowadzenie celu nawigacji).

Android 8.0 pozwala na skrócenie czasu uruchamiania, obsługując kilka ulepszeń w szeregu komponentów. W poniższej tabeli podsumowano tę poprawę wydajności (mierzoną na urządzeniach Google Pixel i Pixel XL).

Część Poprawa
Program rozruchowy
  • Oszczędność 1,6 s poprzez usunięcie dziennika UART
  • Zaoszczędzono 0,4 s, przechodząc na LZ4 z GZIP
Jądro urządzenia
  • Oszczędność 0,3 s poprzez usunięcie nieużywanych konfiguracji jądra i zmniejszenie rozmiaru sterownika
  • Zaoszczędzono 0,3 s dzięki optymalizacji pobierania wstępnego dm-verity
  • Zaoszczędzono 0,15 s, aby usunąć niepotrzebne oczekiwanie/testowanie w sterowniku
  • Oszczędność 0,12 s na usunięcie CONFIG_CC_OPTIMIZE_FOR_SIZE
Strojenie wejść/wyjść
  • Zaoszczędzono 2 sekundy przy normalnym rozruchu
  • Zaoszczędzono 25 sekund przy pierwszym uruchomieniu
init.*.rc
  • Oszczędność 1,5 s dzięki równoległemu wykonaniu poleceń inicjujących
  • Wczesne rozpoczęcie zygoty pozwoliło zaoszczędzić 0,25 s
  • Zaoszczędzono 0,22 s dzięki cpuset tune
Animacja uruchamiania
  • Rozpoczął się 2 s wcześniej przy starcie bez wyzwalania fsck, znacznie większy przy starcie z uruchamianiem fsck
  • Zaoszczędź 5 sekund na Pixel XL dzięki natychmiastowemu wyłączeniu animacji rozruchu
Polityka SELinuksa Zaoszczędzono 0,2 s dzięki genfscon

Optymalizacja bootloadera

Aby zoptymalizować program ładujący w celu skrócenia czasu uruchamiania:

  • Do logowania:
    • Wyłącz zapisywanie dziennika do UART, ponieważ przy dużej liczbie rejestrowań może to zająć dużo czasu. (Na urządzeniach Google Pixel odkryliśmy, że spowalnia program ładujący o 1,5 s).
    • Rejestruj tylko sytuacje błędów i rozważ przechowywanie innych informacji w pamięci za pomocą osobnego mechanizmu do odzyskiwania.
  • W przypadku dekompresji jądra rozważ użycie LZ4 dla współczesnego sprzętu zamiast GZIP (przykładowa łatka ). Należy pamiętać, że różne opcje kompresji jądra mogą mieć różne czasy ładowania i dekompresji, a niektóre opcje mogą działać lepiej niż inne w przypadku konkretnego sprzętu.
  • Sprawdź niepotrzebne czasy oczekiwania na odrzucenie/wejście w tryb specjalny i zminimalizuj je.
  • Przekaż czas rozruchu spędzony w programie ładującym do jądra jako linię cmdline.
  • Sprawdź zegar procesora i rozważ równoległość (wymaga obsługi wielu rdzeni) w celu ładowania jądra i inicjowania wejść/wyjść.

Optymalizacja wydajności we/wy

Poprawa wydajności operacji we/wy ma kluczowe znaczenie dla skrócenia czasu rozruchu, dlatego odczytanie wszystkiego, co nie jest konieczne, należy odłożyć do czasu rozruchu (w przypadku Google Pixel podczas rozruchu odczytywane jest około 1,2 GB danych).

Strojenie systemu plików

Jądro Linuksa uruchamia funkcję odczytu z wyprzedzeniem, gdy plik jest czytany od początku lub gdy bloki są odczytywane sekwencyjnie, co powoduje konieczność dostrojenia parametrów harmonogramu we/wy specjalnie pod kątem rozruchu (który ma inną charakterystykę obciążenia niż normalne aplikacje).

Urządzenia obsługujące płynne aktualizacje (A/B) czerpią ogromne korzyści z dostrajania systemu plików przy pierwszym uruchomieniu (np. 20s w Google Pixel). Dla przykładu dostroiliśmy następujące parametry dla 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
    ...

Różnorodny

  • Włącz rozmiar wstępnego pobierania skrótu dm-verity, używając konfiguracji jądra DM_VERITY_HASH_PREFETCH_MIN_SIZE (domyślny rozmiar to 128).
  • Aby uzyskać lepszą stabilność systemu plików i pomijanie wymuszonego sprawdzania, które występuje przy każdym uruchomieniu, użyj nowego narzędzia do generowania ext4, ustawiając TARGET_USES_MKE2FS w BoardConfig.mk.

Analizowanie wejść/wyjść

Aby zrozumieć działania we/wy podczas rozruchu, użyj danych ftrace jądra (używanych również przez systrace):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

Aby rozdzielić dostęp do każdego pliku, dokonaj następujących zmian w jądrze (tylko jądro rozwojowe; nie używaj w jądrach produkcyjnych):

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

Użyj poniższych skryptów, aby pomóc w analizie wydajności rozruchu.

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py Mierzy czas rozruchu z zestawieniem ważnych kroków w procesie rozruchu.
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace Zawiera informacje o dostępie do każdego pliku.
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace Zawiera zestawienie na poziomie systemu.

Optymalizacja init.*.rc

Init jest mostem prowadzącym od jądra do momentu ustanowienia frameworka, a urządzenia zwykle spędzają kilka sekund na różnych etapach inicjowania.

Uruchamianie zadań równolegle

Chociaż bieżąca inicjacja Androida jest mniej więcej procesem jednowątkowym, nadal możesz wykonywać niektóre zadania równolegle.

  • Wykonuj powolne polecenia w usłudze skryptowej powłoki i dołącz do nich później, czekając na określoną właściwość. Android 8.0 obsługuje ten przypadek użycia za pomocą nowego polecenia wait_for_property .
  • Zidentyfikuj powolne operacje w init. System rejestruje polecenie init exec/wait_for_prop lub jakąkolwiek akcję trwającą długo (w Androidzie 8.0 każde polecenie trwające dłużej niż 50 ms). Na przykład:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    Przeglądanie tego dziennika może wskazać możliwości wprowadzenia ulepszeń.

  • Wcześnie uruchamiaj usługi i włączaj urządzenia peryferyjne na ścieżce krytycznej. Na przykład niektóre SOC wymagają uruchomienia usług związanych z bezpieczeństwem przed uruchomieniem SurfaceFlinger. Przejrzyj dziennik systemowy, gdy ServiceManager zwróci komunikat „czekaj na usługę” — zwykle jest to znak, że najpierw należy uruchomić usługę zależną.
  • Usuń wszystkie nieużywane usługi i polecenia z pliku init.*.rc. Wszystko, co nie zostało użyte na wczesnym etapie inicjacji, powinno zostać odłożone do zakończenia rozruchu.

Uwaga: Usługa właściwości jest częścią procesu inicjowania, więc wywołanie setproperty podczas uruchamiania może spowodować duże opóźnienie, jeśli init jest zajęty wbudowanymi poleceniami.

Korzystanie z dostrajania harmonogramu

Użyj dostrajania harmonogramu w celu wczesnego uruchomienia. Przykład z Pixela Google:

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

Niektóre usługi mogą wymagać zwiększenia priorytetu podczas uruchamiania. Przykład:

init.zygote64.rc:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
...

Wczesne rozpoczęcie zygoty

Urządzenia z szyfrowaniem opartym na plikach mogą uruchamiać zygotę wcześniej w momencie wyzwalacza zygoty (domyślnie zygota jest uruchamiana w klasie głównej, czyli znacznie później niż start zygoty). Robiąc to, upewnij się, że zezwoliłeś na działanie zygoty na wszystkich procesorach (ponieważ nieprawidłowe ustawienie zestawu procesorów może wymusić działanie zygoty na określonych procesorach).

Wyłącz oszczędzanie energii

Podczas uruchamiania urządzenia można wyłączyć ustawienia oszczędzania energii dla komponentów takich jak UFS i/lub regulator procesora.

Uwaga: w celu zapewnienia wydajności należy włączyć oszczędzanie energii w trybie ładowarki.

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

Odłóż niekrytyczną inicjalizację

Niekrytyczną inicjalizację, taką jak ZRAM, można odłożyć do boot_complete.

on property:sys.boot_completed=1
   # Enable ZRAM on boot_complete
   swapon_all /vendor/etc/fstab.${ro.hardware}

Optymalizacja animacji rozruchu

Skorzystaj z poniższych wskazówek, aby zoptymalizować animację rozruchu.

Konfigurowanie wczesnego startu

Android 8.0 umożliwia wczesne rozpoczęcie animacji rozruchu, przed zamontowaniem partycji danych użytkownika. Jednak nawet w przypadku korzystania z nowego łańcucha narzędzi ext4 w systemie Android 8.0 fsck jest nadal uruchamiany okresowo ze względów bezpieczeństwa, powodując opóźnienie w uruchomieniu usługi animacji rozruchowej.

Aby animacja rozruchu rozpoczęła się wcześniej, podziel montaż fstab na dwie fazy:

  • We wczesnej fazie zamontuj tylko partycje (takie jak system/ i vendor/ ), które nie wymagają sprawdzania uruchomienia, a następnie uruchom usługi animacji rozruchu i ich zależności (takie jak menedżer usług i Surfaceflinger).
  • W drugiej fazie zamontuj partycje (takie jak data/ ), które wymagają sprawdzenia działania.

Animacja rozruchu zostanie uruchomiona znacznie szybciej (i w stałym czasie) niezależnie od fsck.

Wykończenie czyste

Po otrzymaniu sygnału wyjścia ostatnią część odgrywa animacja rozruchowa, której długość może spowolnić czas uruchamiania. System, który uruchamia się szybko, nie potrzebuje długich animacji, które skutecznie ukrywałyby wprowadzone ulepszenia. Zalecamy, aby zarówno powtarzająca się pętla, jak i finał były krótkie.

Optymalizacja SELinuksa

Skorzystaj z poniższych wskazówek, aby zoptymalizować SELinux w celu skrócenia czasu uruchamiania.

  • Używaj czystych wyrażeń regularnych (regex) . Źle sformułowane wyrażenie regularne może prowadzić do dużego obciążenia podczas dopasowywania polityki SELinux dla sys/devices w file_contexts . Na przykład wyrażenie regularne /sys/devices/.*abc.*(/.*)? błędnie wymusza skanowanie wszystkich podkatalogów /sys/devices zawierających „abc”, umożliwiając dopasowanie zarówno /sys/devices/abc , jak i /sys/devices/xyz/abc . Poprawiasz to wyrażenie regularne do /sys/devices/[^/]*abc[^/]*(/.*)? umożliwi dopasowanie tylko dla /sys/devices/abc .
  • Przenieś etykiety do genfscon . Ta istniejąca funkcja SELinux przekazuje przedrostki dopasowujące pliki do jądra w pliku binarnym SELinux, gdzie jądro stosuje je do systemów plików generowanych przez jądro. Pomaga to również naprawić błędnie oznaczone pliki utworzone przez jądro, zapobiegając warunkom wyścigu, które mogą wystąpić między procesami przestrzeni użytkownika próbującymi uzyskać dostęp do tych plików przed zmianą etykietowania.

Narzędzie i metody

Skorzystaj z poniższych narzędzi, które pomogą Ci zebrać dane na potrzeby celów optymalizacji.

Schemat startowy

Bootchart zapewnia rozkład obciążenia procesora i wejść/wyjść wszystkich procesów w całym systemie. Nie wymaga przebudowy obrazu systemu i może być użyty do szybkiego sprawdzenia poprawności przed zanurzeniem się w systrace.

Aby włączyć bootchart:

adb shell 'touch /data/bootchart/enabled'
adb reboot

Po uruchomieniu pobierz schemat rozruchowy:

$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh

Po zakończeniu usuń /data/bootchart/enabled , aby zapobiec gromadzeniu danych za każdym razem.

Jeśli bootchart nie działa i pojawia się komunikat o błędzie informujący, że bootchart.png nie istnieje, wykonaj następujące czynności:
  1. Uruchom następujące polecenia:
          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. Zaktualizuj $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh , aby wskazywał lokalną kopię pybootchartgui (znajdującą się pod adresem ~/Documents/bootchart/pybootchartgui.py )

Systrace

Systrace umożliwia zbieranie śladów jądra i systemu Android podczas uruchamiania systemu. Wizualizacja systrace może pomóc w analizie konkretnego problemu podczas uruchamiania. (Jednak aby sprawdzić średnią lub skumulowaną liczbę podczas całego rozruchu, łatwiej jest bezpośrednio zajrzeć do śledzenia jądra).

Aby włączyć systrace podczas uruchamiania:

  • W frameworks/native/cmds/atrace/atrace.rc zmień:
      write /sys/kernel/debug/tracing/tracing_on 0
      write /sys/kernel/tracing/tracing_on 0

    Do:

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/tracing/tracing_on 0
  • Umożliwia to śledzenie (które jest domyślnie wyłączone).

  • W pliku device.mk dodaj następujący wiersz:
    PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
    PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • W pliku BoardConfig.mk urządzenia dodaj następujące polecenie:
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • Aby uzyskać szczegółową analizę wejść/wyjść, dodaj także blok oraz ext4 i f2fs.

  • W pliku init.rc specyficznym dla urządzenia dodaj następujące polecenie:
    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
    
  • Po uruchomieniu pobierz ślad:

    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