زیرساخت فراخوانی روش راه دور (RPC) HIDL از مکانیسمهای بایندر استفاده میکند، به این معنی که تماسها شامل سربار هستند، به عملیات هسته نیاز دارند و میتوانند عملیات زمانبندی را آغاز کنند. با این حال، برای مواردی که داده ها باید بین فرآیندهایی با سربار کمتر و بدون دخالت هسته منتقل شوند، از سیستم صف پیام سریع (FMQ) استفاده می شود.
FMQ صف های پیام را با ویژگی های مورد نظر ایجاد می کند. شما می توانید یک شی MQDescriptorSync
یا MQDescriptorUnsync
را از طریق یک تماس HIDL RPC ارسال کنید و شی توسط فرآیند دریافت برای دسترسی به صف پیام استفاده می شود.
انواع صف
اندروید از دو نوع صف (معروف به طعم ها ) پشتیبانی می کند:
- صف های غیرهمگام مجاز به سرریز شدن هستند و می توانند خواننده های زیادی داشته باشند. هر خواننده باید داده ها را به موقع بخواند یا آن را از دست بدهد.
- صف های همگام سازی شده مجاز به سرریز شدن نیستند و فقط می توانند یک خواننده داشته باشند.
هر دو نوع صف مجاز به پایین آمدن نیستند (خواندن از یک صف خالی با شکست مواجه می شود) و می توانند فقط یک نویسنده داشته باشند.
صف های غیر همگام
یک صف غیرهمگام فقط یک نویسنده دارد، اما می تواند هر تعداد خواننده داشته باشد. یک موقعیت نوشتن برای صف وجود دارد. با این حال، هر خواننده موقعیت خواندن مستقل خود را پیگیری می کند.
نوشتن در صف همیشه با موفقیت انجام می شود (برای سرریز بررسی نمی شود) تا زمانی که بزرگتر از ظرفیت صف پیکربندی شده نباشد (بزرگتر از ظرفیت صف می نویسد بلافاصله با شکست مواجه می شود). از آنجایی که هر خواننده ممکن است موقعیت خواندن متفاوتی داشته باشد، به جای اینکه منتظر هر خواننده برای خواندن هر قطعه داده باشد، هر زمان که نوشته های جدید به فضا نیاز داشته باشند، داده ها از صف خارج می شوند.
خوانندگان مسئول بازیابی داده ها قبل از اینکه از انتهای صف بیفتند، هستند. خواندنی که تلاش میکند دادههای بیشتری از موجود را بخواند یا فوراً با شکست مواجه میشود (اگر مسدود نباشد) یا منتظر میماند تا دادههای کافی در دسترس باشد (در صورت مسدود شدن). خواندنی که تلاش میکند دادههای بیشتری از ظرفیت صف را بخواند، همیشه بلافاصله با شکست مواجه میشود.
اگر خواننده نتواند با نویسنده هماهنگ شود، به طوری که مقدار داده های نوشته شده و هنوز خوانده نشده توسط آن خواننده از ظرفیت صف فراتر رود، خواندن بعدی داده را بر نمی گرداند. در عوض، موقعیت خواندن خواننده را به موقعیت نوشتن به اضافه نیمی از ظرفیت بازنشانی می کند و سپس یک خطا را برمی گرداند. با این کار نیمی از بافر برای خواندن در دسترس باقی میماند و فضایی برای نوشتههای جدید ذخیره میشود تا از سرریز شدن مجدد صف بلافاصله جلوگیری شود. اگر دادههای موجود برای خواندن پس از سرریز بررسی شود اما قبل از خواندن بعدی، دادههای موجود برای خواندن بیشتر از ظرفیت صف را نشان میدهد که نشان میدهد سرریز رخ داده است. (اگر صف بین بررسی داده های موجود و تلاش برای خواندن آن داده ها سرریز شود، تنها نشانه سرریز این است که خواندن ناموفق است.)
صف های هماهنگ شده
یک صف هماهنگ دارای یک نویسنده و یک خواننده با یک موقعیت نوشتن و یک موقعیت خواندن واحد است. غیرممکن است که اطلاعات بیشتری نسبت به صف موجود در صف بنویسید یا اطلاعات بیشتری را نسبت به صف در حال حاضر بخوانید. بسته به اینکه تابع نوشتن یا خواندن مسدود یا غیرمسدود فراخوانی شود، تلاش میشود از فضای موجود یا دادهها فراتر رود، یا فوراً شکست را برمیگرداند یا تا زمانی که عملیات مورد نظر تکمیل شود، مسدود میشود. تلاش برای خواندن یا نوشتن داده های بیشتر از ظرفیت صف همیشه بلافاصله با شکست مواجه می شود.
یک 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 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)
یک شی را ایجاد و مقداردهی اولیه می کند که از عملکرد صف پیام پشتیبانی می کند. - مقداردهی اولیه
MessageQueue<T, flavor>(numElements, configureEventFlagWord)
یک شی را ایجاد و مقداردهی اولیه می کند که از عملکرد صف پیام با مسدود کردن پشتیبانی می کند. -
flavor
می تواند برای یک صف هماهنگ شدهkSynchronizedReadWrite
یا برای یک صف غیرهمگامkUnsynchronizedWrite
باشد. -
uint16_t
(در این مثال) میتواند هر نوع تعریفشده توسط HIDL باشد که شامل بافرهای تودرتو (بدون نوعstring
یاvec
)، دستهها یا رابطها نباشد. -
kNumElementsInQueue
اندازه صف را در تعداد ورودی ها نشان می دهد. اندازه بافر حافظه مشترکی که برای صف تخصیص داده می شود را تعیین می کند.
دومین شیء MessageQueue را ایجاد کنید
سمت دوم صف پیام با استفاده از یک شی MQDescriptor
به دست آمده از سمت اول ایجاد می شود. شی MQDescriptor
از طریق یک فراخوانی HIDL یا AIDL RPC به فرآیندی ارسال می شود که انتهای دوم صف پیام را نگه می دارد. MQDescriptor
حاوی اطلاعاتی در مورد صف است، از جمله:
- اطلاعاتی برای نگاشت بافر و نوشتن اشاره گر.
- اطلاعاتی برای نگاشت نشانگر خواندن (اگر صف هماهنگ باشد).
- اطلاعاتی برای نگاشت کلمه پرچم رویداد (اگر صف مسدود شده است).
- نوع شی (
<T, flavor>
) که شامل نوع تعریف شده توسط HIDL از عناصر صف و طعم صف (همگام یا غیرهمگام) می شود.
می توانید از شی MQDescriptor
برای ساختن یک شیء MessageQueue
استفاده کنید:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
پارامتر resetPointers
نشان می دهد که آیا هنگام ایجاد این شیء MessageQueue
، موقعیت های خواندن و نوشتن به 0 بازنشانی شود یا خیر. در یک صف غیرهمگام، موقعیت خواندن (که محلی برای هر شیء 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 */);
- فرم طولانی ، با شش پارامتر (شامل پرچم رویداد و بیت ماسک)، از استفاده از یک شی
EventFlag
مشترک بین چند صف پشتیبانی می کند و اجازه می دهد تا ماسک های بیت اعلان مورد استفاده را مشخص کنید. در این مورد، پرچم رویداد و بیت ماسک باید برای هر تماس خواندن و نوشتن ارائه شود.
برای فرم طولانی، میتوانید EventFlag
به طور صریح در هر فراخوان readBlocking()
و writeBlocking()
ارائه کنید. میتوانید یکی از صفها را با یک پرچم رویداد داخلی مقداردهی کنید، که سپس باید از اشیاء MessageQueue
آن صف با استفاده از getEventFlagWord()
استخراج شود و برای ایجاد یک اشیاء EventFlag
در هر فرآیند برای استفاده با سایر FMQها استفاده شود. از طرف دیگر، می توانید اشیاء EventFlag
را با هر حافظه مشترک مناسب مقداردهی اولیه کنید.
به طور کلی، هر صف باید فقط از یکی از غیر مسدود کردن، مسدود کردن فرم کوتاه، یا مسدود کردن طولانی استفاده کند. مخلوط کردن آنها خطایی نیست، اما برای به دست آوردن نتیجه مطلوب، برنامه ریزی دقیقی لازم است.
حافظه را به عنوان فقط خواندنی علامت گذاری کنید
به طور پیش فرض، حافظه مشترک دارای مجوز خواندن و نوشتن است. برای صفهای همگامنشده ( kUnsynchronizedWrite
)، نویسنده ممکن است بخواهد قبل از اینکه اشیاء MQDescriptorUnsync
را تحویل دهد، مجوزهای نوشتن را برای همه خوانندهها حذف کند. این تضمین می کند که سایر فرآیندها نمی توانند در صف بنویسند، که برای محافظت در برابر اشکالات یا رفتار بد در فرآیندهای خواننده توصیه می شود. اگر نویسنده بخواهد که خوانندگان بتوانند هر زمان که MQDescriptorUnsync
برای ایجاد سمت خواندنی صف استفاده می کنند، صف را بازنشانی کنند، آنگاه نمی توان حافظه را به عنوان فقط خواندنی علامت گذاری کرد. این رفتار پیش فرض سازنده MessageQueue
است. بنابراین اگر کاربران موجود از این صف وجود دارند، کد آنها باید تغییر کند تا صف با resetPointer=false
ساخته شود.
- Writer:
ashmem_set_prot_region
را با یک توصیفگر فایلMQDescriptor
و منطقه ای که روی فقط خواندنی تنظیم شده است (PROT_READ
) تماس بگیرید:int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
- Reader: با
resetPointer=false
صف پیام ایجاد کنید (پیشفرضtrue
است):mFmq = new (std::nothrow) MessageQueue(mqDesc, false);
از MessageQueue استفاده کنید
API عمومی شی 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);
شما می توانید از 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
بیت ماسک های 32 بیتی هستند.
readBlocking()
روی بیتهای writeNotification
منتظر میماند. اگر آن پارامتر 0 باشد، تماس همیشه ناموفق است. اگر مقدار readNotification
0 باشد، تماس با شکست مواجه نمیشود، اما خواندن موفقیتآمیز هیچ بیت اعلان را تنظیم نمیکند. در یک صف هماهنگ، این بدان معنی است که فراخوانی مربوط به writeBlocking()
هرگز بیدار نمی شود مگر اینکه بیت در جای دیگری تنظیم شده باشد. در یک صف غیر همگام، writeBlocking()
منتظر نمی ماند (هنوز باید برای تنظیم بیت اعلان نوشتن استفاده شود)، و مناسب است برای خواندن هیچ بیت اعلان تنظیم نشود. به طور مشابه، در صورتی که readNotification
0 باشد، writeblocking()
با شکست مواجه می شود و یک نوشتن موفق بیت های writeNotification
مشخص شده را تنظیم می کند.
برای منتظر ماندن روی چندین صف به طور همزمان، از روش wait()
یک شیء EventFlag
استفاده کنید تا روی یک بیت ماسک از اعلان ها منتظر بمانید. متد wait()
یک کلمه وضعیت را با بیت هایی که باعث تنظیم بیدار شدن شده اند برمی گرداند. سپس از این اطلاعات برای تأیید اینکه صف مربوطه دارای فضای کافی یا داده برای عملیات نوشتن و خواندن مورد نظر است و اجرای یک write()
و read()
غیر مسدود کننده استفاده می شود. برای دریافت اعلان عملیات، از فراخوانی دیگری با متد wake()
Object EventFlag
استفاده کنید. برای تعریف انتزاع EventFlag
، به system/libfmq/include/fmq/EventFlag.h
مراجعه کنید.
عملیات کپی صفر
متدهای read
، write
، readBlocking
و writeBlocking()
یک اشاره گر را به بافر ورودی-خروجی به عنوان آرگومان می گیرند و از فراخوانی های memcpy()
به صورت داخلی برای کپی داده ها بین همان و بافر حلقه FMQ استفاده می کنند. برای بهبود عملکرد، Android 8.0 و بالاتر شامل مجموعهای از APIها است که دسترسی اشاره گر مستقیم به بافر حلقه را فراهم میکند و نیاز به استفاده از تماسهای memcpy
را از بین میبرد.
از APIهای عمومی زیر برای عملیات 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()
commit کنید. متدهایbeginRead
وcommitRead
به همین ترتیب عمل می کنند. - متدهای
beginRead
وWrite
تعداد پیامهایی که باید خوانده و نوشته شوند را به عنوان ورودی میگیرند و یک Boolean برمیگردانند که نشان میدهد خواندن یا نوشتن امکانپذیر است. اگر خواندن یا نوشتن امکان پذیر باشد، ساختار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
در یک شی MemTransaction
:
const MemRegion& getFirstRegion(); // get a reference to the first MemRegion const MemRegion& getSecondRegion(); // get a reference to the second MemRegion
به عنوان مثال با استفاده از API های صفر کپی در 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
برای انتقال داده استفاده کنید.