Przekazywanie FUSE

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

  • Urządzenia przechodzące z Androida 11 na Androida 12 nie obsługują przekazywania FUSE, ponieważ jądra tych urządzeń są zamrożone i nie można ich przenieść do jądra, które zostało oficjalnie uaktualnione po wprowadzeniu zmian w przekazywaniu FUSE.

  • Urządzenia z Androidem 12 mogą obsługiwać przekazywanie FUSE, jeśli używają oficjalnego jądra. W przypadku takich urządzeń kod frameworka Androida, który implementuje przepuszczanie FUSE, jest umieszczony 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), mogą również uzyskiwać dostęp do zmian w MediaProvider, ponieważ są one udostępniane publicznie.

FUSE a SDCardFS

System plików w przestrzeni użytkownika (FUSE) to mechanizm, który umożliwia przejęcie przez jądra systemu plików FUSE (sterownik FUSE) programowi przestrzeni użytkownika (demona FUSE), który je wdraża. W Androidzie 11 wycofano SDCardFS i ustawiono FUSE jako domyślne rozwiązanie do emulacji pamięci masowej. W ramach tej zmiany Android wdrożył własnego demona FUSE, aby przechwytywać dostęp do plików, stosować dodatkowe funkcje zabezpieczeń i ochrony prywatności oraz manipulować plikami w czasie wykonywania.

FUSE dobrze radzi sobie z informacjami, które można przechowywać w pamięci podręcznej, takimi jak strony lub atrybuty, ale powoduje regres wydajności podczas dostępu do pamięci zewnętrznej. Jest to szczególnie widoczne na urządzeniach ze średniej i niższej półki. Te regresje są spowodowane przez łańcuch komponentów współpracujących w ramach 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 prostszy i w pełni zaimplementowany w jądrze).

Aby ograniczyć te regresje, aplikacje mogą używać złączania w celu zmniejszenia kopiowania danych i używania interfejsu ContentProvider API do uzyskiwania bezpośredniego dostępu do plików w niższym systemie plików. Nawet po zastosowaniu tych i innych optymalizacji operacje odczytu i zapisu podczas korzystania z FUSE mogą spowodować zmniejszenie przepustowości w porównaniu z bezpośrednim dostępem do niższego systemu plików   zwłaszcza w przypadku losowych operacji odczytu, w których użycie buforowania lub odczytu z wyprzedzeniem nie może pomóc. Aplikacje, które bezpośrednio uzyskują dostęp do pamięci masowej za pomocą starszego interfejsu /sdcard/, nadal notują zauważalne spadki wydajności, zwłaszcza podczas wykonywania operacji intensywnie wykorzystujących wejścia/wyjścia.

Żądania dotyczące przestrzeni użytkownika SDcardFS

Korzystanie z SDcardFS może przyspieszyć emulację pamięci i sprawdzanie uprawnień FUSE, ponieważ usuwa wywołanie przestrzeni użytkownika z jądra. Żądania dotyczące przestrzeni użytkownika podążają ścieżką: przestrzeń użytkownika → VFS → sdcardfs → VFS → ext4 → pamięć podręczna strony/pamięć.

Przekazywanie FUSE do SDcardFS

Rysunek 1. Prośby o przestrzeń użytkownika SDcardFS

Żądania dotyczące przestrzeni użytkownika FUSE

FUSE była początkowo używana do emulacji pamięci i umożliwienia aplikacjom transparentnego korzystania z pamięci wewnętrznej lub zewnętrznej karty SD. Korzystanie z FUSE powoduje pewien narzut, ponieważ każde żądanie w przestrzeni użytkownika przechodzi przez ścieżkę: Przestrzeń użytkownika → VFS → sterownik FUSE → demon FUSE → VFS → ext4 → pamięć podręczna strony/miejsce na dane.

Przekazywanie FUSE FUSE

Rysunek 2. Żądania dotyczące przestrzeni użytkownika FUSE

