שירותים והעברת נתונים

בדף הזה נסביר איך לרשום ולגלות שירותים, ואיך לשלוח נתונים לשירות באמצעות קריאה לשיטות שמוגדרות בממשקים בקובצי .hal.

רישום שירותים

אפשר לרשום שרתי ממשק HIDL (אובייקטים שמטמיעים את הממשק) כשירותים עם שם. השם הרשום לא חייב להיות קשור לממשק או לשם החבילה. אם לא צוין שם, המערכת תשתמש בשם 'default'. צריך להשתמש בשם הזה ל-HALs שלא צריך לרשום שתי הטמעות של אותו ממשק. לדוגמה, הקריאה ב-C++ לרישום השירות שמוגדרת בכל ממשק היא:

status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service");  // if needed

הגרסה של ממשק HIDL כלולה בממשק עצמו. הוא משויך באופן אוטומטי לרישום השירות, וניתן לאחזר אותו באמצעות קריאה ל-method (android::hardware::IInterface::getInterfaceVersion()) בכל ממשק HIDL. אין צורך לרשום אובייקטים של שרת, וניתן להעביר אותם דרך פרמטרים של שיטות HIDL לתהליך אחר שמבצע קריאות לשיטות HIDL בשרת.

חיפוש שירותים

בקשות מקוד הלקוח נשלחות לממשק נתון לפי שם ורמה, באמצעות קריאה ל-getService בכיתה הרצויה של HAL:

// C++
sp<V1_1::IFooService> service = V1_1::IFooService::getService();
sp<V1_1::IFooService> alternateService = V1_1::IFooService::getService("another_foo_service");
// Java
V1_1.IFooService service = V1_1.IFooService.getService(true /* retry */);
V1_1.IFooService alternateService = V1_1.IFooService.getService("another", true /* retry */);

כל גרסה של ממשק HIDL נחשבת כממשק נפרד. לכן, אפשר לרשום את IFooService בגרסה 1.1 ואת IFooService בגרסה 2.2 בתור 'foo_service', ו-getService("foo_service") בכל ממשק יקבל את השירות הרשום לממשק הזה. לכן, ברוב המקרים אין צורך לספק פרמטר שם לרישום או לגילוי (כלומר שם 'ברירת מחדל').

אובייקט ממשק הספק גם משפיע על שיטת התעבורה של הממשק המוחזר. בממשק IFoo בחבילה android.hardware.foo@1.0, הממשק שמוחזר על ידי IFoo::getService תמיד משתמש בשיטת התעבורה שמוגדרת ל-android.hardware.foo במניפסט המכשיר, אם הרשומה קיימת. אם שיטת התעבורה לא זמינה, המערכת מחזירה את הערך nullptr.

במקרים מסוימים, יכול להיות שתצטרכו להמשיך מיד גם בלי לקבל את השירות. זה יכול לקרות (לדוגמה) כשלקוח רוצה לנהל את ההתראות של השירות בעצמו, או בתוכנית אבחון (כמו atrace) שצריכה לקבל את כל hwservices ולאחזר אותם. במקרה כזה, מוצעים ממשקי API נוספים, כמו tryGetService ב-C++‎ או getService("instance-name", false) ב-Java. צריך להשתמש גם ב-API הקודם getService שסופק ב-Java עם התראות על שירותים. השימוש ב-API הזה לא מונע את מצב מרוץ שבו שרת מתעדכן אחרי שהלקוח מבקש זאת באמצעות אחד מממשקי ה-API האלה ללא ניסיון חוזר.

התראות על סגירת שירותים

לקוחות שרוצים לקבל התראה כששירות מסוים יוצא משימוש יכולים לקבל התראות על כך מהמסגרת. כדי לקבל התראות, הלקוח צריך:

  1. יוצרים תת-מחלקה של הכיתה/הממשק של HIDL‏ hidl_death_recipient (בקוד C++‎, לא ב-HIDL).
  2. משנים את השיטה serviceDied().
  3. יוצרים אובייקט של תת-הסוג hidl_death_recipient.
  4. קוראים ל-method‏ linkToDeath() בשירות שרוצים לעקוב אחריו, ומעבירים את אובייקט הממשק של IDeathRecipient. חשוב לזכור שהשיטה הזו לא מקבלת בעלות על נמען הדיווח על פטירה או על שרת ה-proxy שבו היא מופעלת.

