Jeśli chcesz skorzystać z obsługi AIDL, zapoznaj się też FMQ z AIDL.
Infrastruktura zdalnego wywoływania procedur HIDL wykorzystuje mechanizmy Binder, co oznacza, że wywołania wiążą się z nadmiarem pracy, wymagają działania jądra i mogą aktywować działanie algorytmu szeregowania. Jednak w przypadkach, w których dane muszą być przenoszone między z mniejszym nakładem pracy i bez wykorzystania jądra systemu używany jest system FMQ.
FMQ tworzy kolejki wiadomości z odpowiednimi właściwościami. An
Obiekt MQDescriptorSync
lub MQDescriptorUnsync
może być
wysyłane przez wywołanie HIDL i używane przez proces odbierający do uzyskiwania dostępu do
w kolejce wiadomości.
Kolejki szybkich wiadomości są obsługiwane tylko w języku C++ i na urządzeniach z Androidem 8.0 lub nowszym.
Typy MessageQueue
Android obsługuje 2 rodzaje kolejek (rodzaje):
- Niezsynchronizowane kolejki mogą przekraczać limit i mogą zawierać czytelnicy; każdy czytelnik musi odczytać dane w odpowiednim czasie – w przeciwnym razie je utracisz.
- Zsynchronizowane kolejki nie mogą się przepełniać i mogą mieć jednego czytelnika.
Oba typy kolejek nie mogą być niedopełnione (odczyt z pustej kolejki) nie powiodła się) i może mieć tylko jednego zapisującego.
Nie zsynchronizowano
Niezsynchronizowana kolejka ma tylko jednego zapisującego, ale może mieć dowolną liczbę czytelników. Kolejka ma 1 pozycję zapisu. jednak każdy czytelnik zachowuje własnej, niezależnej pozycji odczytu.
Zapisy w kolejce zawsze się udają (nie są sprawdzane pod kątem przepełnienia), dopóki nie są większe niż skonfigurowana pojemność kolejki (zapisy są większe niż możliwości natychmiastowego działania kolejki nie udają się). Każdy czytnik może czytać coś innego zamiast czekać, aż każdy czytelnik zapozna się z każdym fragmentem danych, mogą zniknąć z kolejki za każdym razem, gdy nowe zapisy potrzebują wolnego miejsca.
Czytelnicy są odpowiedzialni za pobranie danych, zanim znikną z końca w kolejce. Odczyt, który próbuje odczytać więcej danych, niż jest dostępnych kończy się niepowodzeniem od razu (jeśli nie jest blokowane) lub czeka na zgromadzenie wystarczającej ilości danych (jeśli blokowanie). Odczyt, który próbuje odczytać więcej danych niż wynosi zawsze pojemność kolejki nie działa od razu.
Jeśli czytelnik nie dotrzymuje kroku autorowi, przez co ilość danych zapisane, ale jeszcze nieprzeczytane przez tego czytnika, przekracza pojemność kolejki, następny odczyt nie zwraca danych; odczytywane przez , aby była równa ostatniej pozycji zapisu, a następnie zwraca błąd. Jeśli dane dostępne do odczytu są sprawdzane po ich przekroczeniu, ale przed kolejnym odczytem pokazuje więcej danych dostępnych do odczytu niż pojemność kolejki, co oznacza wystąpiło przepełnienie. (Jeśli kolejka wystarcza na sprawdzenie dostępnych danych i próbując odczytać te dane, jedynym oznakem przepełnienia jest to, że odczyt nie powiedzie się).
Czytelnicy niezsynchronizowanej kolejki prawdopodobnie nie chcą resetować się wskaźniki do odczytu i zapisu w kolejce. Podczas tworzenia kolejki na podstawie czytelnicy deskryptorów powinni używać argumentu „false” dla argumentu „resetPointers” .
Synchronizowany
Zsynchronizowana kolejka ma 1 zapisującego i 1 czytelnika z 1 zapisem i jedną pozycję odczytu. Nie da się zapisać więcej danych niż kolejka zawiera miejsce na dane lub do odczytu więcej danych niż bieżąca kolejka. W zależności od tego, czy jest próbuje przekroczyć dostępną przestrzeń lub dane albo nie uda się zwrócić natychmiast lub blokować do czasu zakończenia wybranej operacji. Próby odczyt lub zapis danych przekraczających pojemność kolejki zawsze kończy się natychmiastowo.
Skonfiguruj FMQ
Kolejka wiadomości wymaga wielu obiektów MessageQueue
: 1 do
gdzie można zapisać treść i co najmniej jeden być odczytywany. Nie ma wyraźnych
konfiguracja obiektu używanego do zapisu lub odczytu; zależy od
aby upewnić się, że żaden obiekt nie jest używany do odczytu i zapisu,
jest maksymalnie jeden zapisujący, a dla zsynchronizowanych kolejek –
.
Tworzenie pierwszego obiektu MessageQueue
Kolejka wiadomości jest tworzona i konfigurowana z pojedynczym wywołaniem:
#include <fmq/MessageQueue.h> using android::hardware::kSynchronizedReadWrite; using android::hardware::kUnsynchronizedWrite; using android::hardware::MQDescriptorSync; using android::hardware::MQDescriptorUnsync; using android::hardware::MessageQueue; .... // For a synchronized non-blocking FMQ mFmqSynchronized = new (std::nothrow) MessageQueue<uint16_t, kSynchronizedReadWrite> (kNumElementsInQueue); // For an unsynchronized FMQ that supports blocking mFmqUnsynchronizedBlocking = new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite> (kNumElementsInQueue, true /* enable blocking operations */);
- Inicjator
MessageQueue<T, flavor>(numElements)
tworzy i inicjuje obiekt, który obsługuje funkcję kolejki wiadomości. - Inicjator
MessageQueue<T, flavor>(numElements, configureEventFlagWord)
tworzy i inicjuje obiekt obsługujące funkcję kolejki wiadomości z blokowaniem. flavor
może mieć wartośćkSynchronizedReadWrite
w przypadku synchronizowana kolejka lubkUnsynchronizedWrite
w przypadku niezsynchronizowanej kolejki kolejkę.uint16_t
(w tym przykładzie) może być dowolnym Typ określony przez HIDL, który nie zawiera zagnieżdżonych buforów (bezstring
anivec
) różne typy komponentów), uchwyty i interfejsy.kNumElementsInQueue
wskazuje rozmiar kolejki w liczbie wpisy; określa rozmiar przydzielonego bufora pamięci współdzielonej dla kolejki.
Utwórz drugi obiekt MessageQueue.
Druga strona kolejki wiadomości jest tworzona za pomocą
Obiekt MQDescriptor
został pobrany z pierwszej strony.
Obiekt MQDescriptor
jest wysyłany do procesu przez wywołanie HIDL lub AIDL RPC
który zawiera drugi koniec kolejki wiadomości.
MQDescriptor
zawiera informacje o kolejce, w tym:
- Informacje do mapowania bufora i wskaźnika zapisu.
- informacje do mapowania wskaźnika odczytu (jeśli kolejka jest zsynchronizowana).
- Informacje do mapowania słowa flagi zdarzenia (jeśli kolejka jest blokowana).
- Typ obiektu (
<T, flavor>
), który obejmuje Zdefiniowany przez HDL typ: elementów i rodzaju kolejki (zsynchronizowanych lub niezsynchronizowanych),
Obiektu MQDescriptor
można użyć do utworzenia
Obiekt MessageQueue
:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
Parametr resetPointers
wskazuje, czy zresetować odczyt
i zapisz pozycje jako 0 podczas tworzenia tego obiektu MessageQueue
.
W niezsynchronizowanej kolejce pozycja odczytu (lokalna dla każdej
MessageQueue
obiekt w niezsynchronizowanych kolejkach) ma zawsze wartość 0
podczas tworzenia. Zwykle MQDescriptor
jest inicjowany podczas
podczas tworzenia pierwszego obiektu kolejki wiadomości. Dodatkowa kontrola nad udostępnianymi
możesz skonfigurować MQDescriptor
ręcznie
(MQDescriptor
jest zdefiniowany w
system/libhidl/base/include/hidl/MQDescriptor.h
).
a następnie utwórz każdy obiekt MessageQueue
zgodnie z opisem w tej sekcji.
Blokuj kolejki i flagi zdarzeń
Domyślnie kolejki nie obsługują blokowania odczytu ani zapisu. Są 2 rodzaje blokowania wywołań odczytu/zapisu:
- Wersja krótka z 3 parametrami (wskaźnikiem danych, liczbą elementów,
). Obsługuje blokowanie pojedynczych operacji odczytu/zapisu na pojedynczym
kolejkę. Gdy używasz tego formularza, kolejka obsługuje flagę zdarzenia i maski bitowe
wewnętrznie, a pierwszy obiekt kolejki wiadomości musi
zostanie zainicjowana drugim parametrem o wartości
true
. Na przykład:// For an unsynchronized FMQ that supports blocking mFmqUnsynchronizedBlocking = new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite> (kNumElementsInQueue, true /* enable blocking operations */);
- Wersja długa z 6 parametrami (obejmuje flagę zdarzenia i maski bitowe).
Obsługuje używanie wspólnego obiektu
EventFlag
między wieloma kolejkami i umożliwia określenie masek bitów powiadomień, które mają być używane. W tym przypadku parametr Każde wywołanie odczytu i zapisu musi mieć flagę zdarzenia i maski bitowe.
W przypadku długiej formy właściwość EventFlag
należy podać jawnie w
za każde połączenie readBlocking()
i writeBlocking()
. Jedna z tych wartości:
kolejki mogą zostać zainicjowane przy użyciu wewnętrznej flagi zdarzenia, którą należy następnie
wyodrębnione z obiektów MessageQueue
tej kolejki za pomocą funkcji
getEventFlagWord()
i użyto do utworzenia EventFlag
obiektów w każdym procesie do użycia z innymi FMQ. Ewentualnie
EventFlag
obiektu można zainicjować za pomocą dowolnego odpowiedniego udostępnionego
pamięci.
Ogólnie w każdej kolejce powinna być używana tylko jedna z krótkich, nieblokujących treści albo blokowanie długich filmów. Mieszanie tych elementów to nie błąd, ale zachowaj ostrożność do osiągnięcia zamierzonych rezultatów.
Oznacz pamięć jako tylko do odczytu
Domyślnie pamięć współdzielona ma uprawnienia do odczytu i zapisu. Niezsynchronizowane
(kUnsynchronizedWrite
), autor może usunąć uprawnienia do zapisu wszystkich
czytelników, zanim przekaże obiekty MQDescriptorUnsync
. Dzięki temu druga strona
procesy nie mogą zapisywać w kolejce, co jest zalecane w celu ochrony przed błędami i niewłaściwym działaniem
jak interpretuje czytelnika.
Jeśli autor chce, aby czytelnicy mogli zresetować kolejkę za każdym razem, gdy używają polecenia
MQDescriptorUnsync
, aby utworzyć stronę do odczytu kolejki, to pamięci nie można oznaczyć
jako tylko do odczytu. Jest to domyślne działanie konstruktora „MessageQueue”. Jeśli więc użytkownik zawiera już
z istniejących użytkowników tej kolejki, ich kod należy zmienić, aby utworzyć kolejkę
resetPointer=false
- Zapisujący: wywołaj funkcję
ashmem_set_prot_region
z deskryptorem plikuMQDescriptor
i region ustawiony jako tylko do odczytu (PROT_READ
):int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
- Czytnik: utwórz kolejkę wiadomości za pomocą funkcji
resetPointer=false
wartość domyślna totrue
):mFmq = new (std::nothrow) MessageQueue(mqDesc, false);
Używanie MessageQueue
Publiczny interfejs API obiektu MessageQueue
:
size_t availableToWrite() // Space available (number of elements). size_t availableToRead() // Number of elements available. size_t getQuantumSize() // Size of type T in bytes. size_t getQuantumCount() // Number of items of type T that fit in the FMQ. bool isValid() // Whether the FMQ is configured correctly. const MQDescriptor<T, flavor>* getDesc() // Return info to send to other process. bool write(const T* data) // Write one T to FMQ; true if successful. bool write(const T* data, size_t count) // Write count T's; no partial writes. bool read(T* data); // read one T from FMQ; true if successful. bool read(T* data, size_t count); // Read count T's; no partial reads. bool writeBlocking(const T* data, size_t count, int64_t timeOutNanos = 0); bool readBlocking(T* data, size_t count, int64_t timeOutNanos = 0); // Allows multiple queues to share a single event flag word std::atomic<uint32_t>* getEventFlagWord(); bool writeBlocking(const T* data, size_t count, uint32_t readNotification, uint32_t writeNotification, int64_t timeOutNanos = 0, android::hardware::EventFlag* evFlag = nullptr); // Blocking write operation for count Ts. bool readBlocking(T* data, size_t count, uint32_t readNotification, uint32_t writeNotification, int64_t timeOutNanos = 0, android::hardware::EventFlag* evFlag = nullptr) // Blocking read operation for count Ts; //APIs to allow zero copy read/write operations bool beginWrite(size_t nMessages, MemTransaction* memTx) const; bool commitWrite(size_t nMessages); bool beginRead(size_t nMessages, MemTransaction* memTx) const; bool commitRead(size_t nMessages);
Można użyć availableToWrite()
i availableToRead()
aby określić, ile danych można przenieść w ramach pojedynczej operacji. W
niezsynchronizowana kolejka:
- Funkcja
availableToWrite()
zawsze zwraca pojemność kolejki. - Każdy czytelnik ma własne miejsce odczytu i wykonuje własne obliczenia
availableToRead()
- Z punktu widzenia powolnego czytelnika kolejka może się przepełnić.
może to spowodować, że funkcja
availableToRead()
zwróci wartość większą niż od rozmiaru kolejki. Pierwszy odczyt po przekroczeniu limitu kończy się niepowodzeniem i powoduje pozycja odczytu tego czytnika ustawiona na bieżący wskaźnik zapisu, czy zgłoszenie przepełnienia zostało zgłoszoneavailableToRead()
Metody read()
i write()
zwracają
true
, jeśli można przenieść (i być) wszystkie żądane dane do/z
w kolejce. Te metody nie blokują dostępu. mu się to uda (i zwróci
true
) lub niepowodzenie zwrotu (false
) natychmiast.
Metody readBlocking()
i writeBlocking()
czekają
aż do zakończenia żądanej operacji lub do momentu, gdy upłynie czas oczekiwania (a
Wartość timeOutNanos
o wartości 0 oznacza, że nigdy nie przekroczono limitu czasu.
Operacje blokowania są implementowane za pomocą słowa flagi zdarzenia. Domyślnie
każda kolejka tworzy i używa własnych flag do obsługi krótkich
readBlocking()
i writeBlocking()
. To możliwe,
wiele kolejek do współdzielenia 1 słowa, tak aby proces mógł czekać na zapisy lub
do dowolnej z kolejek. Wskaźnikiem do słowa flagi zdarzenia kolejki może być
uzyskane przez wywołanie metody getEventFlagWord()
i ten wskaźnik (lub dowolny
wskaźnik do odpowiedniej lokalizacji współdzielonej pamięci) można wykorzystać do utworzenia
EventFlag
obiekt, który ma zostać przesłany do długiej postaci
readBlocking()
i writeBlocking()
dla innego
kolejkę. readNotification
i writeNotification
które określają, które bity flagi zdarzenia mają być używane do sygnalizowania odczytów i
pisze w tej kolejce. readNotification
i
writeNotification
to 32-bitowa maska bitowa.
readBlocking()
czeka na writeNotification
bitach;
jeśli ten parametr ma wartość 0, wywołanie zawsze kończy się niepowodzeniem. Jeśli
readNotification
ma wartość 0, wywołanie nie kończy się niepowodzeniem, ale
udany odczyt nie ustawi żadnych powiadomień. W zsynchronizowanej kolejce
oznaczałoby to, że odpowiednie wywołanie writeBlocking()
nigdy się nie wybudza, chyba że bit zostanie ustawiony w innym miejscu. W niezsynchronizowanej kolejce
Funkcja writeBlocking()
nie czeka (nadal należy jej używać do ustawiania
zapisu powiadomienia), a odczyty nie mogą ustawiać żadnych
powiadomienia. Analogicznie żądanie writeblocking()
kończy się niepowodzeniem, jeśli
readNotification
to 0, a udany zapis ustawia określoną wartość
writeNotification
bitów.
Aby zaczekać na kilku kolejkach jednocześnie, użyj metody EventFlag
Metoda wait()
oczekiwania na maskę bitową powiadomień.
Metoda wait()
zwraca słowo stanu z bitami, które spowodowały
Pobudka ustawiona. Te informacje są następnie używane do sprawdzenia, czy odpowiednia kolejka
wystarczającą ilość miejsca lub danych do wykonania żądanej operacji zapisu/odczytu, a następnie wykonaj
nieblokujące: write()
/read()
. Aby pobrać operację wysyłania
powiadomienie, użyj innego połączenia z numerem EventFlag
wake()
. Definicja: EventFlag
abstrakcja, odnosi się
system/libfmq/include/fmq/EventFlag.h
0 operacji kopiowania
read
.write
.readBlocking
.writeBlocking()
Interfejsy API używają wskaźnika do bufora wejściowego/wyjściowego jako argumentu i używają
memcpy()
wywołuje wewnętrznie, aby skopiować dane między tym samym a
Bufor dzwonka FMQ. Aby zwiększyć wydajność, Android 8.0 i nowsze wersje zawierają zestaw
interfejsy API zapewniające bezpośredni dostęp wskaźnika do bufora pierścieniowego, eliminujące
muszą użyć memcpy
połączeń.
Użyj następujących publicznych interfejsów API do operacji FMQ bez kopiowania:
bool beginWrite(size_t nMessages, MemTransaction* memTx) const; bool commitWrite(size_t nMessages); bool beginRead(size_t nMessages, MemTransaction* memTx) const; bool commitRead(size_t nMessages);
- Metoda
beginWrite
dostarcza wskaźniki bazowe do pierścienia FMQ bufora. Po zapisaniu danych zatwierdź je za pomocącommitWrite()
. MetodybeginRead
/commitRead
działają tak samo. - Metody
beginRead
/Write
przyjmują jako dane wejściowe liczba wiadomości do odczytu/zapisania i zwraca wartość logiczną wskazującą, czy odczyt/zapis jest możliwy. Jeśli odczyt lub zapis jest możliwy,memTx
element struct zawiera wskaźniki bazowe, których można używać we wskaźniku bezpośrednim do współdzielonej pamięci bufora dzwonka. - Struktura
MemRegion
zawiera szczegóły bloku pamięci, łącznie ze wskaźnikiem bazowym (adresem podstawowym bloku pamięci) i długością w argumencie wyrażeniaT
(długość bloku pamięci zgodnie ze zdefiniowanym przez HIDL typu kolejki wiadomości). - Struktura
MemTransaction
zawiera 2 elementy:MemRegion
struct,first
isecond
jako odczyt lub zapis w bufor dzwonka może wymagać opuszczenia kolejki na początku kolejki. Ten oznaczałoby, że do odczytu/zapisu danych w FMQ potrzebne są dwa wskaźniki bazowe bufor dzwonka.
Aby uzyskać adres podstawowy i jego długość z struktury MemRegion
:
T* getAddress(); // gets the base address size_t getLength(); // gets the length of the memory region in terms of T size_t getLengthInBytes(); // gets the length of the memory region in bytes
Aby uzyskać odniesienia do pierwszego i drugiego elementu MemRegion
w
Obiekt MemTransaction
:
const MemRegion& getFirstRegion(); // get a reference to the first MemRegion const MemRegion& getSecondRegion(); // get a reference to the second MemRegion
Przykład zapisu do FMQ z użyciem interfejsów API typu zero-copy:
MessageQueueSync::MemTransaction tx; if (mQueue->beginRead(dataLen, &tx)) { auto first = tx.getFirstRegion(); auto second = tx.getSecondRegion(); foo(first.getAddress(), first.getLength()); // method that performs the data write foo(second.getAddress(), second.getLength()); // method that performs the data write if(commitWrite(dataLen) == false) { // report error } } else { // report error }
W MemTransaction
są też dostępne te metody pomocnicze:
T* getSlot(size_t idx);
Zwraca wskaźnik do boksuidx
w ciągu znakówMemRegions
, które są częścią tego komponentu (MemTransaction
) obiektu. Jeśli obiektMemTransaction
reprezentuje pamięć regionów do odczytu/zapisu N elementów typu T, a następnie poprawny zakresidx
mieści się w zakresie od 0 do N-1.bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
ZapisznMessages
elementy typu T w regionach pamięci opisane przez ten obiekt, począwszy od indeksustartIdx
. Ta metoda ma formatmemcpy()
i nie powinien być używany do wykonywania kopii zerowej . Jeśli obiektMemTransaction
reprezentuje pamięć do odczytu/zapisu N elementów typu T, prawidłowy zakresidx
wynosi od 0 do N-1.bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
Metoda pomocnicza do odczytunMessages
elementów typu T z regiony pamięci opisane przez obiekt, począwszy odstartIdx
. Ten korzysta z metodymemcpy()
i nie powinna być używana do wykonywania kopii zerowej .
Wyślij kolejkę przez HIDL
Strona tworzenia:
- Utwórz obiekt kolejki wiadomości w sposób opisany powyżej.
- Sprawdź, czy obiekt jest prawidłowy w
isValid()
. - Jeśli czekasz na wiele kolejek, przechodząc
EventFlag
na długą formęreadBlocking()
/writeBlocking()
, możesz wyodrębnić parametr wskaźnik flagi zdarzenia (za pomocągetEventFlagWord()
) zMessageQueue
obiekt, który został zainicjowany w celu utworzenia flagi, oraz użyj tej flagi do utworzenia niezbędnego obiektuEventFlag
. - Użyj metody
MessageQueue
getDesc()
, aby uzyskać do obiektu deskryptora. - W pliku
.hal
podaj parametrowi metodyfmq_sync
lubfmq_unsync
, gdzieT
to odpowiedni typ zdefiniowany przez HIDL. Użyj tego pola, aby wysłać obiekt zwrócony przezgetDesc()
do procesu odbierania.
Po stronie odbiorcy:
- Obiekt deskryptora pozwala utworzyć obiekt
MessageQueue
. Bądź użyj tego samego rodzaju kolejki i typu danych. W przeciwnym razie szablon nie kompilować. - Jeśli została wyodrębniona flaga zdarzenia, wyodrębnij ją z odpowiednich
MessageQueue
obiekt w procesie odbierania. - Do przeniesienia danych użyj obiektu
MessageQueue
.