HIDL'nin uzak prosedür çağrısı (RPC) altyapısı, bağlayıcı mekanizmaları kullanır. Bu, çağrıların ek maliyet içerdiği, çekirdek işlemlerini gerektirdiği ve planlayıcı işlemini tetikleyebileceği anlamına gelir. Ancak, verilerin daha az ek yük ve çekirdek katılımı olmadan işlemler arasında aktarılması gereken durumlarda Hızlı Mesaj Kuyruğu (FMQ) sistemi kullanılır.
FMQ, istenen özelliklere sahip mesaj sıraları oluşturur. HIDL RPC çağrısı üzerinden bir MQDescriptorSync
veya MQDescriptorUnsync
nesnesi gönderebilirsiniz. Bu nesne, mesaj kuyruğuna erişmek için alıcı işlem tarafından kullanılır.
Sıra türleri
Android iki sıra türünü (lezzetler olarak bilinir) destekler:
- Senkronize edilmemiş sıraların taşmasına izin verilir ve bu sıralarda çok sayıda okuyucu olabilir. Her okuyucu, verileri zamanında okumalı veya kaybeder.
- Senkronize sıraların taşmasına izin verilmez ve bu sıralarda yalnızca bir okuyucu olabilir.
Her iki sıra türünde de alt akışa izin verilmez (boş bir sıradan okuma başarısız olur) ve yalnızca bir yazar olabilir.
Senkronize edilmemiş sıralar
Senkronize edilmemiş bir kuyrukta yalnızca bir yazar ancak herhangi bir sayıda okuyucu olabilir. Sıra için bir yazma konumu vardır ancak her okuyucu kendi bağımsız okuma konumunu izler.
Yapılandırılmış sıra kapasitesinden büyük olmadıkları sürece sıraya yapılan yazma işlemleri her zaman başarılı olur (taşma olup olmadığı kontrol edilmez) (sıra kapasitesinden büyük yazma işlemleri hemen başarısız olur). Her okuyucunun farklı bir okuma konumu olabileceğinden, her okuyucunun her veri parçasını okumasını beklemek yerine, yeni yazma işlemlerinin alana ihtiyacı olduğunda veriler kuyruktan çıkarılır.
Verileri, kuyruğun sonuna düşmeden önce almak okuyucuların sorumluluğundadır. Mevcut olandan daha fazla veri okumaya çalışan bir okuma işlemi, engellenmeyen durumlarda hemen başarısız olur veya engelleyen durumlarda yeterli verinin mevcut olmasını bekler. Sıra kapasitesinden daha fazla veri okumaya çalışan bir okuma işlemi her zaman hemen başarısız olur.
Bir okuyucu, yazara yetişemezse (ör. yazılan ve henüz bu okuyucu tarafından okunmayan veri miktarı, sıra kapasitesini aşarsa) bir sonraki okuma işlemi veri döndürmez. Bunun yerine, okuyucunun okuma konumunu yazma konumuna ve kapasitenin yarısına sıfırlar ve ardından bir hata döndürür. Bu sayede, arabelleğin yarısı okuma için kullanılabilir durumda kalır ve kuyruğun hemen tekrar dolmasını önlemek için yeni yazma işlemleri için yer ayrılır. Okunabilir veriler, taşmadan sonra ancak bir sonraki okumadan önce kontrol edilirse okunabilir veri sayısı, sıra kapasitesinden daha fazla olur. Bu da taşma olduğunu gösterir. (Kuyruk, mevcut verileri kontrol etme ve bu verileri okuma denemesi arasında taşarsa taşmanın tek göstergesi okuma işleminin başarısız olmasıdır.)
Senkronize edilen sıraları
Senkronize bir kuyrukta tek bir yazma ve tek bir okuma konumu olan bir yazar ve bir okuyucu bulunur. Sırada yer alan alan miktarından daha fazla veri yazmak veya sırada bulunandan daha fazla veri okumak mümkün değildir. Engelleyici veya engellemeyen yazma ya da okuma işlevinin çağrılıp çağrılmadığına bağlı olarak, mevcut alanı veya verileri aşma girişimleri hemen başarısız olur ya da istenen işlem tamamlanana kadar engellenir. Sıra kapasitesinden daha fazla veri okuma veya yazma girişimleri her zaman hemen başarısız olur.
FMQ oluşturma
Mesaj kuyruğu için birden fazla MessageQueue
nesnesi gerekir: Biri yazılacak, biri veya daha fazlası okunacak. Yazma veya okuma için hangi nesnenin kullanılacağına dair açık bir yapılandırma yoktur. Kullanıcı, hem okuma hem de yazma için hiçbir nesnenin kullanılmadığından, en fazla bir yazar bulunduğundan ve senkronize edilen sıralarda en fazla bir okuyucu bulunduğundan emin olmalıdır.
İlk MessageQueue nesnesini oluşturma
Tek bir çağrıyla bir mesaj kuyruğu oluşturulur ve yapılandırılır:
#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 nonblocking 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 */);
MessageQueue<T, flavor>(numElements)
başlatıcısı, mesaj kuyruğu işlevini destekleyen bir nesne oluşturur ve başlatır.MessageQueue<T, flavor>(numElements, configureEventFlagWord)
başlatıcısı, mesaj kuyruğu işlevini engellemeyle destekleyen bir nesne oluşturup başlatır.flavor
, senkronize edilmiş bir sıra içinkSynchronizedReadWrite
veya senkronize edilmemiş bir sıra içinkUnsynchronizedWrite
olabilir.uint16_t
(bu örnekte), iç içe yerleştirilmiş arabellekleri (string
veyavec
türleri yok), imleçleri veya arayüzleri içermeyen herhangi bir HIDL tanımlı tür olabilir.kNumElementsInQueue
, giriş sayısı olarak kuyruğun boyutunu belirtir; kuyruk için ayrılan paylaşılan bellek arabelleğinin boyutunu belirler.
İkinci MessageQueue nesnesini oluşturun
Mesaj kuyruğunun ikinci tarafı, birinci taraftan elde edilen bir MQDescriptor
nesnesi kullanılarak oluşturulur. MQDescriptor
nesnesi, mesaj kuyruğunun ikinci ucunu tutan işleme HIDL veya AIDL RPC çağrısı üzerinden gönderilir. MQDescriptor
, aşağıdakiler dahil olmak üzere sırayla ilgili bilgileri içerir:
- Arabelleği ve yazma işaretçisini eşlemek için bilgiler.
- Okuma işaretçisini eşlemek için gereken bilgiler (sıra senkronize edilmişse).
- Etkinlik işareti kelimesini eşlemek için gereken bilgiler (sıra engelliyorsa).
- Nesne türü (
<T, flavor>
), sıra öğelerinin HIDL tarafından tanımlanan türünü ve sıra çeşidini (senkronize veya senkronize edilmemiş) içerir.
MessageQueue
nesnesi oluşturmak için MQDescriptor
nesnesini kullanabilirsiniz:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
resetPointers
parametresi, bu MessageQueue
nesnesi oluşturulurken okuma ve yazma konumlarının 0'a sıfırlanıp sıfırlanmayacağını belirtir.
Senkronize edilmemiş bir kuyrukta, okuma konumu (senkronize edilmemiş kuyruklardaki her MessageQueue
nesnesine yereldir) oluşturulma sırasında her zaman 0 olarak ayarlanır. MQDescriptor
genellikle ilk mesaj kuyruğu nesnesi oluşturulurken başlatılır. Paylaşılan bellek üzerinde daha fazla kontrol sahibi olmak için MQDescriptor
'ü manuel olarak ayarlayabilir (MQDescriptor
, system/libhidl/base/include/hidl/MQDescriptor.h
içinde tanımlanır) ve ardından her MessageQueue
nesnesini bu bölümde açıklandığı gibi oluşturabilirsiniz.
Engelleme sıraları ve etkinlik işaretleri
Varsayılan olarak, sıralar okuma ve yazma işlemlerini engellemeyi desteklemez. Okuma ve yazma çağrılarını engellemenin iki yolu vardır:
- Üç parametre (veri işaretçisi, öğe sayısı, zaman aşımı) içeren kısa form, tek bir kuyrukta tek tek okuma ve yazma işlemlerinde engellemeyi destekler. Bu form kullanıldığında, sıra etkinlik işaretini ve bit maskelerini dahili olarak işler ve ilk mesaj sırası nesnesi,
true
değerine sahip ikinci bir parametreyle başlatılmalıdır. Örnek:// For an unsynchronized FMQ that supports blocking mFmqUnsynchronizedBlocking = new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite> (kNumElementsInQueue, true /* enable blocking operations */);
- Altı parametre (etkinlik işareti ve bit maskeleri dahil) içeren uzun biçim, birden fazla sıra arasında paylaşılan bir
EventFlag
nesnesi kullanılmasını destekler ve kullanılacak bildirim bit maskelerini belirtmenize olanak tanır. Bu durumda, her okuma ve yazma çağrısına etkinlik işareti ve bit maskeleri sağlanmalıdır.
Uzun form için her readBlocking()
ve writeBlocking()
çağrısında EventFlag
değerini açıkça sağlayabilirsiniz. Kuyruklardan birini dahili bir etkinlik işaretiyle başlatabilirsiniz. Bu işaret daha sonra getEventFlagWord()
kullanılarak ilgili kuyruğun MessageQueue
nesnelerinden ayıklanır ve diğer FMQ'lerle birlikte kullanılmak üzere her işlemde bir EventFlag
nesnesi oluşturmak için kullanılır. Alternatif olarak, EventFlag
nesnelerini uygun herhangi bir paylaşılan bellekle başlatabilirsiniz.
Genel olarak her sırada yalnızca engelleme, kısa video engelleme veya uzun video engelleme seçeneklerinden biri kullanılmalıdır. Bunları karıştırmak hata değildir ancak istenen sonucu elde etmek için dikkatli bir şekilde programlama yapmanız gerekir.
Belleği salt okunur olarak işaretleme
Paylaşılan bellek varsayılan olarak okuma ve yazma izinlerine sahiptir. Senkronize edilmemiş sıralarda (kUnsynchronizedWrite
), yazar MQDescriptorUnsync
nesnelerini dağıtmadan önce tüm okuyucuların yazma izinlerini kaldırmak isteyebilir. Bu sayede diğer işlemlerin sıraya yazması engellenir. Bu, okuyucu işlemlerindeki hatalara veya kötü davranışlara karşı koruma sağlamak için önerilir.
Yazıcı, okuyucuların kuyruğun okuma tarafını oluşturmak için MQDescriptorUnsync
kullandıklarında kuyruğu sıfırlayabilmesini istiyorsa bellek salt okunur olarak işaretlenemez. Bu, MessageQueue
kurucusunun varsayılan davranışıdır. Bu nedenle, bu kuyruğun mevcut kullanıcıları varsa kuyruğu resetPointer=false
ile oluşturacak şekilde kodlarının değiştirilmesi gerekir.
- Yazıcı:
MQDescriptor
dosya tanımlayıcısıyla ve bölge salt okunur olarak ayarlanmış (PROT_READ
)ashmem_set_prot_region
çağrısı yapın:int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
- Okuyucu:
resetPointer=false
ile mesaj kuyruğu oluşturun (varsayılantrue
'tür):mFmq = new (std::nothrow) MessageQueue(mqDesc, false);
MessageQueue'ı kullanma
MessageQueue
nesnesinin herkese açık API'si:
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);
Tek bir işlemde ne kadar veri aktarılabileceğini belirlemek için availableToWrite()
ve availableToRead()
öğelerini kullanabilirsiniz. Senkronize edilmemiş bir sırada:
availableToWrite()
her zaman kuyruğun kapasitesini döndürür.- Her okuyucunun kendi okuma konumu vardır ve
availableToRead()
için kendi hesaplamasını yapar. - Yavaş bir okuyucunun bakış açısından, kuyruğun taşmasına izin verilir. Bu durum,
availableToRead()
'ün kuyruğun boyutundan daha büyük bir değer döndürmesine neden olabilir. Aşırı taşma sonrasında yapılan ilk okuma başarısız olur ve taşmanınavailableToRead()
aracılığıyla bildirilip bildirilmediğine bakılmaksızın, söz konusu okuyucunun okuma konumunun geçerli yazma işaretçisine eşit ayarlanmasına neden olur.
read()
ve write()
yöntemleri, istenen tüm verilerin sıraya aktarılıp aktarılamadığını (ve aktarılıp aktarılmadığını) true
döndürür. Bu yöntemler engelleme yapmaz; ya başarılı olur (ve true
döndürür) ya da hemen başarısız olur (false
döndürür).
readBlocking()
ve writeBlocking()
yöntemleri, istenen işlemin tamamlanmasını veya zaman aşımına uğramasını bekler (timeOutNanos
değeri 0 ise zaman aşımı asla gerçekleşmez).
Engelleme işlemleri, etkinlik işareti sözcüğü kullanılarak uygulanır. Varsayılan olarak her sıra, readBlocking()
ve writeBlocking()
kısa biçimlerini desteklemek için kendi işaret kelimesini oluşturur ve kullanır. Birden fazla sıra tek bir kelimeyi paylaşabilir. Böylece bir işlem, sıralardan herhangi birine yazma veya okuma işlemini bekleyebilir. getEventFlagWord()
işlevini çağırarak bir kuyruğun etkinlik işareti sözcüğüne işaretçi alabilir ve bu işaretçiyi (veya uygun bir paylaşılan bellek konumuna işaret eden herhangi bir işaretçiyi) farklı bir kuyruk için readBlocking()
ve writeBlocking()
işlevinin uzun biçimine iletilecek bir EventFlag
nesnesi oluşturmak üzere kullanabilirsiniz. readNotification
ve writeNotification
parametreleri, etkinlik işaretindeki hangi bitlerin söz konusu sıradaki okuma ve yazma işlemlerini işaretlemek için kullanılacağını belirtir. readNotification
ve writeNotification
, 32 bitlik bit maskeleridir.
readBlocking()
, writeNotification
bitlerini bekler; bu parametre 0 ise çağrı her zaman başarısız olur. readNotification
değeri 0 ise çağrı başarısız olmaz ancak başarılı bir okuma, bildirim bitlerini ayarlamaz. Senkronize bir kuyrukta bu, bit başka bir yerde ayarlanmadıkça ilgili writeBlocking()
çağrısının hiçbir zaman uyanmamasıdır. Senkronize edilmemiş bir kuyrukta writeBlocking()
beklemez (yine de yazma bildirimi biti ayarlamak için kullanılmalıdır) ve okumaların bildirim biti ayarlamamasının uygun olduğu kabul edilir. Benzer şekilde, readNotification
0 ise writeblocking()
başarısız olur ve başarılı bir yazma işlemi, belirtilen writeNotification
bitlerini ayarlar.
Aynı anda birden fazla sırada beklemek için bildirimlerin bit maskesini beklemek üzere bir EventFlag
nesnesinin wait()
yöntemini kullanın. wait()
yöntemi, uyanma ayarının yapılmasına neden olan bitleri içeren bir durum kelimesi döndürür. Ardından bu bilgiler, ilgili kuyruğun istenen yazma ve okuma işlemi için yeterli alana veya veriye sahip olduğunu doğrulamak ve engellenmeyen bir write()
ve read()
gerçekleştirmek için kullanılır. İşlem sonrası bildirim almak için EventFlag
nesnesinin wake()
yöntemine başka bir çağrı yapın. EventFlag
Abstreksiyonun tanımı için system/libfmq/include/fmq/EventFlag.h
bölümüne bakın.
Kopyasız işlemler
read
, write
, readBlocking
ve writeBlocking()
yöntemleri, bağımsız değişken olarak bir giriş/çıkış arabelleğinin işaretçisini alır ve aynı arabellek ile FMQ halka arabelleği arasında veri kopyalamak için dahili olarak memcpy()
çağrılarını kullanır. Android 8.0 ve sonraki sürümler, performansı artırmak için halka arabelleğe doğrudan işaretçi erişimi sağlayan bir API grubu içerir. Bu sayede memcpy
çağrılarını kullanma ihtiyacı ortadan kalkar.
Kopyasız FMQ işlemleri için aşağıdaki herkese açık API'leri kullanın:
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);
beginWrite
yöntemi, FMQ halka arabelleğine temel işaretçiler sağlar. Veriler yazıldıktan sonracommitWrite()
kullanarak verileri gönderin.beginRead
vecommitRead
yöntemleri aynı şekilde çalışır.beginRead
veWrite
yöntemleri, okunacak ve yazılacak mesajların sayısını giriş olarak alır ve okuma ya da yazma işleminin mümkün olup olmadığını belirten bir Boole değeri döndürür. Okuma veya yazma mümkünsememTx
struct, halka arabelleğinin paylaşılan belleğine doğrudan işaretçi erişimi için kullanılabilecek temel işaretçilerle doldurulur.MemRegion
yapısı, taban işaretçi (bellek bloğunun taban adresi) veT
açısından uzunluk (mesaj kuyruğunun HIDL tarafından tanımlanan türü açısından bellek bloğunun uzunluğu) dahil olmak üzere bir bellek bloğuyla ilgili ayrıntıları içerir.MemTransaction
yapı, halka tamponuna okuma veya yazma işleminin, kuyruğun başına dönmeyi gerektirebileceği için ikiMemRegion
yapısı (first
vesecond
) içerir. Bu, FMQ halka arabelleğine veri okumak ve yazmak için iki temel işaretçi gerektiği anlamına gelir.
Bir MemRegion
yapısından taban adresi ve uzunluğu almak için:
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
Bir MemTransaction
nesnesinde ilk ve ikinci MemRegion
yapılarına referans almak için:
const MemRegion& getFirstRegion(); // get a reference to the first MemRegion const MemRegion& getSecondRegion(); // get a reference to the second MemRegion
Sıfır kopyalama API'lerini kullanarak FMQ'ye yazma örneği:
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 }
Aşağıdaki yardımcı yöntemler de MemTransaction
'e dahildir:
T* getSlot(size_t idx);
, buMemTransaction
nesnesinin parçası olanMemRegions
içindekiidx
yuvasına işaret eden bir işaretçi döndürür.MemTransaction
nesnesi,T
türündeki N öğeyi okumak ve yazmak için bellek bölgelerini temsil ediyorsaidx
için geçerli aralık 0 ile N-1 arasındadır.bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
,startIdx
dizininden başlayarakT
türündekinMessages
öğeleri nesne tarafından açıklanan bellek bölgelerine yazar. Bu yöntemmemcpy()
kullanır ve sıfır kopyalama işlemi için kullanılmak üzere tasarlanmamıştır.MemTransaction
nesnesi,T
türündeki N öğeyi okumak ve yazmak için belleği temsil ediyorsaidx
için geçerli aralık 0 ile N-1 arasındadır.bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
,startIdx
'den başlayarak nesne tarafından açıklanan bellek bölgelerindenT
türündekinMessages
öğeyi okumak için kullanılan bir yardımcı yöntemdir. Bu yöntemmemcpy()
kullanır ve sıfır kopyalama işlemi için kullanılmak üzere tasarlanmamıştır.
Sırayı HIDL üzerinden gönderme
İçerik üreten taraf için:
- Yukarıda açıklandığı şekilde bir ileti kuyruğu nesnesi oluşturun.
- Nesnenin
isValid()
ile geçerli olduğunu doğrulayın. EventFlag
öğesinireadBlocking()
veyawriteBlocking()
öğesinin uzun biçimine göndererek birden fazla sırada bekliyorsanız işaretçi oluşturmak için başlatılmış birMessageQueue
nesnesinden etkinlik işaretçisini (getEventFlagWord()
kullanarak) ayıklayabilir ve gerekliEventFlag
nesnesini oluşturmak için bu işaretçiyi kullanabilirsiniz.- Tanımlayıcı nesnesi almak için
MessageQueue
yönteminigetDesc()
kullanın. - HAL dosyasında, yönteme
fmq_sync
veyafmq_unsync
türündeki bir parametre verin. BuradaT
, uygun bir HIDL tanımlı türdür.getDesc()
tarafından döndürülen nesneyi alıcı işleme göndermek için bunu kullanın.
Alıcı tarafında:
MessageQueue
nesnesi oluşturmak için tanımlayıcı nesnesini kullanın. Aynı sıra çeşidini ve veri türünü kullanın. Aksi takdirde şablon derlenemez.- Bir etkinlik işareti ayıkladıysanız işareti, alıcı işlemdeki ilgili
MessageQueue
nesnesinden ayıklayın. - Veri aktarmak için
MessageQueue
nesnesini kullanın.