Żądania przekazywania FUSE

Większość uprawnień dostępu do plików jest sprawdzana w momencie ich otwierania, a dodatkowe sprawdzanie uprawnień odbywa się podczas odczytu i zapisu do pliku. W niektórych przypadkach w momencie otwierania pliku można wiedzieć, że aplikacja, która wysłała żądanie, ma pełny dostęp do tego pliku, więc system nie musi przekierowywać żądań z sterownika FUSE do demona FUSE (ponieważ oznaczałoby to tylko przenoszenie danych z jednego miejsca do drugiego).

W przypadku przekazywania przez 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ć przekazywane bezpośrednio do niższego systemu plików. Dzięki temu nie trzeba czekać na odpowiedź demona FUSE w przestrzeni użytkownika na prośby kierowane do sterownika FUSE.

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

Porównanie przekazywania FUSE

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

Gdy aplikacja uzyskuje dostęp do systemu plików FUSE, wykonuje te operacje:

  1. Sterownik FUSE obsługuje i umieszcza w kole żądanie, a następnie przekazuje je demonowi FUSE, który obsługuje system plików FUSE za pomocą konkretnej instancji połączenia z pliku /dev/fuse, którego odczyt jest zablokowany dla demona FUSE.

  2. Gdy demon FUSE otrzyma prośbę o otwarcie pliku, zdecyduje, czy w przypadku tego pliku ma być dostępna opcja FUSE passthrough. Jeśli jest dostępny, demon:

    1. Powiadamia sterownik FUSE o tym żądaniu.

    2. Umożliwia przekazywanie danych przez FUSE do pliku za pomocą ioctl FUSE_DEV_IOC_PASSTHROUGH_OPEN, który musi być wykonany na deskryptorze pliku otwartego /dev/fuse.

  3. ioctl otrzymuje (jako parametr) strukturę danych zawierającą:

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

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

    • Dodatkowe pola, które mogą pozostać puste i służą do przyszłych implementacji.

  4. Jeśli ioctl się powiedzie, demon FUSE wypełnia żądanie otwarcia, sterownik FUSE obsługuje odpowiedź demona FUSE, a do pliku FUSE w rdzeniu dodawane jest odwołanie do niższego pliku systemowego. Gdy aplikacja żąda operacji odczytu/zapisu w pliku FUSE, sterownik FUSE sprawdza, czy dostępne jest odwołanie do pliku niższego poziomu.

    • Jeśli dostępne jest odwołanie, sterownik tworzy nowe żądanie VFS z tymi samymi parametrami kierowanymi na plik znajdujący się na niższym poziomie systemu plików.

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

Powyższe operacje występują w przypadku odczytu/zapisu i odczytu-iteracji/zapisu-iteracji w przypadku ogólnych plików oraz operacji odczytu/zapisu w przypadku plików zmapowanych na pamięć. Przekazywanie FUSE dla konkretnego pliku istnieje 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 urządzenia docelowego.

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

Aby wyłączyć przekazywanie FUSE, pomiń tę zmianę konfiguracji lub ustaw wartość persist.sys.fuse.passthrough.enable na false. Jeśli wcześniej włączysz przekazywanie FUSE, wyłączenie tej funkcji spowoduje, że urządzenie nie będzie używać przekazywania FUSE, ale urządzenie pozostanie sprawne.

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

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

Więcej informacji znajdziesz w implementacji referencyjnej.

Weryfikacja przekazywania FUSE

Aby sprawdzić, czy MediaProvider używa przekierowania FUSE, sprawdź logcat pod kątem wiadomości debugowania. 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 wskazuje, że przepuszczanie FUSE jest używane.

Pakiet CTS dla Androida 12 zawiera CtsStorageTest, który obejmuje testy, które uruchamiają przepuszczanie FUSE. Aby przeprowadzić test ręcznie, użyj testu w ten sposób:

atest CtsStorageTest