דוגמה לפסאודו-קוד (C++‎ ו-Java דומים):

class IMyDeathReceiver : hidl_death_recipient {
  virtual void serviceDied(uint64_t cookie,
                           wp<IBase>& service) override {
    log("RIP service %d!", cookie);  // Cookie should be 42
  }
};
....
IMyDeathReceiver deathReceiver = new IMyDeathReceiver();
m_importantService->linkToDeath(deathReceiver, 42);

אפשר לרשום את אותו נמען הודעה על פטירה בכמה שירותים שונים.

העברת נתונים

אפשר לשלוח נתונים לשירות על ידי קריאה לשיטות שמוגדרות בממשקים בקובצי .hal. יש שני סוגים של שיטות:

  • שיטות חסימת ממתינות עד שהשרת ייצור תוצאה.
  • שיטות Oneway שולחות נתונים בכיוון אחד בלבד ולא חוסמות. אם כמות הנתונים בתנועה בקריאות RPC חורגת ממגבלות ההטמעה, יכול להיות שהקריאות ייחסמו או יחזירו הודעת שגיאה (ההתנהגות עדיין לא נקבעה).

שיטה שלא מחזירה ערך אבל לא מוצהרת בתור oneway עדיין חוסמת.

כל השיטות שמוצהרות בממשק HIDL נקראות בכיוון אחד, מה-HAL או ל-HAL. הממשק לא מציין את הכיוון שבו הוא נקרא. בארכיטקטורות שבהן הקריאות צריכות להגיע מ-HAL, צריך לספק שני ממשקים (או יותר) בחבילת ה-HAL ולספק את הממשק המתאים מכל תהליך. המילים לקוח ושרת משמשות בהתאם לכיוון הקריאה של הממשק (כלומר, ה-HAL יכול להיות שרת של ממשק אחד ולקוח של ממשק אחר).

קריאות חזרה (callbacks)

המילה callback מתייחסת לשני מושגים שונים, שמתחלקים לקריאה חוזרת אסינכררונית ולקריאה חוזרת סינכרונית.

קריאות חזרה אסינכרניות משמשות בשיטות HIDL מסוימות שמחזירות נתונים. שיטת HIDL שמחזירה יותר מערך אחד (או מחזירה ערך אחד מסוג לא פרימיטיבי) מחזירה את התוצאות שלה באמצעות פונקציית קריאה חוזרת. אם מוחזר רק ערך אחד והוא מסוג פרימיטיבי, לא נעשה שימוש ב-callback והערך מוחזר מהשיטה. השרת מטמיע את השיטות של HIDL והלקוח מטמיע את הפונקציות החוזרות (callbacks).

קריאות חזרה אסינכרוניות מאפשרות לשרת של ממשק HIDL להתחיל שיחות. כדי לעשות זאת, מעבירים מופע של ממשק שני דרך הממשק הראשון. הלקוח של הממשק הראשון צריך לפעול בתור השרת של הממשק השני. השרת של הממשק הראשון יכול לקרוא לשיטות באובייקט הממשק השני. לדוגמה, הטמעת HAL יכולה לשלוח מידע באופן אסינכרוני חזרה לתהליך שמשתמש בו, על ידי קריאה לשיטות באובייקט ממשק שנוצר על ידי התהליך הזה ומשמש אותו. שיטות בממשקים שמשמשות להתקשרות חזרה אסינכררונית יכולות להיות חסימות (ויכולות להחזיר ערכים למבצע הקריאה) או oneway. לדוגמה, אפשר לעיין בקטע 'קריאות חזרה אסינכרוניות' במאמר HIDL C++.

כדי לפשט את הבעלות על הזיכרון, קריאות ל-method וקריאות חוזרות (callbacks) מקבלות רק פרמטרים מסוג in ולא תומכות בפרמטרים מסוג out או inout.

מגבלות לעסקה

