hiDL

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

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

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

טרמינולוגיה

הקטע הזה משתמש במונחים הבאים שקשורים ל-HIDL:

מקושר מציין שנעשה שימוש ב-HIDL לביצוע קריאות פרוגרמטיות בין תהליכים, שמוטמעות באמצעות מנגנון דמוי-Binnder. כדאי לעיין גם במעבר חיצוני.
קריאה חוזרת, אסינכרונית הממשק משרת על ידי משתמש HAL, שמועבר ל-HAL (בשיטת HIDL) ונקרא על ידי HAL כדי להחזיר נתונים בכל שלב.
קריאה חוזרת, סינכרונית מחזירה נתונים מהטמעה של שיטת HIDL של שרת ללקוח. לא משתמשים בו ל-methods שמחזירות ערך מבוטל או ערך ראשוני יחיד.
לקוח תהליך שקורא ל-methods של ממשק מסוים. תהליך HAL או framework של Android יכול להיות לקוח של ממשק אחד ושרת של ממשק אחר. אפשר לקרוא גם מעבר חיצוני.
נמשך מציין ממשק שמוסיף שיטות ו/או סוגים לממשק אחר. ממשק יכול להרחיב רק ממשק אחד נוסף. אפשר להשתמש בתוספות לגרסה המשנית באותו שם חבילה, או לחבילה חדשה (למשל, תוסף ספק) כדי לבנות חבילה ישנה יותר.
יוצרת מציינת שיטת ממשק שמחזירה ערכים ללקוח. כדי להחזיר ערך לא פרימיטיבי אחד או יותר מערך אחד, נוצרת פונקציית קריאה חוזרת סנכרונית.
ממשק אוסף של שיטות וסוגים. תורגם לכיתה ב-C++ או ב-Java. לכל ה-methods בממשק יש קריאה באותו כיוון: תהליך לקוח מפעיל שיטות שמוטמעות על ידי תהליך בשרת.
כיוון אחד כשמחילים אותה על method HIDL, היא מציינת שה-method לא מחזירה ערכים ולא חוסמת.
חבילה איסוף של ממשקים וסוגי נתונים שחולקים גרסה.
העברת סיגנל ללא שינוי מצב HIDL שבו השרת הוא ספרייה משותפת, dlopenעל ידי הלקוח. במצב passthrough, הלקוח והשרת הם אותו תהליך, אבל בסיסי קוד נפרדים. משמש רק להעברת מסדי קוד מדור קודם למודל HIDL. ראו גם Binderized.
שרת תהליך שמטמיע שיטות בממשק. אפשר לקרוא גם מעבר חיצוני.
תחבורה תשתית HIDL שמעבירה נתונים בין השרת ללקוח.
גרסה גרסת החבילה. מורכב משני מספרים שלמים: ראשי וקטין. גרסאות קטנות יותר יכולות להוסיף סוגים ושיטות (אבל לא לשנות אותם).

עיצוב HIDL

המטרה של HIDL היא שאפשר להחליף את ה-framework של Android בלי שתצטרכו לבנות מחדש את HAL. רכיבי HAL ייפתחו על ידי ספקים או יצרני SOC, ויכללו מחיצת /vendor במכשיר. כך מסגרת של Android, במחיצה משלה, תוחלף ב-OTA בלי להרכיב מחדש את ה-HALs.

תכנון HIDL מאזן את החששות הבאים:

  • יכולת פעולה הדדית. יצירת ממשקים עם יכולת פעולה הדדית ואמינה בין תהליכים, שעשויים להרכיב בעזרת ארכיטקטורות שונות, שרשראות כלים והגדרות build. לממשקי HIDL יש גרסאות ואי אפשר לשנות אותם אחרי שמפרסמים אותם.
  • יעילות. HIDL מנסה למזער את מספר פעולות ההעתקה. נתונים בהגדרת HIDL נשלחים לקוד C++ במבני נתונים של פריסה רגילה של C++ , שאפשר להשתמש בהם בלי לפרוס את הנתונים באריזה. HIDL גם מספקים ממשקי זיכרון משותפים, ומכיוון ש-RPCs איטיים במידה מסוימת, HIDL תומך בשתי דרכים להעברת נתונים ללא שימוש בקריאה ל-RPC: זיכרון משותף ותור הודעות מהיר (FMQ).
  • אינטואיטיבי. HIDL נמנע מבעיות עצומות של בעלות על זיכרון על ידי שימוש בפרמטרים in בלבד ל-RPC (ראו Android Interface Definition Language (AIDL)); ערכים שאי אפשר להחזיר ביעילות מ-methods מוחזרים באמצעות פונקציות קריאה חוזרת. העברת הנתונים ל-HIDL או קבלת נתונים מ-HIDL לא משנה את הבעלות על הנתונים – הבעלות תמיד נשארת עם הפונקציה הקריאה. הנתונים צריכים להישמר רק במהלך הפונקציה שנקראה, והם עלולים להושמד מיד אחרי שהפונקציה שנקראה חוזרת.

