HIDL'nin uzak prosedür çağrısı (RPC) altyapısı, bağlayıcı mekanizmaları kullanır. Yani çağrılar ek yük içerir, çekirdek işlemleri gerektirir ve planlayıcı işlemini tetikleyebilir. 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, alıcı işlem tarafından mesaj kuyruğuna erişmek için kullanılır.
Sıra türleri
Android, iki sıra türünü (aroma olarak bilinir) destekler:
- Senkronize edilmemiş sıraların taşmasına izin verilir ve çok sayıda okuyucu olabilir. Her okuyucu, verileri zamanında okumalı veya kaybetmelidir.
- 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. Bununla birlikte, 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.
Okuyucular, veriler sıranın sonuna düşmeden önce bunları almaktan sorumludur. 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 okuma işlemleri her zaman anında başarısız olur.
Bir okuyucu, yazara yetişemezse (bu nedenle, yazılan ve henüz okuyucu tarafından okunmayan veri miktarı, sıra kapasitesinden büyükse) sonraki okuma işlemi veri döndürmez. Bunun yerine, okuyucunun okuma konumunu en son yazma konumuna eşit olacak şekilde sıfırlar ve ardından hata döndürür. Okunabilir veriler, taşmadan sonra ancak sonraki okumadan önce kontrol edilirse okunabilir veri sayısı, sıra kapasitesinden fazla olur ve taşma meydana geldiğini gösterir. (Sıra, kullanılabilir verileri kontrol etme ve bu verileri okumaya çalışma arasında taşarsa taşmanın tek göstergesi okumanın başarısız olduğudur.)
Senkronize edilen sıralar
Senkronize bir kuyrukta tek bir yazma konumu ve tek bir okuma konumu olan bir yazar ve bir okuyucu bulunur. Sıranın şu anda sakladığından daha fazla veri yazmak veya 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ıranın 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 eşleme ve işaretçi yazma bilgileri.
- 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).
- Sıralı liste öğelerinin HIDL tarafından tanımlanan türünü ve sıralı liste çeşidini (senkronize veya senkronize edilmemiş) içeren nesne türü (
<T, flavor>
).
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 sırada, okuma konumu (senkronize edilmemiş sıralardaki her bir MessageQueue
nesnesinin yerelidir) oluşturma sırasında her zaman 0'a 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şlemlerinin engellenmesini 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 biçim, 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ı parametreli (etkinlik işareti ve bit maskeleri dahil) uzun biçim, birden çok sıra arasında paylaşılan bir
EventFlag
nesnesinin kullanılmasını destekler ve kullanılacak bildirim bit maskelerinin belirtilmesini sağlar. Bu durumda, her okuma ve yazma çağrısına etkinlik işareti ve bit maskeleri sağlanmalıdır.
Uzun biçim için EventFlag
öğesini her readBlocking()
ve writeBlocking()
çağrısında 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, diğer işlemlerin sıraya yazamamasını sağlar. Bu, okuyucu işlemlerindeki hatalara veya kötü davranışlara karşı koruma sağlamak için önerilir.
Yazar, sıranın okunan tarafını oluşturmak için MQDescriptorUnsync
kullandığında okuyucuların sırayı sıfırlayabilmesini isterse 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'yu 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. Taşma işleminden sonraki ilk okuma işlemi başarısız olur ve bu okuyucunun okuma konumu,availableToRead()
aracılığıyla bildirilip bildirilmediğine bakılmaksızın mevcut yazma işaretçisine eşit şekilde ayarlanır.
read()
ve write()
yöntemleri, istenen tüm verilerin sıraya aktarılıp aktarılamadığına bağlı olarak true
değerini 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 edilmiş bir sırada bu,
bit başka bir yerde ayarlanmadığı sürece ilgili writeBlocking()
çağrısının hiçbir zaman uyanmayacağı anlamına gelir. Senkronize edilmemiş bir sırada, writeBlocking()
beklemez (yazma bildirimi bitini ayarlamak için yine de kullanılmalıdır) ve okumaların herhangi bir bildirim biti ayarlamaması uygundur. 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 bildirim bit maskesini beklemek üzere bir EventFlag
nesnesininwait()
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. Bu bilgiler daha sonra ilgili sırada istenen yazma ve okuma işlemi için yeterli alana veya veriye sahip olduğunu doğrulamak ve engelleyici olmayan write()
ve read()
işlemleri 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
struct, bir bellek bloğuyla ilgili ayrıntıları içerir. Bu ayrıntılar arasında temel işaretçi (hafıza bloğunun temel adresi) veT
cinsinden uzunluk (HIDL tarafından tanımlanan mesaj sırasının HIDL tanımlı türü olarak bellek bloğunun uzunluğu) yer alır.MemTransaction
struct, ikiMemRegion
struct içerir; halka arabelleğine okuma veya yazma işlemi sırasında sıranın başlangıcına sarmalama gerekebilir.first
vesecond
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 okuyup yazacak bellek bölgelerini temsil ediyorsa geçerliidx
aralığı 0 ile N-1 arasında olur.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.- Açıklayıcı nesne almak için
MessageQueue
getDesc()
yöntemini 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 çıkardıysanız işareti alma işleminde karşılık gelen
MessageQueue
nesnesinden çıkarın. - Veri aktarmak için
MessageQueue
nesnesini kullanın.