Przekazywanie FUSE

Android 12 obsługuje przekazywanie FUSE, co minimalizuje narzut FUSE i pozwala osiągnąć wydajność porównywalną z bezpośrednim dostępem do niższego systemu plików. Przekazywanie FUSE jest obsługiwane w jądrach android12-5.4, android12-5.10android-mainline (tylko testy), co oznacza, że obsługa tej funkcji zależy od jądra używanego przez urządzenie i wersji Androida, na której działa urządzenie:

  • Urządzenia, które przechodzą z Androida 11 na Androida 12, nie mogą obsługiwać przekazywania FUSE, ponieważ ich jądra są zamrożone i nie można ich przenieść do jądra, które zostało oficjalnie zaktualizowane o zmiany związane z przekazywaniem FUSE.

  • Urządzenia z Androidem 12 mogą obsługiwać przekazywanie FUSE, gdy używają oficjalnego jądra. W przypadku takich urządzeń kod platformy Android, który implementuje przekazywanie FUSE, jest osadzony w głównym module MediaProvider, który jest automatycznie aktualizowany. Urządzenia, które nie implementują MediaProvider jako modułu głównego (np. urządzenia z Androidem Go), również mogą uzyskać dostęp do zmian w MediaProvider, gdy zostaną one publicznie udostępnione.

FUSE a SDCardFS

System plików w przestrzeni użytkownika (FUSE) to mechanizm, który umożliwia przekazywanie operacji wykonywanych w systemie plików FUSE przez jądro (sterownik FUSE) do programu w przestrzeni użytkownika (demon FUSE), który implementuje te operacje. Android 11 wycofał SDCardFS i ustawił FUSE jako domyślne rozwiązanie do emulacji pamięci. W ramach tej zmiany Android wdrożył własny demon FUSE, który przechwytuje dostęp do plików, wymusza dodatkowe funkcje zabezpieczeń i ochrony prywatności oraz manipuluje plikami w czasie działania.

FUSE dobrze radzi sobie z informacjami, które można przechowywać w pamięci podręcznej, takimi jak strony czy atrybuty, ale powoduje regresję wydajności podczas uzyskiwania dostępu do zewnętrznej pamięci masowej, co jest szczególnie widoczne na urządzeniach ze średniej i niskiej półki. Te regresje są spowodowane przez łańcuch komponentów współpracujących przy implementacji systemu plików FUSE, a także przez wielokrotne przełączanie się z przestrzeni jądra do przestrzeni użytkownika w komunikacji między sterownikiem FUSE a demonem FUSE (w porównaniu z bezpośrednim dostępem do niższego systemu plików, który jest bardziej uproszczony i w całości zaimplementowany w jądrze).

Aby złagodzić te regresje, aplikacje mogą używać łączenia, aby zmniejszyć kopiowanie danych, oraz interfejsu ContentProvider API, aby uzyskać bezpośredni dostęp do plików systemu plików niższego poziomu. Nawet przy tych i innych optymalizacjach operacje odczytu i zapisu mogą mieć mniejszą przepustowość podczas korzystania z FUSE w porównaniu z bezpośrednim dostępem do niższego systemu plików – zwłaszcza w przypadku operacji odczytu losowego, w których nie pomaga buforowanie ani odczyt z wyprzedzeniem. Aplikacje, które uzyskują bezpośredni dostęp do pamięci przez starszą ścieżkę /sdcard/, nadal odczuwają zauważalny spadek wydajności, zwłaszcza podczas wykonywania operacji wymagających dużej liczby operacji wejścia/wyjścia.

Żądania przestrzeni użytkownika SDcardFS

Korzystanie z SDcardFS może przyspieszyć emulację pamięci i sprawdzanie uprawnień FUSE przez usunięcie wywołania przestrzeni użytkownika z jądra. Żądania przestrzeni użytkownika przechodzą ścieżkę: Przestrzeń użytkownika → VFS → sdcardfs → VFS → ext4 → Pamięć podręczna strony/Pamięć masowa.

FUSE Passthrough SDcardFS

Rysunek 1. Żądania przestrzeni użytkownika SDcardFS

Żądania przestrzeni użytkownika FUSE

FUSE był początkowo używany do emulacji pamięci i umożliwiał aplikacjom transparentne korzystanie z pamięci wewnętrznej lub zewnętrznej karty SD. Korzystanie z FUSE powoduje pewne obciążenie, ponieważ każde żądanie przestrzeni użytkownika przechodzi ścieżkę: Przestrzeń użytkownika → VFS → sterownik FUSE → demon FUSE → VFS → ext4 → pamięć podręczna strony/pamięć masowa.

Przekazywanie FUSE

Rysunek 2. Żądania przestrzeni użytkownika FUSE

Żądania przekazywane przez FUSE

Większość uprawnień dostępu do plików jest sprawdzana podczas otwierania pliku, a dodatkowe kontrole uprawnień są przeprowadzane podczas odczytywania i zapisywania danych w tym pliku. W niektórych przypadkach w momencie otwierania pliku można stwierdzić, że aplikacja wysyłająca żądanie ma pełny dostęp do żądanego pliku, więc system nie musi dalej przekazywać żądań odczytu i zapisu z sterownika FUSE do demona FUSE (ponieważ spowodowałoby to tylko przeniesienie danych z jednego miejsca do drugiego).