שימוש במצב העברה

כדי לעדכן מכשירים שמותקנות בהם גרסאות קודמות של Android ל-Android O, אפשר לארוז את מכשירי ה-HAL הרגילים (והקודמים) בממשק HIDL חדש שמשרת את ה-HAL במצב קישור (bindered) ובמצב זהה (pass-pass). האריזה הזו שקופה גם ל-HAL וגם ל-framework של Android.

מצב העברת נתונים זמין רק ללקוחות ולהטמעות של C++. במכשירים שמותקנות בהם גרסאות קודמות של Android, פרוטוקול HAL לא נכתב ב-Java, ולכן ממשקי Java HAL מקושרים מטבעם.

כשמתבצע הידור של קובץ .hal, הפקודה hidl-gen יוצרת קובץ כותרת נוסף של העברה, BsFoo.h בנוסף לכותרות שמשמשות לתקשורת בין הקישורים. הכותרת הזו מגדירה פונקציות שצריך להפעיל dlopen. כש-HALs של מעבר מעבר פועלים באותו תהליך שבו הם נקראים, ברוב המקרים שיטות העברה מופעלות על ידי קריאה ישירה של פונקציה (אותו שרשור). השיטות של oneway פועלות בשרשור משלהן, כי הן לא נועדו להמתין ל-HAL כדי לעבד אותן (כלומר, כל ממשק HAL שמשתמש ב-methods של oneway במצב מעבר צריך להיות בטוח לשרשורים).

בהינתן IFoo.hal, הערך BsFoo.h כולל את ה-methods שנוצרו על ידי HIDL כדי לספק תכונות נוספות (למשל, ביצוע טרנזקציות oneway שפועלות ב-thread אחר). הקובץ הזה דומה לקובץ BpFoo.h, אבל במקום להעביר קריאות IPC באמצעות binder, הפונקציות הרצויות מופעלות ישירות. הטמעות עתידיות של HAL עשויות לספק הטמעות מרובות, כמו FooFast HAL ו-FooExact HAL. במקרים כאלה, ייווצר קובץ לכל הטמעה נוספת (למשל, PTFooFast.cpp ו-PTFooAccurate.cpp).

HAL של העברה מסוג העברת נתונים

אפשר לקשר בין הטמעות HAL שתומכות במצב העברה. כשמשתמשים בממשק HAL a.b.c.d@M.N::IFoo, נוצרות שתי חבילות:

  • a.b.c.d@M.N::IFoo-impl – מכיל את ההטמעה של HAL וחושף את הפונקציה IFoo* HIDL_FETCH_IFoo(const char* name). במכשירים מדור קודם, החבילה dlopen מופעלת וההטמעה מתחילה באמצעות HIDL_FETCH_IFoo. אפשר ליצור את קוד הבסיס באמצעות hidl-gen, -Lc++-impl ו--Landroidbp-impl.
  • a.b.c.d@M.N::IFoo-service. פותח את פרוטוקול המעבר HAL ורושם את עצמו כשירות מקושר, כך שאפשר להשתמש באותה הטמעת HAL גם כהעברה וגם כקישור.

בסוג IFoo, אפשר לקרוא ל-sp<IFoo> IFoo::getService(string name, bool getStub) כדי לקבל גישה למכונה של IFoo. אם הערך של getStub נכון, getService ינסה לפתוח את HAL רק במצב העברה. אם הערך של getStub הוא False, getService מנסה למצוא שירות מקושר. אם הפעולה נכשלת, הוא ינסה למצוא את שירות ההעברה. אסור להשתמש בפרמטר getStub אלא רק ב-defaultPassthroughServiceImplementation. (מכשירים שמופעלים באמצעות Android O הם מכשירים מקושרים לגמרי, ולכן אסור לפתוח שירות במצב מעבר).

דקדוק HIDL

שפת ה-HIDL מתוכננות באופן דומה ל-C (אבל לא נעשה בה שימוש במעבד C). כל סימני הפיסוק שלא מתוארים בהמשך (מלבד השימוש הברור ב-= וב-|) הם חלק מהדקדוק.