אין הגבלות לכל עסקה על כמות הנתונים שנשלחים בשיטות ובקריאות החוזרות של HIDL. עם זאת, קריאות שמגיעות ליותר מ-4KB לעסקה נחשבות לקריאות מוגזמות. אם מופיעה השגיאה הזו, מומלץ לתכנן מחדש את הארכיטקטורה של ממשק ה-HIDL הנתון. מגבלה נוספת היא המשאבים שזמינים לתשתית HIDL כדי לטפל בכמה עסקאות בו-זמנית. יכול להיות שיהיו מספר עסקאות בטיפול בו-זמנית בגלל מספר שרשורים או תהליכים ששולחים קריאות לתהליך, או מספר קריאות oneway שלא מטופלות במהירות על ידי התהליך המקבל. נפח האחסון הכולל המקסימלי שזמין לכל העסקאות בו-זמנית הוא 1MB כברירת מחדל.

בממשק שתוכנן היטב, לא אמורה להיות אפשרות לחרוג ממגבלות המשאבים האלה. אם כן, הקריאה שחורגת מהן יכולה להיחסם עד שהמשאבים יהיו זמינים או להעביר אות של שגיאת תעבורה. כדי להקל על ניפוי הבאגים, מתועדים ביומן כל מקרה של חריגה מהמגבלות לכל עסקה או של חריגה ממלאי המשאבים להטמעת HIDL על ידי עסקאות מצטברות בטיפול.

הטמעות של שיטות

‏HIDL יוצר קובצי כותרת שמצהירים על הסוגים, השיטות והקריאות החוזרות (callbacks) הנדרשים בשפת היעד (C++‎ או Java). אב הטיפוס של השיטות והקריאות החוזרות (callbacks) שמוגדרות ב-HIDL זהה לקוד של הלקוח ושל השרת. מערכת HIDL מספקת הטמעות proxy של השיטות בצד מבצע הקריאה, שמארגנות את הנתונים להעברה ב-IPC, וקוד stub בצד הקריאה, שמעביר את הנתונים להטמעות של המפתחים של השיטות.

למבצע הקריאה לפונקציה (שיטה של HIDL או קריאה חוזרת) יש בעלות על מבני הנתונים שהועברו לפונקציה, והוא שומר על הבעלות גם אחרי הקריאה. בכל המקרים, הגורם שאליו מועברת הקריאה לא צריך לפנות או לשחרר את האחסון.

  • ב-C++‏, הנתונים עשויים להיות לקריאה בלבד (ניסיונות לכתוב בהם עלולים לגרום לשגיאת חלוקה) והם תקפים למשך הקריאה. הלקוח יכול להעתיק את הנתונים לעומק כדי להפיץ אותם מעבר לשיחה.
  • ב-Java, הקוד מקבל עותק מקומי של הנתונים (אובייקט Java רגיל), שהוא יכול לשמור ולשנות או לאפשר איסוף אשפה.

העברת נתונים ללא RPC

ב-HIDL יש שתי דרכים להעביר נתונים בלי להשתמש בקריאה ל-RPC: זיכרון משותף ו-Fast Message Queue‏ (FMQ), ושניהם נתמכים רק ב-C++‎.

  • זיכרון משותף. הסוג המובנה של HIDL‏ memory משמש להעברת אובייקט שמייצג זיכרון משותף שהוקצה. אפשר להשתמש בו בתהליך קבלה כדי למפות את הזיכרון המשותף.
  • Fast Message Queue‏ (FMQ). ‏HIDL מספק סוג של תור הודעות לפי תבנית שמטמיע העברת הודעות ללא המתנה. הוא לא משתמש בליבה או בלוח הזמנים במצב העברה או במצב קישור (לתקשורת בין מכשירים אין את המאפיינים האלה). בדרך כלל, ה-HAL מגדיר את הקצה שלו בתור, יוצר אובייקט שאפשר להעביר דרך RPC באמצעות פרמטר של HIDL מובנה מסוג MQDescriptorSync או MQDescriptorUnsync. התהליך המקבל יכול להשתמש באובייקט הזה כדי להגדיר את הקצה השני של התור.
    • אסור שתור Sync יתמלא, ויכול להיות בו רק קורא אחד.
    • בתורים של Unsync מותר לחרוג מהקיבולת, ויכולים להיות בהם הרבה קוראים, שכל אחד מהם חייב לקרוא את הנתונים בזמן או לאבד אותם.
    אסור שיהיו פחות פריטים מאשר היכולת של אף אחד מהסוגים להכיל (קריאה מתור ריק נכשלת), וכל סוג יכול לכלול רק כותב אחד.

פרטים נוספים על FMQ זמינים במאמר Fast Message Queue ‏ (FMQ).