W przypadku przekazywania FUSE demon FUSE obsługujący otwarte żądanie może powiadomić sterownik FUSE, że operacja jest dozwolona i że wszystkie kolejne żądania odczytu i zapisu mogą być bezpośrednio przekazywane do niższego systemu plików. Pozwala to uniknąć dodatkowego obciążenia związanego z oczekiwaniem na odpowiedź demona FUSE w przestrzeni użytkownika na żądania sterownika FUSE.

Poniżej znajdziesz porównanie żądań FUSE i żądań przekazywanych FUSE.

Porównanie przekazywania FUSE

Rysunek 3. Żądanie FUSE a żądanie przekazywane FUSE

Gdy aplikacja uzyskuje dostęp do systemu plików FUSE, wykonywane są te działania:

  1. Sterownik FUSE obsługuje i kolejkuje żądanie, a następnie przekazuje je do demona FUSE, który obsługuje ten system plików FUSE za pomocą konkretnego wystąpienia połączenia w pliku /dev/fuse, którego demon FUSE nie może odczytać.

  2. Gdy demon FUSE otrzyma prośbę o otwarcie pliku, decyduje, czy dla tego konkretnego pliku ma być dostępny tryb przekazywania FUSE. Jeśli jest dostępny, demon:

    1. Powiadamia sterownik FUSE o tym żądaniu.

    2. Umożliwia przekazywanie FUSE dla pliku za pomocą FUSE_DEV_IOC_PASSTHROUGH_OPEN ioctl, które musi zostać wykonane na deskryptorze pliku otwartego /dev/fuse.

  3. Funkcja ioctl otrzymuje (jako parametr) strukturę danych, która zawiera te elementy:

    • Deskryptor pliku w systemie plików niższego poziomu, który jest celem funkcji przekazywania.

    • Unikalny identyfikator żądania FUSE, które jest obecnie obsługiwane (musi być otwarte lub utworzone i otwarte).

    • Dodatkowe pola, które można pozostawić puste i które są przeznaczone do przyszłych wdrożeń.

  4. Jeśli ioctl się powiedzie, demon FUSE zakończy żądanie otwarcia, sterownik FUSE obsłuży odpowiedź demona FUSE, a do pliku FUSE w jądrze zostanie dodane odwołanie do pliku systemu plików niższego poziomu. Gdy aplikacja zażąda operacji odczytu lub zapisu w pliku FUSE, sterownik FUSE sprawdzi, czy dostępny jest odnośnik do pliku w systemie plików niższego poziomu.

    • Jeśli odwołanie jest dostępne, sterownik tworzy nowe żądanie wirtualnego systemu plików (VFS) z tymi samymi parametrami, które są kierowane do pliku w niższym systemie plików.

    • Jeśli odwołanie nie jest dostępne, sterownik przekazuje żądanie do demona FUSE.

Powyższe operacje występują w przypadku operacji odczytu/zapisu i odczytu-iteracji/zapisu-iteracji na plikach ogólnych oraz operacji odczytu/zapisu na plikach mapowanych w pamięci. Przekazywanie FUSE dla danego pliku jest aktywne do momentu jego zamknięcia.

Implementowanie przekazywania FUSE

Aby włączyć przekazywanie FUSE na urządzeniach z Androidem 12, dodaj te wiersze do pliku $ANDROID_BUILD_TOP/device/…/device.mk na urządzeniu docelowym.

# Use FUSE passthrough
PRODUCT_PRODUCT_PROPERTIES += \
    persist.sys.fuse.passthrough.enable=true

Aby wyłączyć przekazywanie FUSE, pomiń powyższą zmianę konfiguracji lub ustaw wartość persist.sys.fuse.passthrough.enable na false. Jeśli wcześniej włączono przekazywanie FUSE, wyłączenie tej funkcji uniemożliwi urządzeniu korzystanie z niej, ale urządzenie nadal będzie działać.

Aby włączyć lub wyłączyć przekazywanie FUSE bez flashowania urządzenia, zmień właściwość systemu za pomocą poleceń ADB. Przykład znajdziesz poniżej.

adb root
adb shell setprop persist.sys.fuse.passthrough.enable {true,false}
adb reboot

Aby uzyskać dodatkową pomoc, zapoznaj się z implementacją referencyjną.

Weryfikacja przekazywania FUSE

Aby sprawdzić, czy MediaProvider korzysta z przekazywania FUSE, poszukaj komunikatów debugowania w logcat. Na przykład:

adb logcat FuseDaemon:V \*:S
--------- beginning of main
03-02 12:09:57.833  3499  3773 I FuseDaemon: Using FUSE passthrough
03-02 12:09:57.833  3499  3773 I FuseDaemon: Starting fuse...

Wpis FuseDaemon: Using FUSE passthrough w logu potwierdza, że używane jest przekazywanie FUSE.

Pakiet CTS dla Androida 12 zawiera CtsStorageTest, który obejmuje testy wywołujące przekazywanie FUSE. Aby ręcznie uruchomić test, użyj polecenia atest w sposób pokazany poniżej:

atest CtsStorageTest