تستخدم البنية التحتية لاستدعاء الإجراء البعيد (RPC) لـ HIDL آليات Binder ، مما يعني أن الاستدعاءات تتضمن حملًا زائدًا ، وتتطلب عمليات kernel ، وقد تؤدي إلى إجراء الجدولة. ومع ذلك ، في الحالات التي يجب فيها نقل البيانات بين العمليات بأقل عبء وبدون تدخل kernel ، يتم استخدام نظام Fast Message Queue (FMQ).
يقوم FMQ بإنشاء قوائم انتظار الرسائل بالخصائص المطلوبة. يمكن إرسال كائن MQDescriptorSync
أو MQDescriptorUnsync
عبر استدعاء HIDL RPC واستخدامه بواسطة عملية الاستلام للوصول إلى قائمة انتظار الرسائل.
يتم دعم قوائم انتظار الرسائل السريعة فقط في C ++ وعلى الأجهزة التي تعمل بنظام Android 8.0 والإصدارات الأحدث.
أنواع MessageQueue
يدعم Android نوعين من قوائم الانتظار (المعروفة باسم النكهات ):
- يُسمح لقوائم الانتظار غير المتزامنة بالتجاوز ، ويمكن أن تحتوي على العديد من القراء ؛ يجب على كل قارئ قراءة البيانات في الوقت المناسب وإلا فقدها.
- لا يُسمح بتجاوز قوائم الانتظار المتزامنة ، ويمكن أن تحتوي على قارئ واحد فقط.
لا يُسمح لكلا نوعي قائمة الانتظار بالتجاوز (ستفشل القراءة من قائمة انتظار فارغة) ويمكن أن يكون لهما كاتب واحد فقط.
غير متزامن
تحتوي قائمة الانتظار غير المتزامنة على كاتب واحد فقط ، ولكن يمكن أن تحتوي على أي عدد من القراء. يوجد موضع كتابة واحد لقائمة الانتظار ؛ ومع ذلك ، يتتبع كل قارئ موقع القراءة المستقل الخاص به.
تنجح عمليات الكتابة إلى قائمة الانتظار دائمًا (لا يتم التحقق من تجاوز سعة الانتظار) طالما أنها ليست أكبر من سعة قائمة الانتظار المكونة (تفشل عمليات الكتابة الأكبر من سعة قائمة الانتظار على الفور). نظرًا لأن كل قارئ قد يكون لديه موضع قراءة مختلف ، بدلاً من انتظار قراءة كل قارئ لكل جزء من البيانات ، يُسمح للبيانات بالتراجع عن قائمة الانتظار كلما احتاج الكتاب الجديد إلى المساحة.
القراءات مسؤولة عن استرداد البيانات قبل أن تقع في نهاية قائمة الانتظار. القراءة التي تحاول قراءة بيانات أكثر مما هو متاح إما أن تفشل على الفور (إذا لم يتم حظرها) أو تنتظر توفر بيانات كافية (في حالة الحظر). القراءة التي تحاول قراءة بيانات أكثر من سعة قائمة الانتظار تفشل دائمًا على الفور.
إذا فشل القارئ في مواكبة الكاتب ، بحيث تكون كمية البيانات المكتوبة والتي لم يقرأها ذلك القارئ بعد أكبر من سعة قائمة الانتظار ، فإن القراءة التالية لا تُرجع البيانات ؛ بدلاً من ذلك ، فإنه يعيد تعيين موضع قراءة القارئ ليساوي موضع الكتابة الأخير ثم يعيد الفشل. إذا تم فحص البيانات المتاحة للقراءة بعد تجاوز السعة ولكن قبل القراءة التالية ، فإنها تعرض المزيد من البيانات المتاحة للقراءة أكثر من سعة قائمة الانتظار ، مما يشير إلى حدوث تجاوز. (إذا تجاوزت قائمة الانتظار بين التحقق من البيانات المتاحة ومحاولة قراءة تلك البيانات ، فإن المؤشر الوحيد على تجاوز السعة هو فشل القراءة.)
متزامن
تحتوي قائمة الانتظار المتزامنة على كاتب واحد وقارئ واحد بموضع كتابة واحد وموضع قراءة واحد. من المستحيل كتابة بيانات أكثر مما تحتويه قائمة الانتظار على مساحة أو قراءة بيانات أكثر مما تحتويه قائمة الانتظار حاليًا. اعتمادًا على ما إذا كانت وظيفة الحظر أو الكتابة أو القراءة غير المحظورة تسمى ، محاولات تجاوز المساحة المتاحة أو البيانات إما بإرجاع الفشل على الفور أو الحظر حتى يمكن إكمال العملية المطلوبة. ستفشل دائمًا محاولات قراءة أو كتابة بيانات أكثر من سعة قائمة الانتظار على الفور.
إنشاء FMQ
تتطلب قائمة انتظار الرسائل كائنات متعددة لـ MessageQueue
: واحد تتم الكتابة إليه وواحد أو أكثر للقراءة منه. لا يوجد تكوين صريح للكائن المستخدم في الكتابة أو القراءة ؛ الأمر متروك للمستخدم للتأكد من عدم استخدام أي كائن للقراءة والكتابة ، وأن هناك كاتب واحد على الأكثر ، ولقوائم الانتظار المتزامنة ، أنه يوجد قارئ واحد على الأكثر.
إنشاء كائن MessageQueue الأول
يتم إنشاء قائمة انتظار الرسائل وتكوينها بمكالمة واحدة:
#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 */);
- يقوم
MessageQueue<T, flavor>(numElements)
بإنشاء وتهيئة كائن يدعم وظيفة قائمة انتظار الرسائل. - يقوم
MessageQueue<T, flavor>(numElements, configureEventFlagWord)
بإنشاء وتهيئة كائن يدعم وظيفة قائمة انتظار الرسائل مع الحظر. - يمكن أن تكون
flavor
إماkSynchronizedReadWrite
انتظار متزامنة أوkUnsynchronizedWrite
انتظار غير متزامنة. - يمكن أن يكون
uint16_t
(في هذا المثال) أي نوع معرف HIDL لا يتضمن المخازن المؤقتة المتداخلة (لا توجدstring
أو أنواعvec
) أو مقابض أو واجهات. - يشير
kNumElementsInQueue
إلى حجم قائمة الانتظار في عدد الإدخالات ؛ يحدد حجم ذاكرة التخزين المؤقت المشتركة التي سيتم تخصيصها لقائمة الانتظار.
إنشاء كائن MessageQueue الثاني
يتم إنشاء الجانب الثاني من قائمة انتظار الرسائل باستخدام كائن MQDescriptor
تم الحصول عليه من الجانب الأول. يتم إرسال كائن MQDescriptor
عبر استدعاء HIDL RPC إلى العملية التي ستحتوي على الطرف الثاني لقائمة انتظار الرسائل. يحتوي MQDescriptor
على معلومات حول قائمة الانتظار ، بما في ذلك:
- معلومات لتعيين المخزن المؤقت وكتابة المؤشر.
- معلومات لتعيين مؤشر القراءة (إذا كانت قائمة الانتظار متزامنة).
- معلومات لتعيين كلمة علم الحدث (إذا كانت قائمة الانتظار محظورة).
- نوع الكائن (
<T, flavor>
) ، والذي يتضمن نوع HIDL المحدد لعناصر قائمة الانتظار ونكهة قائمة الانتظار (متزامنة أو غير متزامنة).
يمكن استخدام كائن MQDescriptor
لإنشاء كائن MessageQueue
:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
تشير المعلمة resetPointers
إلى ما إذا كان سيتم إعادة تعيين مواضع القراءة والكتابة إلى 0 أثناء إنشاء كائن MessageQueue
. في قائمة انتظار غير متزامنة ، يتم دائمًا تعيين موضع القراءة (الذي يكون محليًا لكل كائن MessageQueue
في قوائم الانتظار غير المتزامنة) على 0 أثناء الإنشاء. عادةً ما يتم تهيئة MQDescriptor
أثناء إنشاء كائن قائمة انتظار الرسائل الأول. لمزيد من التحكم في الذاكرة المشتركة ، يمكنك إعداد MQDescriptor
يدويًا (يتم تعريف MQDescriptor
في system/libhidl/base/include/hidl/MQDescriptor.h
) ثم إنشاء كل كائن MessageQueue
كما هو موضح في هذا القسم.
حظر قوائم الانتظار وعلامات الحدث
بشكل افتراضي ، لا تدعم قوائم الانتظار حظر عمليات القراءة / الكتابة. هناك نوعان من حظر مكالمات القراءة / الكتابة:
- نموذج قصير ، مع ثلاث معلمات (مؤشر البيانات ، عدد العناصر ، المهلة). يدعم الحظر على عمليات القراءة / الكتابة الفردية في قائمة انتظار واحدة. عند استخدام هذا النموذج ، ستتعامل قائمة الانتظار مع علامة الحدث وأزرار البت داخليًا ، ويجب تهيئة كائن قائمة انتظار الرسائل الأول باستخدام معلمة ثانية من
true
. على سبيل المثال:// For an unsynchronized FMQ that supports blocking mFmqUnsynchronizedBlocking = new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite> (kNumElementsInQueue, true /* enable blocking operations */);
- Long form, with six parameters (includes event flag and bitmasks).
Supports using a shared
EventFlag
object between multiple queues and allows specifying the notification bit masks to be used. In this case, the event flag and bitmasks must be supplied to each read and write call.
For the long form, the EventFlag
can be supplied explicitly in
each readBlocking()
and writeBlocking()
call. One of
the queues may be initialized with an internal event flag, which must then be
extracted from that queue's MessageQueue
objects using
getEventFlagWord()
and used to create EventFlag
objects in each process for use with other FMQs. Alternatively, the
EventFlag
objects can be initialized with any suitable shared
memory.
In general, each queue should use only one of non-blocking, short-form blocking, or long-form blocking. It is not an error to mix them, but careful programming is required to get the desired result.
Using the MessageQueue
The public API of the MessageQueue
object is:
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);
يمكن استخدام availableToWrite()
و availableToRead()
لتحديد مقدار البيانات التي يمكن نقلها في عملية واحدة. في قائمة انتظار غير متزامنة:
- تُرجع
availableToWrite()
دائمًا سعة قائمة الانتظار. - لكل قارئ موضع قراءة خاص به ويقوم بحساباته الخاصة لـ
availableToRead()
. - من وجهة نظر القارئ البطيء ، يُسمح لقائمة الانتظار بالتجاوز ؛ قد ينتج عن هذا
availableToRead()
إرجاع قيمة أكبر من حجم قائمة الانتظار. ستفشل القراءة الأولى بعد تجاوز السعة وتؤدي إلى تعيين موضع القراءة لذلك القارئ على قدم المساواة مع مؤشر الكتابة الحالي ، سواء تم الإبلاغ عن الفائض من خلالavailableToRead()
أم لا.
تعود أساليب read()
write()
بشكل true
إذا كان من الممكن (وتم) نقل جميع البيانات المطلوبة إلى / من قائمة الانتظار. هذه الأساليب لا تمنع ؛ إما أن ينجحوا (ويعيدوا true
) ، أو يعيدون الفشل ( false
) على الفور.
readBlocking()
و writeBlocking()
حتى يمكن إتمام العملية المطلوبة ، أو حتى انتهاء المهلة (قيمة timeOutNanos
0 تعني عدم انتهاء المهلة أبدًا).
يتم تنفيذ عمليات الحظر باستخدام كلمة علامة الحدث. بشكل افتراضي ، تقوم كل قائمة انتظار بإنشاء واستخدام كلمة العلم الخاصة بها لدعم النموذج المختصر لـ readBlocking()
و writeBlocking()
. يمكن لقوائم الانتظار المتعددة مشاركة كلمة واحدة ، بحيث يمكن لعملية ما أن تنتظر الكتابة أو القراءة لأي من قوائم الانتظار. يمكن الحصول على مؤشر إلى كلمة علامة حدث لقائمة الانتظار من خلال استدعاء getEventFlagWord()
، ويمكن استخدام هذا المؤشر (أو أي مؤشر إلى موقع ذاكرة مشتركة مناسب) لإنشاء كائن EventFlag
لتمريره إلى الشكل الطويل لـ readBlocking()
و writeBlocking()
انتظار مختلفة. تخبر معلمات readNotification
و writeNotification
البتات الموجودة في علامة الحدث التي يجب استخدامها للإشارة إلى عمليات القراءة والكتابة في قائمة الانتظار هذه. readNotification
و writeNotification
هما bitmasks 32 بت.
readBlocking()
ينتظر بتات writeNotification
؛ إذا كانت هذه المعلمة تساوي 0 ، يفشل الاستدعاء دائمًا. إذا كانت قيمة readNotification
هي 0 ، فلن تفشل المكالمة ، لكن القراءة الناجحة لن تعين أي بتات إعلام. في قائمة انتظار متزامنة ، قد يعني هذا أن استدعاء writeBlocking()
المقابل لن يتم تنشيطه أبدًا ما لم يتم تعيين البت في مكان آخر. في قائمة انتظار غير متزامنة ، لن تنتظر writeBlocking()
(لا يزال يتعين استخدامها لتعيين بت إعلام الكتابة) ، ومن المناسب للقراءات عدم تعيين أي بتات إعلام. وبالمثل ، writeblocking()
إذا كانت readNotification
0 ، وتعين الكتابة الناجحة بتات writeNotification
المحددة.
للانتظار في قوائم انتظار متعددة في وقت واحد ، استخدم طريقة wait()
الخاصة بكائن EventFlag
للانتظار على قناع بت للإعلامات. تقوم طريقة wait()
بإرجاع كلمة حالة مع وحدات البت التي تسببت في إعداد التنبيه. تُستخدم هذه المعلومات بعد ذلك للتحقق من أن قائمة الانتظار المقابلة بها مساحة كافية أو بيانات لعملية الكتابة / القراءة المطلوبة وإجراء write()
/ read()
. للحصول على إشعار بعد العملية ، استخدم استدعاء آخر EventFlag
's wake()
. لتعريف تجريد EventFlag
، ارجع إلى system/libfmq/include/fmq/EventFlag.h
.
عمليات النسخ الصفري
تأخذ واجهات برمجة التطبيقات read
/ write
/ readBlocking
writeBlocking()
مؤشرًا إلى المخزن المؤقت للإدخال / الإخراج كوسيطة وتستخدم مكالمات memcpy()
داخليًا لنسخ البيانات بين المخزن المؤقت الدائري FMQ نفسه. لتحسين الأداء ، يشتمل Android 8.0 والإصدارات الأحدث على مجموعة من واجهات برمجة التطبيقات التي توفر وصولاً مباشرًا للمؤشر إلى المخزن المؤقت الحلقي ، مما يلغي الحاجة إلى استخدام مكالمات memcpy
.
استخدم واجهات برمجة التطبيقات العامة التالية لعمليات FMQ بدون نسخ:
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
مؤشرات أساسية في المخزن المؤقت للحلقة FMQ. بعد كتابة البيانات ،commitWrite()
. يعملbeginRead
/commitRead
بنفس الطريقة. - تأخذ أساليب
beginRead
/Write
كمدخلات عدد الرسائل المراد قراءتها / كتابتها وإرجاع قيمة منطقية تشير إلى ما إذا كانت القراءة / الكتابة ممكنة. إذا كانت القراءة أو الكتابة ممكنة ، يتم ملء بنيةmemTx
أساسية يمكن استخدامها للوصول المباشر للمؤشر إلى الذاكرة المشتركة لمخزن الحلقة المؤقت. - تحتوي بنية
MemRegion
على تفاصيل حول كتلة من الذاكرة ، بما في ذلك المؤشر الأساسي (العنوان الأساسي لكتلة الذاكرة) والطول من حيثT
(طول كتلة الذاكرة من حيث النوع المحدد HIDL لقائمة انتظار الرسائل). - تحتوي بنية
MemTransaction
على هيكلينMemRegion
،first
second
كقراءة أو كتابة في المخزن المؤقت الحلقي قد يتطلب التفافًا حول بداية قائمة الانتظار. هذا يعني أن هناك حاجة إلى مؤشرين أساسيين لقراءة / كتابة البيانات في المخزن المؤقت لحلقة FMQ.
للحصول على العنوان الأساسي والطول من بنية 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
للحصول على مراجع إلى أول وثاني MemRegion
s داخل كائن MemTransaction
:
const MemRegion& getFirstRegion(); // get a reference to the first MemRegion const MemRegion& getSecondRegion(); // get a reference to the second MemRegion
مثال على الكتابة إلى FMQ باستخدام نسخة صفرية من واجهات برمجة التطبيقات:
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 }
تعتبر الطرق المساعدة التالية أيضًا جزءًا من MemTransaction
:
-
T* getSlot(size_t idx);
إرجاع مؤشر إلى الفتحةidx
داخلMemRegions
التي تعد جزءًا من كائنMemTransaction
هذا. إذا كان كائنMemTransaction
يمثل مناطق الذاكرة لقراءة / كتابة عناصر N من النوع T ، فإن النطاق الصالحidx
هو بين 0 و N-1. -
bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
اكتب عناصرnMessages
من النوع T في مناطق الذاكرة التي وصفها الكائن ، بدءًا من مؤشرstartIdx
. تستخدم هذه الطريقةmemcpy()
وليس المقصود استخدامها لعملية نسخ صفري. إذا كان كائنMemTransaction
يمثل ذاكرة لقراءة / كتابة عناصر N من النوع T ، فإن النطاق الصالحidx
الهوية يكون بين 0 و N-1. -
bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
أسلوب مساعد لقراءة عناصرnMessages
من النوع T من مناطق الذاكرة الموصوفة بواسطة الكائن بدءًا منstartIdx
. تستخدم هذه الطريقةmemcpy()
ولا يُقصد استخدامها لعملية نسخ صفري.
إرسال قائمة الانتظار عبر HIDL
على جانب الإنشاء:
- إنشاء كائن قائمة انتظار الرسائل كما هو موضح أعلاه.
- تحقق من صحة الكائن باستخدام
isValid()
. - إذا كنت ستنتظر قوائم انتظار متعددة عن طريق تمرير
EventFlag
إلى النموذج الطويلreadBlocking()
/writeBlocking()
، فيمكنك استخراج مؤشر علامة الحدث (باستخدامgetEventFlagWord()
) من كائنMessageQueue
تمت تهيئته لإنشاء العلامة ، واستخدم هذه العلامة لإنشاء كائنEventFlag
الضروري. - استخدم الأسلوب
MessageQueue
getDesc()
للحصول على كائن واصف. - في الملف
.hal
، أعط الطريقة معلمة من النوعfmq_sync
أو fmq_unsync
حيث T
هو نوع معرف HIDL مناسب. استخدم هذا لإرسال الكائن الذي تم إرجاعه بواسطةgetDesc()
إلى عملية الاستلام.
على الجانب المستقبل:
- استخدم كائن واصف لإنشاء كائن
MessageQueue
. تأكد من استخدام نفس نكهة قائمة الانتظار ونوع البيانات ، وإلا سيفشل القالب في التحويل البرمجي. - إذا قمت باستخراج علامة حدث ، فاستخرج العلامة من كائن
MessageQueue
المقابل في عملية الاستلام. - استخدم كائن
MessageQueue
لنقل البيانات.