Starsze aktualizacje systemu A/B, zwane też bezproblemowymi aktualizacjami, zapewniają, że podczas aktualizacji bezprzewodowej (OTA) na dysku pozostaje działający system uruchamiania. Takie podejście zmniejsza prawdopodobieństwo, że po aktualizacji urządzenie będzie nieaktywne, co oznacza mniej wymian i ponownych instalacji oprogramowania w centrach napraw i serwisach gwarancyjnych. Inne systemy operacyjne klasy komercyjnej, takie jak ChromeOS, również z powodzeniem korzystają z aktualizacji A/B.
Więcej informacji o aktualizacjach systemu A/B i ich działaniu znajdziesz w sekcji Wybór partycji (slotów).
Aktualizacje systemu A/B zapewniają te korzyści:
- Aktualizacje OTA mogą być przeprowadzane podczas działania systemu, bez przerywania pracy użytkownika. Użytkownicy mogą nadal korzystać z urządzeń podczas aktualizacji OTA. Jedyny czas przestoju podczas aktualizacji to moment, w którym urządzenie ponownie uruchamia się na zaktualizowanej partycji dysku.
- Po aktualizacji ponowne uruchomienie trwa nie dłużej niż zwykłe ponowne uruchomienie.
- Jeśli aktualizacja OTA nie zostanie zastosowana (np. z powodu nieprawidłowego flashowania), użytkownik nie odczuje tego. Użytkownik będzie nadal korzystać ze starego systemu operacyjnego, a klient może ponownie spróbować przeprowadzić aktualizację.
- Jeśli aktualizacja OTA zostanie zastosowana, ale nie uda się uruchomić urządzenia, zostanie ono ponownie uruchomione w starej partycji i będzie można z niego korzystać. Klient może ponownie spróbować zaktualizować dane.
- Wszelkie błędy (np. błędy wejścia/wyjścia) wpływają tylko na nieużywany zestaw partycji i można je ponowić. Prawdopodobieństwo wystąpienia takich błędów jest też mniejsze, ponieważ obciążenie wejścia/wyjścia jest celowo niskie, aby nie pogarszać wrażeń użytkowników.
-
Aktualizacje mogą być przesyłane strumieniowo na urządzenia A/B, co eliminuje konieczność pobierania pakietu przed instalacją. Streaming oznacza, że użytkownik nie musi mieć wystarczającej ilości wolnego miejsca, aby zapisać pakiet aktualizacji na urządzeniu
/data
lub/cache
. - Partycja pamięci podręcznej nie jest już używana do przechowywania pakietów aktualizacji OTA, więc nie trzeba dbać o to, aby była wystarczająco duża na przyszłe aktualizacje.
- dm-verity gwarantuje, że urządzenie uruchomi nieuszkodzony obraz. Jeśli urządzenie nie uruchamia się z powodu nieprawidłowej aktualizacji OTA lub problemu z dm-verity, może zostać ponownie uruchomione z użyciem starego obrazu. (Android weryfikacja podczas uruchamiania nie wymaga aktualizacji A/B).
Informacje o aktualizacjach systemu A/B
Aktualizacje A/B wymagają zmian zarówno w kliencie, jak i w systemie. Serwer pakietów OTA nie powinien jednak wymagać zmian: pakiety aktualizacji są nadal udostępniane przez HTTPS. W przypadku urządzeń korzystających z infrastruktury OTA Google zmiany w systemie są wprowadzane w AOSP, a kod klienta jest dostarczany przez Usługi Google Play. Producenci OEM, którzy nie korzystają z infrastruktury OTA Google, będą mogli ponownie użyć kodu systemowego AOSP, ale będą musieli dostarczyć własnego klienta.
W przypadku producentów OEM dostarczających własnego klienta musi on:
- Zdecyduj, kiedy chcesz przeprowadzić aktualizację. Aktualizacje A/B są przeprowadzane w tle, więc nie są już inicjowane przez użytkownika. Aby uniknąć zakłóceń w pracy użytkowników, zalecamy planowanie aktualizacji na czas, gdy urządzenie jest w trybie konserwacji w stanie bezczynności, np. w nocy, i gdy jest połączone z Wi-Fi. Klient może jednak używać dowolnych heurystyk.
- Sprawdź serwery pakietów OTA i ustal, czy jest dostępna aktualizacja. Powinien być w większości taki sam jak dotychczasowy kod klienta, z tym że musisz zasygnalizować, że urządzenie obsługuje testy A/B. (Klient Google zawiera też przycisk Sprawdź teraz, który umożliwia użytkownikom sprawdzenie dostępności najnowszej aktualizacji).
-
Wywołaj
update_engine
z adresem URL HTTPS pakietu aktualizacji, jeśli jest on dostępny.update_engine
zaktualizuje surowe bloki na obecnie nieużywanej partycji podczas przesyłania strumieniowego pakietu aktualizacji. -
Zgłaszaj do swoich serwerów informacje o udanych i nieudanych instalacjach na podstawie
update_engine
kodu wyniku. Jeśli aktualizacja zostanie zastosowana,update_engine
poinformuje program rozruchowy, aby przy następnym ponownym uruchomieniu wczytał nowy system operacyjny. Jeśli nowy system operacyjny nie uruchomi się, program rozruchowy powróci do starego systemu operacyjnego, więc klient nie musi nic robić. Jeśli aktualizacja się nie powiedzie, klient musi na podstawie szczegółowego kodu błędu zdecydować, kiedy (i czy) spróbować ponownie. Na przykład dobry klient może rozpoznać, że częściowy pakiet OTA („diff”) nie działa, i zamiast niego spróbować użyć pełnego pakietu OTA.
Opcjonalnie klient może:
- Wyświetl powiadomienie z prośbą o ponowne uruchomienie. Jeśli chcesz wdrożyć zasadę, zgodnie z którą użytkownik jest zachęcany do regularnego aktualizowania, możesz dodać to powiadomienie do klienta. Jeśli klient nie wyświetli użytkownikom prośby, otrzymają oni aktualizację przy następnym ponownym uruchomieniu. (Klient Google ma konfigurowalne opóźnienie dla każdej aktualizacji).
- Wyświetl powiadomienie informujące użytkowników, czy uruchomili nową wersję systemu operacyjnego, czy też powinni to zrobić, ale wrócili do starej wersji. (Klient Google zwykle nie robi ani jednego, ani drugiego).
Po stronie systemu aktualizacje systemu A/B wpływają na te elementy:
-
wybór partycji (slotów), demon
update_engine
i interakcje z programem rozruchowym (opisane poniżej); - Proces kompilacji i generowanie pakietu aktualizacji OTA (opisane w Implementowaniu aktualizacji A/B)
Wybór partycji (przedziały czasu)
Aktualizacje systemu A/B korzystają z 2 zestawów partycji, które są nazywane slotami (zwykle slot A i slot B). System działa w bieżącym slocie, a partycje w nieużywanym slocie nie są dostępne dla działającego systemu podczas normalnej pracy. Dzięki temu aktualizacje są odporne na błędy, ponieważ nieużywane gniazdo jest traktowane jako gniazdo zapasowe: jeśli podczas aktualizacji lub bezpośrednio po niej wystąpi błąd, system może przywrócić stare gniazdo i dalej działać. Aby to osiągnąć, żadna partycja używana przez bieżące gniazdo nie powinna być aktualizowana w ramach aktualizacji OTA (w tym partycje, dla których istnieje tylko jedna kopia).
Każde gniazdo ma atrybut bootable, który określa, czy zawiera ono prawidłowy system, z którego urządzenie może się uruchomić. Bieżący przedział jest rozruchowy, gdy system jest uruchomiony, ale drugi przedział może zawierać starszą (ale nadal prawidłową) wersję systemu, nowszą wersję lub nieprawidłowe dane. Niezależnie od tego, który przedział jest bieżący, istnieje jeden przedział, który jest aktywny (z którego program rozruchowy uruchomi system przy następnym uruchomieniu) lub preferowany.
Każde gniazdo ma też atrybut successful ustawiany przez przestrzeń użytkownika, który jest istotny tylko wtedy, gdy gniazdo jest też rozruchowe. Prawidłowo działające gniazdo powinno być w stanie uruchomić się, działać i zaktualizować się samodzielnie. Gniazdo rozruchowe, które nie zostało oznaczone jako prawidłowe (po kilku próbach uruchomienia z niego), powinno zostać oznaczone przez program rozruchowy jako niemożliwe do uruchomienia. Obejmuje to zmianę aktywnego gniazda na inne gniazdo rozruchowe (zwykle na gniazdo, które było aktywne bezpośrednio przed próbą uruchomienia nowego, aktywnego gniazda). Szczegółowe informacje o interfejsie są zdefiniowane w
boot_control.h
.
Aktualizowanie demona silnika
Aktualizacje systemu A/B korzystają z procesu działającego w tle o nazwie
update_engine
, który przygotowuje system do uruchomienia w nowej, zaktualizowanej wersji. Ten demon może wykonywać te działania:
- Odczytaj dane z bieżących partycji A/B gniazda i zapisz je w nieużywanych partycjach A/B gniazda zgodnie z instrukcjami w pakiecie OTA.
- Wywołaj interfejs
boot_control
w predefiniowanym przepływie pracy. - Uruchom program po instalacji z nowej partycji po zapisaniu wszystkich nieużywanych partycji gniazd zgodnie z instrukcjami w pakiecie OTA. (Więcej informacji znajdziesz w sekcji Po instalacji).
Demon update_engine
nie jest zaangażowany w sam proces uruchamiania, więc jego możliwości podczas aktualizacji są ograniczone przez zasady i funkcje SELinux w bieżącym slocie (takich zasad i funkcji nie można zaktualizować, dopóki system nie uruchomi się w nowej wersji). Aby zachować stabilność systemu, proces aktualizacji nie powinien modyfikować tabeli partycji, zawartości partycji w bieżącym slocie ani zawartości partycji innych niż A/B, których nie można wyczyścić przez przywrócenie ustawień fabrycznych.
Aktualizowanie źródła silnika
Źródło update_engine
znajduje się w lokalizacji system/update_engine
. Pliki dexopt aktualizacji A/B OTA są dzielone między installd
i menedżera pakietów:
-
frameworks/native/cmds/installd/
ota* zawiera skrypt poinstalacyjny, plik binarny dla chroot, klon installd, który wywołuje dex2oat, skrypt przenoszący artefakty po aktualizacji OTA oraz plik rc dla skryptu przenoszącego. -
frameworks/base/services/core/java/com/android/server/pm/OtaDexoptService.java
(plusOtaDexoptShellCommand
) to menedżer pakietów, który przygotowuje polecenia dex2oat dla aplikacji.
Działający przykład znajdziesz w sekcji /device/google/marlin/device-common.mk
.
Logi mechanizmu aktualizacji
W przypadku Androida 8.x i starszych wersji dzienniki update_engine
można znaleźć w logcat
oraz w raporcie o błędzie. Aby udostępnić logi update_engine
w systemie plików, wprowadź te zmiany w kompilacji:
Te zmiany zapisują kopię najnowszego dziennika update_engine
w folderze /data/misc/update_engine_log/update_engine.YEAR-TIME
. Oprócz bieżącego dziennika 5 najnowszych dzienników jest zapisywanych w sekcji/data/misc/update_engine_log/
. Użytkownicy z identyfikatorem grupy log będą mieć dostęp do dzienników systemu plików.
Interakcje z programem rozruchowym
Interfejs HAL boot_control
jest używany przez update_engine
(i prawdopodobnie inne demony) do instruowania programu rozruchowego, z czego ma się uruchomić. Typowe przykłady scenariuszy i powiązanych z nimi stanów:
- Normalny przypadek: system działa w bieżącym slocie, czyli A lub B. Nie zastosowano jeszcze żadnych aktualizacji. Obecny slot systemu jest rozruchowy, prawidłowy i aktywny.
- Trwa aktualizacja: system działa w slocie B, więc jest to slot rozruchowy, aktywny i działający prawidłowo. Gniazdo A zostało oznaczone jako niemożliwe do uruchomienia, ponieważ jego zawartość jest aktualizowana, ale proces ten nie został jeszcze zakończony. Ponowne uruchomienie w tym stanie powinno spowodować kontynuowanie rozruchu z gniazda B.
- Zastosowano aktualizację, oczekuje na ponowne uruchomienie: system działa w slocie B, slot B jest rozruchowy i działa prawidłowo, ale slot A został oznaczony jako aktywny (i dlatego jest oznaczony jako rozruchowy). Gniazdo A nie jest jeszcze oznaczone jako udane i program rozruchowy powinien podjąć pewną liczbę prób uruchomienia z gniazda A.
-
System uruchomiony ponownie w nowej aktualizacji: system działa w slocie A po raz pierwszy, slot B nadal można uruchomić i działa on prawidłowo, a slot A można tylko uruchomić i jest on nadal aktywny, ale nie działa prawidłowo. Demon przestrzeni użytkownika,
update_verifier
, powinien oznaczyć slot A jako zakończony sukcesem po przeprowadzeniu kilku kontroli.
Obsługa aktualizacji strumieniowych
Urządzenia użytkowników nie zawsze mają wystarczająco dużo miejsca na /data
, aby pobrać pakiet aktualizacji. Ani producenci OEM, ani użytkownicy nie chcą marnować miejsca na partycji /cache
, dlatego niektórzy użytkownicy nie otrzymują aktualizacji, ponieważ na urządzeniu nie ma miejsca na pakiet aktualizacji. Aby rozwiązać ten problem, w Androidzie 8.0 dodano obsługę przesyłania strumieniowego aktualizacji A/B, które zapisują bloki bezpośrednio w partycji B w miarę ich pobierania, bez konieczności przechowywania ich w /data
. Aktualizacje A/B przesyłane strumieniowo nie wymagają prawie żadnej pamięci tymczasowej, a jedynie miejsca na około 100 KiB metadanych.
Aby włączyć przesyłanie strumieniowe aktualizacji w Androidzie 7.1, wybierz te poprawki:
- Zezwalaj na anulowanie prośby o rozwiązanie serwera proxy
- Naprawianie przerywania przesyłania podczas rozwiązywania problemów z serwerami proxy
- Dodaj test jednostkowy dla funkcji TerminateTransfer między zakresami
- Wyczyść funkcję RetryTimeoutCallback()
Te poprawki są wymagane do obsługi strumieniowych aktualizacji A/B w Androidzie 7.1 i nowszym, niezależnie od tego, czy używasz Usług mobilnych Google (GMS), czy innego klienta aktualizacji.
Cykl życia aktualizacji testu A/B
Proces aktualizacji rozpoczyna się, gdy pakiet OTA (w kodzie określany jako payload) jest dostępny do pobrania. Zasady na urządzeniu mogą opóźniać pobieranie i stosowanie pakietu danych w zależności od poziomu baterii, aktywności użytkownika, stanu ładowania lub innych zasad. Dodatkowo, ponieważ aktualizacja jest przeprowadzana w tle, użytkownicy mogą nie wiedzieć, że trwa aktualizacja. Oznacza to, że proces aktualizacji może zostać przerwany w dowolnym momencie z powodu zasad, nieoczekiwanych ponownych uruchomień lub działań użytkownika.
Opcjonalnie metadane w samym pakiecie OTA wskazują, że aktualizację można przesyłać strumieniowo. Tego samego pakietu można też używać do instalacji bez przesyłania strumieniowego. Serwer może używać metadanych, aby poinformować klienta, że przesyła strumieniowo, dzięki czemu klient prawidłowo przekaże aktualizację OTA do update_engine
. Producenci urządzeń, którzy mają własny serwer i klienta, mogą włączyć aktualizacje strumieniowe, sprawdzając, czy serwer rozpoznaje, że aktualizacja jest strumieniowa (lub zakłada, że wszystkie aktualizacje są strumieniowe), a klient wykonuje prawidłowe wywołanie funkcji update_engine
w celu strumieniowania. Producenci mogą wykorzystać fakt, że pakiet jest wariantem przesyłanym strumieniowo, aby wysłać do klienta flagę, która spowoduje przekazanie do strony platformy jako przesyłanie strumieniowe.
Gdy pakiet danych będzie dostępny, proces aktualizacji przebiega w ten sposób:
Krok | Działania |
---|---|
1 |
Bieżący slot (lub „slot źródłowy”) jest oznaczany jako przetworzony (jeśli nie został jeszcze oznaczony) symbolem markBootSuccessful() .
|
2 |
Nieużywane gniazdo (lub „gniazdo docelowe”) jest oznaczane jako nieuruchamialne przez wywołanie funkcji
setSlotAsUnbootable() . Bieżące gniazdo jest zawsze oznaczane jako prawidłowe na początku aktualizacji, aby zapobiec powrotowi programu rozruchowego do nieużywanego gniazda, które wkrótce będzie zawierać nieprawidłowe dane. Jeśli system osiągnął punkt, w którym może rozpocząć stosowanie aktualizacji, bieżący slot jest oznaczany jako udany, nawet jeśli inne główne komponenty są uszkodzone (np. interfejs użytkownika w pętli awarii), ponieważ można przesłać nowe oprogramowanie, aby rozwiązać te problemy. Ładunek aktualizacji to nieprzezroczysta struktura danych zawierająca instrukcje aktualizacji do nowej wersji. Ładunek aktualizacji składa się z tych elementów:
|
3 | Metadane ładunku zostaną pobrane. |
4 | W przypadku każdej operacji zdefiniowanej w metadanych w odpowiedniej kolejności powiązane dane (jeśli takie istnieją) są pobierane do pamięci, operacja jest stosowana, a powiązana pamięć jest odrzucana. |
5 | Całe partycje są odczytywane ponownie i weryfikowane pod kątem zgodności z oczekiwanym skrótem. |
6 | Wykonany zostanie etap po instalacji (jeśli występuje). W przypadku błędu podczas wykonywania dowolnego kroku aktualizacja nie powiedzie się i zostanie ponowiona, być może z innym ładunkiem. Jeśli wszystkie dotychczasowe kroki zakończą się powodzeniem, aktualizacja zostanie przeprowadzona, a ostatni krok zostanie wykonany. |
7 |
Nieużywane miejsce jest oznaczane jako aktywne przez wywołanie funkcji setActiveBootSlot() .
Oznaczenie nieużywanego gniazda jako aktywnego nie oznacza, że proces uruchamiania zostanie zakończony. Program rozruchowy (lub sam system) może przywrócić aktywny slot, jeśli nie odczyta stanu powodzenia.
|
8 |
Po instalacji (opisanej poniżej) należy uruchomić program z „nowej aktualizacji”,
ale nadal korzystać ze starej wersji. Jeśli ten krok jest zdefiniowany w pakiecie OTA, jest obowiązkowy, a program musi zwrócić kod zakończenia 0 . W przeciwnym razie aktualizacja się nie powiedzie.
|
9 |
Gdy system uruchomi się w nowym slocie i zakończy sprawdzanie po ponownym uruchomieniu, bieżący slot (wcześniej „slot docelowy”) zostanie oznaczony jako udany przez wywołanie funkcji markBootSuccessful() .
|
Po instalacji
W przypadku każdej partycji, w której zdefiniowano krok po instalacji, update_engine
montuje nową partycję w określonej lokalizacji i wykonuje program określony w aktualizacji OTA względem zamontowanej partycji. Jeśli na przykład program po instalacji jest zdefiniowany jako usr/bin/postinstall
w partycji systemowej, ta partycja z nieużywanego gniazda zostanie zamontowana w stałej lokalizacji (np. /postinstall_mount
) i zostanie wykonane polecenie /postinstall_mount/usr/bin/postinstall
.
Aby proces po instalacji przebiegł pomyślnie, stary kernel musi być w stanie:
- Zamontuj nowy format systemu plików. Typ systemu plików nie może się zmienić, chyba że jest obsługiwany przez stary kernel, w tym szczegóły takie jak algorytm kompresji używany w przypadku skompresowanego systemu plików (np. SquashFS).
-
Poznaj format programu po instalacji w przypadku nowej partycji. Jeśli używasz pliku binarnego w formacie ELF (Executable and Linkable Format), powinien on być zgodny ze starym jądrem (np. 64-bitowy nowy program działający na starym 32-bitowym jądrze, jeśli architektura została zmieniona z 32-bitowej na 64-bitową). Jeśli program ładujący (
ld
) nie otrzyma polecenia użycia innych ścieżek lub utworzenia statycznego pliku binarnego, biblioteki będą ładowane ze starego obrazu systemu, a nie z nowego.
Możesz na przykład użyć skryptu powłoki jako programu po instalacji interpretowanego przez stary kod binarny powłoki systemu (z znacznikiem #!
na górze), a następnie skonfigurować ścieżki bibliotek z nowego środowiska, aby wykonać bardziej złożony binarny program po instalacji. Możesz też uruchomić krok po instalacji z dedykowanej mniejszej partycji, aby umożliwić aktualizację formatu systemu plików na głównej partycji systemowej bez powodowania problemów ze zgodnością wsteczną lub aktualizacji pośrednich. Dzięki temu użytkownicy będą mogli aktualizować system bezpośrednio do najnowszej wersji z obrazu fabrycznego.
Nowy program po instalacji jest ograniczony przez zasady SELinux zdefiniowane w starym systemie. Dlatego krok po instalacji nadaje się do wykonywania zadań wymaganych przez projekt na danym urządzeniu lub innych zadań, które mają być wykonane z możliwie największą dokładnością. Krok po instalacji nie nadaje się do jednorazowych poprawek błędów przed ponownym uruchomieniem, które wymagają nieprzewidzianych uprawnień.
Wybrany program po instalacji jest uruchamiany w postinstall
kontekście SELinux. Wszystkie pliki w nowej zamontowanej partycji zostaną oznaczone tagiem postinstall_file
, niezależnie od ich atrybutów po ponownym uruchomieniu w tym nowym systemie. Zmiany atrybutów SELinux w nowym systemie nie będą miały wpływu na krok po instalacji. Jeśli program po instalacji wymaga dodatkowych uprawnień, należy je dodać do kontekstu po instalacji.
Po ponownym uruchomieniu
Po ponownym uruchomieniu update_verifier
wywołuje sprawdzanie integralności za pomocą dm-verity.
Sprawdzanie rozpoczyna się przed procesem zygote, aby uniknąć nieodwracalnych zmian w usługach Java, które uniemożliwiłyby bezpieczne wycofanie zmian. Podczas tego procesu program rozruchowy i jądro mogą również uruchomić ponownie urządzenie, jeśli weryfikacja podczas uruchamiania lub dm-verity wykryją jakiekolwiek uszkodzenie. Po zakończeniu sprawdzania
update_verifier
oznacza, że rozruch zakończył się pomyślnie.
update_verifier
odczyta tylko bloki wymienione w pliku /data/ota_package/care_map.txt
, który jest zawarty w pakiecie OTA A/B podczas korzystania z kodu AOSP. Klient aktualizacji systemu Java, np. GmsCore, wyodrębnia care_map.txt
, konfiguruje uprawnienia dostępu przed ponownym uruchomieniem urządzenia i usuwa wyodrębniony plik po pomyślnym uruchomieniu systemu w nowej wersji.