Optymalizacja organizacji zajmujących się handlem narkotykami

Na tej stronie omawiamy optymalizacje, które możesz wprowadzić w ramach implementacji nakładki drzewa urządzenia (DTO). Opisujemy też ograniczenia dotyczące nakładania na węzeł względny i podajemy szczegółowe informacje o konfigurowaniu skompresowanych nakładek w obrazie DTBO. Znajdziesz w nim też instrukcje implementacji i przykładowy kod.

Wiersz poleceń jądra systemu

Oryginalny wiersz poleceń jądra w drzewie urządzenia (DT) znajduje się w węźle chosen/bootargs. Program rozruchowy musi złączać tę lokalizację z innymi źródłami linii poleceń jądra:

/dts-v1/;

/ {
  chosen: chosen {
    bootargs = "...";
  };
};

DTO nie może konkatenować wartości z głównego DT i nakładkowego DT, więc musisz umieścić wiersz poleceń jądra głównego DT w chosen/bootargs, a wiersz poleceń jądra nakładkowego DT w chosen/bootargs_ext. Bootloader może następnie złączyć te lokalizacje i przekazać wynik do jądra.

main.dts. nakładek.dts
/dts-v1/;

/ {
  chosen: chosen {
    bootargs = "...";
  };
};
/dts-v1/;
/plugin/;

&chosen {
  bootargs_ext = "...";
};

libufdt

Choć najnowsze libfdt obsługuje nakładki drzewa urządzeń, do ich wdrożenia zalecamy użycie zasady libufdt (Źródło AOSP: platform/system/libufdt). libufdt tworzy prawdziwą strukturę drzewa (niespłaszczone drzewo urządzeń, lub ufdt) z płaskiego drzewa urządzeń (FDT), aby poprawić połączenie dwóch plików .dtb z zakresu O(N2) do O(N), gdzie N określa liczbę węzłów w drzewie.

Testy wydajności

W ramach testów wewnętrznych Google użycie funkcji libufdt na 2405 .dtb i 283 .dtbo węzłach DT skutkowało rozmiarami plików wynoszącymi 70 618 i 8 566 bajtów po skompilowaniu. W porównaniu z implementacją DTO przeniesioną z FreeBSD (czas wykonywania 124 ms), czas wykonywania libufdtDTO wynosi 10 ms.

Testy wydajności urządzeń Pixel w porównaniu z libufdtlibfdt. Efekt liczby węzłów podstawowych jest podobny, ale występują te różnice:

  • 500 operacji nakładania (dodawania lub zastępowania) zajmuje 6–8 razy więcej czasu
  • 1000 operacji nakładania (dodawania lub zastępowania) zajmuje 8–10 razy więcej czasu.

Przykład z liczbą dodawania ustawioną na X:

Rysunek 1. Liczba dołączania wynosi X.

Przykład z liczbą zastąpienia ustawioną na X:

Rysunek 2. Liczba zastąpień wynosi X.

libufdt jest opracowywany z wykorzystaniem niektórych interfejsów API libfdt i struktur danych. Jeśli używasz interfejsu libufdt, musisz go uwzględnić i do niego odwoływać (w kodzie możesz jednak używać interfejsu libfdt do obsługi DTB lub DTBO).

Interfejs libufdt DTO API

Główny interfejs API używany przez DTO w regionie libufdt wygląda tak:

struct fdt_header *ufdt_apply_overlay(
        struct fdt_header *main_fdt_header,
        size_t main_fdt_size,
        void *overlay_fdt,
        size_t overlay_size);

Parametr main_fdt_header to główny DT, a overlay_fdt to bufor zawierający zawartość pliku .dtbo. Zwracany jest nowy bufor zawierający scalone dane DT (lub null w przypadku błędu). Połączony DT jest sformatowany w formacie FDT, który można przekazać do jądra podczas uruchamiania jądra.

Nowy bufor z wartością zwracaną jest tworzony przez funkcję dto_malloc(), którą należy zaimplementować podczas przenoszenia funkcji libufdt do bootloadera. Implementacje referencyjne znajdziesz tutaj: sysdeps/libufdt_sysdeps_*.c.

Ograniczenia węzła głównego

Nie możesz nałożyć nowego węzła ani właściwości na węzeł główny głównego DT, ponieważ operacje nakładania opierają się na etykietach. Ponieważ główny plik przenoszenia danych musi definiować etykietę, a nakładka przenoszenia danych przypisuje węzły do nakładania etykiet, nie możesz nadać etykiety węzłowi głównemu (więc nie możesz nałożyć go na węzeł główny).

