אם אתה מחפש תמיכה ב-AIDL, ראה גם FMQ עם AIDL .
תשתית Call Procedure מרחוק (RPC) של HIDL משתמשת במנגנוני Binder, כלומר שיחות כרוכות בתקורה, דורשות פעולות ליבה ועשויות להפעיל פעולת מתזמן. עם זאת, במקרים בהם יש להעביר נתונים בין תהליכים עם פחות תקורה וללא מעורבות של ליבה, נעשה שימוש במערכת ה-Fast Message Queue (FMQ).
FMQ יוצר תורי הודעות עם המאפיינים הרצויים. ניתן לשלוח אובייקט MQDescriptorSync
או MQDescriptorUnsync
באמצעות שיחת HIDL RPC ולהשתמש בתהליך הקבלה כדי לגשת לתור ההודעות.
תורי הודעות מהירים נתמכים רק ב-C++ ובמכשירים שבהם פועל אנדרואיד 8.0 ומעלה.
סוגי MessageQueue
אנדרואיד תומך בשני סוגי תורים (הידועים כטעמים ):
- תורים לא מסונכרנים רשאים לעלות על גדותיהם, ויכולים להיות להם קוראים רבים; כל קורא חייב לקרוא נתונים בזמן או לאבד אותם.
- תורים מסונכרנים אינם רשאים לעלות על גדותיהם, ויכולים להיות להם קורא אחד בלבד.
שני סוגי התורים אינם רשאים להסתער (קריאה מתוך תור ריק תיכשל) ויכולים להיות בעלי כותב אחד בלבד.
לא מסונכרן
לתור לא מסונכרן יש רק כותב אחד, אבל יכול להיות כל מספר של קוראים. יש עמדת כתיבה אחת לתור; עם זאת, כל קורא עוקב אחר עמדת הקריאה העצמאית שלו.
כתיבה לתור תמיד מצליחה (לא נבדקת עבור גלישה) כל עוד הן אינן גדולות מקיבולת התור שהוגדרה (כתיבה גדולה מקיבולת התור נכשלת מיד). מכיוון שלכל קורא יתכן מיקום קריאה שונה, במקום לחכות שכל קורא יקרא כל פיסת נתונים, הנתונים רשאים ליפול מהתור בכל פעם שכתובות חדשות זקוקות למרחב.
הקוראים אחראים לאחזור נתונים לפני שהם נופלים מקצה התור. קריאה שמנסה לקרוא יותר נתונים ממה שזמינים נכשלת מיד (אם לא חוסמת) או ממתינה לדי נתונים זמינים (אם חוסמים). קריאה שמנסה לקרוא יותר נתונים מקיבולת התור תמיד נכשלת מיד.
אם קורא לא מצליח לעמוד בקצב של הכותב, כך שכמות הנתונים שנכתבו ועדיין לא נקראו על ידי אותו קורא גדולה מיכולת התור, הקריאה הבאה לא מחזירה נתונים; במקום זאת, הוא מאפס את מיקום הקריאה של הקורא כך שיהיה שווה למיקום הכתיבה האחרון ואז מחזיר כשל. אם הנתונים הזמינים לקריאה נבדקים לאחר ההצפה אך לפני הקריאה הבאה, הוא מציג יותר נתונים זמינים לקריאה מאשר קיבולת התור, מה שמציין שהתרחשה הצפה. (אם התור עולה על גדותיו בין בדיקת נתונים זמינים לניסיון לקרוא נתונים אלה, האינדיקציה היחידה לגלישה היא שהקריאה נכשלת).
סביר שקוראים של תור לא מסונכרן לא ירצו לאפס את מצביעי הקריאה והכתיבה של התור. לכן, בעת יצירת התור מהמתאר, הקוראים צריכים להשתמש בארגומנט `false` עבור הפרמטר `resetPointers`.
מסונכרן
לתור מסונכרן יש כותב אחד וקורא אחד עם עמדת כתיבה בודדת ועמדת קריאה אחת. אי אפשר לכתוב יותר נתונים ממה שיש לתור מקום או לקרוא יותר נתונים ממה שהתור מכיל כרגע. תלוי אם נקראת פונקציית הכתיבה או הקריאה החוסמת או שאינה חוסמת, ניסיונות לחרוג מהשטח הפנוי או הנתונים הפנויים או מחזירים כשל מיד או חסימה עד שניתן להשלים את הפעולה הרצויה. ניסיונות לקרוא או לכתוב יותר נתונים מקיבולת התור תמיד ייכשלו מיד.
הגדרת 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 או AIDL 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 */);
- צורה ארוכה , עם שישה פרמטרים (כולל דגל אירוע ומסיכות סיביות). תומך בשימוש באובייקט
EventFlag
משותף בין תורים מרובים ומאפשר לציין את מסכות סיביות ההתראה לשימוש. במקרה זה, יש לספק את דגל האירוע ומסיכות הסיביות לכל קריאת קריאה וכתיבה.
עבור הטופס הארוך, ניתן לספק את ה- EventFlag
במפורש בכל קריאת readBlocking()
ו- writeBlocking()
. ניתן לאתחל את אחד מהתורים עם דגל אירוע פנימי, שאותו יש לחלץ מאובייקטי MessageQueue
של אותו תור באמצעות getEventFlagWord()
ולהשתמש בו ליצירת אובייקטי EventFlag
בכל תהליך לשימוש עם FMQs אחרים. לחלופין, ניתן לאתחל את אובייקטי EventFlag
עם כל זיכרון משותף מתאים.
באופן כללי, כל תור צריך להשתמש רק באחד של חסימה לא חוסמת, חסימה קצרה או חסימה ארוכה. זו לא שגיאה לערבב אותם, אבל נדרש תכנות קפדני כדי לקבל את התוצאה הרצויה.
סימון הזיכרון כקריאה בלבד
כברירת מחדל, לזיכרון המשותף יש הרשאות קריאה וכתיבה. עבור תורים לא מסונכרנים ( kUnsynchronizedWrite
), ייתכן שהכותב ירצה להסיר הרשאות כתיבה עבור כל הקוראים לפני שהוא מחלק את האובייקטים MQDescriptorUnsync
. זה מבטיח שהתהליכים האחרים לא יכולים לכתוב לתור, מה שמומלץ להגן מפני באגים או התנהגות רעה בתהליכי הקורא. אם הכותב רוצה שהקוראים יוכלו לאפס את התור בכל פעם שהם משתמשים ב- MQDescriptorUnsync
כדי ליצור את צד הקריאה של התור, אז לא ניתן לסמן את הזיכרון כקריאה בלבד. זוהי התנהגות ברירת המחדל של הבנאי 'MessageQueue'. לכן, אם יש כבר משתמשים קיימים בתור הזה, יש לשנות את הקוד שלהם כדי לבנות את התור עם resetPointer=false
.
- כותב: התקשר ל-
ashmem_set_prot_region
עם מתאר קובץMQDescriptor
והאזור מוגדר לקריאה בלבד (PROT_READ
):int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
- קורא: צור תור הודעות עם
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()
לעולם לא תתעורר אלא אם כן ה-bit מוגדר במקום אחר. בתור לא מסונכרן, writeBlocking()
לא ימתין (עדיין יש להשתמש בו כדי להגדיר את סיביות הכתיבה), ומתאים לקריאה לא להגדיר סיביות התראה. באופן דומה, writeblocking()
ייכשל אם readNotification
הוא 0, וכתיבה מוצלחת מגדירה את סיביות writeNotification
שצוינו.
כדי להמתין על מספר תורים בו-זמנית, השתמש בשיטת wait()
של אובייקט EventFlag
כדי להמתין למסכת סיביות של התראות. המתודה wait()
מחזירה מילת סטטוס עם הביטים שגרמו ל-Wike up set. מידע זה משמש לאחר מכן כדי לוודא שלתור המתאים יש מספיק מקום או נתונים עבור פעולת הכתיבה/קריאה הרצויה ולבצע write()
/ read()
. כדי לקבל הודעה לאחר הפעולה, השתמש בקריאה אחרת לשיטת wake()
של EventFlag
. להגדרה של הפשטה של EventFlag
, עיין ב- system/libfmq/include/fmq/EventFlag.h
.
אפס פעולות העתקה
ממשקי ה-API של read
/ write
/ readBlocking
/ writeBlocking()
לוקחים מצביע למאגר קלט/פלט כארגומנט ומשתמשים בקריאות memcpy()
באופן פנימי כדי להעתיק נתונים בין אותו למאגר הצלצול של FMQ. כדי לשפר את הביצועים, אנדרואיד 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()
. שיטות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
הראשון והשני בתוך אובייקט MemTransaction
:
const MemRegion& getFirstRegion(); // get a reference to the first MemRegion const MemRegion& getSecondRegion(); // get a reference to the second MemRegion
דוגמה לכתיבה ל-FMQ באמצעות APIs אפס העתקה:
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 לתוך אזורי הזיכרון המתוארים על ידי האובייקט, החל מ- indexstartIdx
. שיטה זו משתמשת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
כדי להעביר נתונים.