הערה: במדריך לסגנון קוד תוכלו לקרוא פרטים נוספים על סגנון הקוד HIDL.

  • /** */ מציינת הערה במסמך. אפשר להחיל אותן רק על הצהרות מסוג type, method, שדה ו-enum.
  • /* */ מציין תגובה לכמה שורות.
  • // מציין תגובה לסוף השורה. חוץ מ-//, שורות חדשות זהות לכל רווח אחר.
  • בדקדוק לדוגמה שבהמשך, טקסט מ-// עד סוף השורה הוא לא חלק מהדקדוק, אלא תגובה על הדקדוק.
  • המשמעות של [empty] היא שיכול להיות שהמונח ריק.
  • השימוש ב? אחרי שימוש בליטרל או במונח הוא אופציונלי.
  • ... מציין רצף שמכיל אפס פריטים או יותר עם הפרדה בין סימני פיסוק כפי שצוין. אין ארגומנטים שונים ב-HIDL.
  • פסיקים נפרדים לרכיבי רצף.
  • נקודה ופסיק מסיימים כל רכיב, כולל הרכיב האחרון.
  • כל המילים באותיות גדולות הן גדולות.
  • italics היא משפחת אסימונים כמו integer או identifier (כללים רגילים לניתוח C).
  • constexpr הוא ביטוי קבוע בסגנון C (כמו 1 + 1 ו-1L << 3).
  • import_name הוא שם חבילה או ממשק, שעומד בדרישות כפי שמתואר ב ניהול גרסאות HIDL.
  • אותיות קטנות words הן אסימונים מילוליים.

דוגמה:

ROOT =
    PACKAGE IMPORTS PREAMBLE { ITEM ITEM ... }  // not for types.hal
  | PACKAGE IMPORTS ITEM ITEM...  // only for types.hal; no method definitions

ITEM =
    ANNOTATIONS? oneway? identifier(FIELD, FIELD ...) GENERATES?;
  |  safe_union identifier { UFIELD; UFIELD; ...};
  |  struct identifier { SFIELD; SFIELD; ...};  // Note - no forward declarations
  |  union identifier { UFIELD; UFIELD; ...};
  |  enum identifier: TYPE { ENUM_ENTRY, ENUM_ENTRY ... }; // TYPE = enum or scalar
  |  typedef TYPE identifier;

VERSION = integer.integer;

PACKAGE = package android.hardware.identifier[.identifier[...]]@VERSION;

PREAMBLE = interface identifier EXTENDS

EXTENDS = <empty> | extends import_name  // must be interface, not package

GENERATES = generates (FIELD, FIELD ...)

// allows the Binder interface to be used as a type
// (similar to typedef'ing the final identifier)
IMPORTS =
   [empty]
  |  IMPORTS import import_name;

TYPE =
  uint8_t | int8_t | uint16_t | int16_t | uint32_t | int32_t | uint64_t | int64_t |
 float | double | bool | string
|  identifier  // must be defined as a typedef, struct, union, enum or import
               // including those defined later in the file
|  memory
|  pointer
|  vec<TYPE>
|  bitfield<TYPE>  // TYPE is user-defined enum
|  fmq_sync<TYPE>
|  fmq_unsync<TYPE>
|  TYPE[SIZE]

FIELD =
   TYPE identifier

UFIELD =
   TYPE identifier
  |  safe_union identifier { FIELD; FIELD; ...} identifier;
  |  struct identifier { FIELD; FIELD; ...} identifier;
  |  union identifier { FIELD; FIELD; ...} identifier;

SFIELD =
   TYPE identifier
  |  safe_union identifier { FIELD; FIELD; ...};
  |  struct identifier { FIELD; FIELD; ...};
  |  union identifier { FIELD; FIELD; ...};
  |  safe_union identifier { FIELD; FIELD; ...} identifier;
  |  struct identifier { FIELD; FIELD; ...} identifier;
  |  union identifier { FIELD; FIELD; ...} identifier;

SIZE =  // Must be greater than zero
     constexpr

ANNOTATIONS =
     [empty]
  |  ANNOTATIONS ANNOTATION

ANNOTATION =
  |  @identifier
  |  @identifier(VALUE)
  |  @identifier(ANNO_ENTRY, ANNO_ENTRY  ...)

ANNO_ENTRY =
     identifier=VALUE

VALUE =
     "any text including \" and other escapes"
  |  constexpr
  |  {VALUE, VALUE ...}  // only in annotations

ENUM_ENTRY =
     identifier
  |  identifier = constexpr