Dostawcy układów SoC muszą zdefiniować możliwości nakładania głównego DT; ODM/OEM mogą tylko dodawać lub zastępować węzły etykietami zdefiniowanymi przez dostawcę układu SoC. Aby obejść ten problem, możesz zdefiniować węzeł odm w węźle głównym w podstawowym interfejsie przenoszenia danych, dzięki czemu wszystkie węzły ODM w nakładki DT mogą dodawać nowe węzły. Możesz też umieścić wszystkie węzły związane z SoC w podstawowym DT w węźle soc pod węzłem głównym, jak pokazano poniżej:

main.dts overlay.dts
/dts-v1/;

/ {
    compatible = "corp,bar";
    ...

    chosen: chosen {
        bootargs = "...";
    };

    /* nodes for all soc nodes */
    soc {
        ...
        soc_device@0: soc_device@0 {
            compatible = "corp,bar";
            ...
        };
        ...
    };

    odm: odm {
        /* reserved for overlay by odm */
    };
};
/dts-v1/;
/plugin/;

/ {
};

&chosen {
    bootargs_ex = "...";
};

&odm {
    odm_device@0 {
        ...
    };
    ...
};

Używanie skompresowanych nakładek

W Androidzie 9 można korzystać z skompresowanych nakładek w obrazie DTBO, gdy jest używana wersja 1 nagłówka tabeli DT. W przypadku nagłówka DTBO w wersji 1 4 najmniej znaczące bity pola flagi w dt_table_entry wskazują format kompresji wpisu DT.

struct dt_table_entry_v1 {
  uint32_t dt_size;
  uint32_t dt_offset;  /* offset from head of dt_table_header */
  uint32_t id;         /* optional, must be zero if unused */
  uint32_t rev;        /* optional, must be zero if unused */
  uint32_t flags;      /* For version 1 of dt_table_header, the 4 least significant bits
                        of 'flags' are used to indicate the compression
                        format of the DT entry as per the enum 'dt_compression_info' */
  uint32_t custom[3];  /* optional, must be zero if unused */
};

Obecnie obsługiwane są kompresje zlib i gzip.

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

Android 9 umożliwia testowanie skompresowanych nakładek w teście VtsFirmwareDtboVerification, aby ułatwić weryfikację prawidłowego działania aplikacji nakładki.

Przykładowa implementacja nakładek drzewa urządzeń

Poniżej znajdziesz instrukcje przykładowej implementacji nakładek drzewa urządzeń za pomocą libufdt (przykładowy kod poniżej).

Przykładowe instrukcje dotyczące nakładek drzewa urządzeń

  1. Uwzględnij biblioteki. Aby używać libufdt, dodaj libfdt dla struktur danych i interfejsów API:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
    
  2. Wczytaj główny plik przenoszenia danych i nałożony plik przenoszenia danych. Wczytaj do pamięci .dtb i .dtbo z pamięci (dokładne kroki zależą od projektu). W tym momencie powinieneś mieć bufor i rozmiar .dtb/.dtbo:
    main_size = my_load_main_dtb(main_buf, main_buf_size)
    
    overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);
    
  3. Nakładanie DT:
    1. Aby pobrać nagłówek FDT dla głównego drzewa urządzeń, użyj narzędzia ufdt_install_blob():
      main_fdt_header = ufdt_install_blob(main_buf, main_size);
      main_fdt_size = main_size;
      
    2. Aby pobrać scalone drzewo urządzeń w formacie FDT, wywołaj funkcję ufdt_apply_overlay() w DTO:
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
      
    3. Aby uzyskać rozmiar elementu dtc_totalsize(), użyj funkcji merged_fdt:
      merged_fdt_size = dtc_totalsize(merged_fdt);
      
    4. Przekaż scalone drzewo urządzeń, aby uruchomić jądro:
      my_kernel_entry(0, machine_type, merged_fdt);
      

Przykładowy kod DTO

#include <libfdt.h>
#include <ufdt_overlay.h>

…

{
  struct fdt_header *main_fdt_header;
  struct fdt_header *merged_fdt;

  /* load main dtb into memory and get the size */
  main_size = my_load_main_dtb(main_buf, main_buf_size);

  /* load overlay dtb into memory and get the size */
  overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);

  /* overlay */
  main_fdt_header = ufdt_install_blob(main_buf, main_size);
  main_fdt_size = main_size;
  merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                  overlay_buf, overlay_size);
  merged_fdt_size = dtc_totalsize(merged_fdt);

  /* pass to kernel */
  my_kernel_entry(0, machine_type, merged_fdt);
}