שפת הגדרה לבניית ממשק HAL (HIDL) היא שפת תיאור ממשק (IDL) שמשמשת לציון הממשק בין HAL לבין המשתמשים שלו. HIDL מאפשר לציין סוגים קריאות ל-method, שנאספים בממשקים ובחבילות. באופן כללי, HIDL היא מערכת לתקשורת בין בסיסים של קוד שאפשר לקמפל בנפרד.
HIDL מיועד לשימוש בתקשורת בין תהליכים (IPC). ממשקי HAL שנוצרו באמצעות HDL נקראים ממשקי HAL מצורפים (binderized HALs), כי הם יכולים לתקשר עם שכבות אחרות בארכיטקטורה באמצעות קריאות IPC (תקשורת בין תהליכים) של ה-binder. ממשקי HAL שמצורפים ל-Binder פועלים בתהליך נפרד מהלקוח שמשתמש בהם. בספריות שצריך לקשר לתהליך, זמין גם מצב העברה (אין תמיכה ב-Java).
ב-HIDL מצוינים מבני נתונים וחתימות של שיטות, שמאורגנים בממשקים (בדומה לכיתה) שנאספים בחבילות. תחביר ה-HIDL נראה מוכר למתכנתים של C++ ו-Java, אבל עם קבוצה שונה של מילות מפתח. ב-HIDL נעשה שימוש גם בהערות בסגנון Java.
טרמינולוגיה
בקטע הזה נעשה שימוש במונחים הבאים שקשורים ל-HIDL:
ב-binder | המשמעות היא ש-HIDL משמש לקריאות לפרוצדורות מרחוק בין תהליכים, שמוטמעות באמצעות מנגנון שדומה ל-Binder. מידע נוסף זמין במאמר עובר. |
---|---|
קריאה חוזרת, אסינכרוני | ממשק שמשתמש ב-HAL מספק, מועבר ל-HAL (באמצעות שיטת HIDL) ו-HAL קורא לו כדי להחזיר נתונים בכל שלב. |
קריאה חוזרת, סינכרונית | הפונקציה מחזירה ללקוח נתונים מהטמעת שיטת HIDL של השרת. לא בשימוש בשיטות שמחזירות void או ערך פרימיטיבי יחיד. |
לקוח | תהליך שמפעיל שיטות של ממשק מסוים. תהליך HAL או Android framework יכול להיות לקוח של ממשק אחד ושרת של ממשק אחר. למידע נוסף, ראו ערוץ העברה. |
extends | מציין ממשק שמוסיף שיטות ו/או סוגים לממשק אחר. ממשק יכול להרחיב רק ממשק אחד נוסף. אפשר להשתמש בהם כדי להגדיל גרסה משנית באותו שם חבילה, או כדי ליצור חבילה חדשה (למשל, תוסף של ספק) שמבוססת על חבילה ישנה יותר. |
יוצר | מציין שיטת ממשק שמחזירה ערכים ללקוח. כדי להחזיר ערך לא פרימיטיבי אחד או יותר ערכים, נוצרת פונקציית קריאה חוזרת אסינכרונית. |
ממשק | אוסף של שיטות וסוגים. מתורגם לכיתה ב-C++ או ב-Java. כל השיטות בממשק מופעלות באותו כיוון: תהליך לקוח מפעיל שיטות שהוגדרו בתהליך שרת. |
oneway | כשהיא חלה על שיטת HIDL, היא מציינת שהשיטה לא מחזירה ערכים ולא חוסמת. |
חבילה | אוסף של ממשקים וסוגי נתונים שיש להם אותה גרסה. |
העברת סיגנל ללא שינוי | מצב של HIDL שבו השרת הוא ספרייה משותפת, dlopen ed
על ידי הלקוח. במצב העברה ישירה, הלקוח והשרת הם אותו תהליך, אבל יש להם בסיס קוד נפרד. משמש רק להעברת קודי מקור מדור קודם למודל HIDL.
אפשר לעיין גם במאמר קישור קבצים. |
שרת | תהליך שמטמיע שיטות של ממשק. למידע נוסף, ראו ערוץ העברה. |
תחבורה | תשתית HIDL להעברת נתונים בין השרת ללקוח. |
גרסה | גרסת החבילה. מורכב משני מספרים שלמים, ראשי ומשני. במהדורות משניות יכולים להתווסף סוגי שיטות (אבל לא לשנות אותן). |
עיצוב HIDL
מטרת HIDL היא לאפשר החלפה של מסגרת Android בלי צורך לבנות מחדש את ה-HALs. ספקים או יצרני SoC יוצרים את ה-HALs ומכניסים אותם למחיצה /vendor
במכשיר. כך אפשר להחליף את מסגרת Android, במחיצה משלה, באמצעות עדכון OTA בלי לבצע הידור מחדש של ה-HALs.
בעיצוב של HIDL יש איזון בין הבעיות הבאות:
- יכולת פעולה הדדית. יצירת ממשקים אמינים עם יכולת פעולה הדדית בין תהליכים שאפשר לקמפל באמצעות ארכיטקטורות, כלי פיתוח ותצורות build שונות. לממשקי HIDL יש גרסאות, ולא ניתן לשנות אותם אחרי הפרסום.
- יעילות. HIDL מנסה לצמצם את מספר פעולות ההעתקה. נתונים שהוגדרו ב-HIDL מועברים לקוד C++ במבני נתונים בפריסה רגילה של C++, שאפשר להשתמש בהם בלי ביטול האריזה. HIDL מספק גם ממשקי זיכרון משותף, ומכיוון שקריאות RPC איטיות מטבען, HIDL תומך בשתי דרכים להעברת נתונים בלי להשתמש בקריאת RPC: זיכרון משותף ו-Fast Message Queue (FMQ).
- אינטואיטיבי. כדי להימנע מבעיות מורכבות של בעלות על זיכרון, ב-HIDL נעשה שימוש רק בפרמטרים מסוג
in
ל-RPC (ראו Android Interface Definition Language (AIDL)). ערכים שלא ניתן להחזיר ביעילות מה-methods מוחזרים באמצעות פונקציות קריאה חוזרת. העברת נתונים ל-HIDL להעברה או קבלת נתונים מ-HIDL לא משנים את הבעלות על הנתונים – הבעלות תמיד נשארת בפונקציה הקוראת. הנתונים צריכים להישמר רק למשך זמן הפעולה של הפונקציה שנקראת, וניתן למחוק אותם מיד אחרי שהפונקציה שנקראת חוזרת.
שימוש במצב העברה ישירה
כדי לעדכן מכשירי Android בגרסאות קודמות ל-Android O, אפשר לעטוף את ממשקי ה-HAL הרגילים (והקודמים) בממשק HIDL חדש שמציג את ה-HAL במצבים של קישור (binder) ובאותו תהליך (passthrough). האריזה הזו שקופה גם ל-HAL וגם למסגרת Android.
מצב העברה זמין רק ללקוחות ולתרחישי הטמעה של C++. במכשירים עם גרסאות קודמות של Android אין ממשקי HAL שנכתבו ב-Java, ולכן ממשקי HAL של Java הם בעצם מצורפים.
קובצי כותרות של העברה ישירה
כשקובץ .hal
עובר הידור, hidl-gen
יוצר קובץ כותרת נוסף של העברה BsFoo.h
בנוסף לכותרות שמשמשות לתקשורת של ה-binder. הכותרת הזו מגדירה את הפונקציות שצריך dlopen
. מאחר ש-HALs של העברה פועלים באותו תהליך שבו הם נקראים, ברוב המקרים שיטות העברה מופעלות על ידי קריאה ישירה לפונקציה (באותו חוט). שיטות oneway
פועלות בשרשור משלהם, כי הן לא נועדו להמתין לעיבוד שלהן על ידי HAL (כלומר, כל HAL שמשתמש בשיטות oneway
במצב העברה חייב להיות בטוח לשרשור).
כשמקבלים IFoo.hal
, ה-BsFoo.h
עוטף את השיטות שנוצרו על ידי HIDL כדי לספק תכונות נוספות (למשל, הפעלת עסקאות oneway
בשרשור אחר). הקובץ הזה דומה לקובץ BpFoo.h
, אבל במקום להעביר קריאות IPC באמצעות binder, הפונקציות הרצויות מופעלות ישירות. בהטמעות עתידיות של HALs יכול להיות שיהיו כמה הטמעות, כמו HAL של FooFast ו-HAL של FooAccurate. במקרים כאלה, ייווצר קובץ לכל הטמעה נוספת (למשל PTFooFast.cpp
ו-PTFooAccurate.cpp
).
הפיכת ממשקי HAL של העברה ל-Binder
אפשר להשתמש ב-binder להטמעות HAL שתומכות במצב העברה (passthrough). כשנותנים ממשק 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
הוא true, getService
מנסה לפתוח את ה-HAL רק במצב העברה (passthrough). אם הערך של getStub
הוא false, הפונקציה getService
תנסה למצוא שירות שמקושר ל-binder. אם הניסיון הזה נכשל, היא תנסה למצוא את שירות העברה (passthrough). אסור להשתמש בפרמטר getStub
אלא ב-defaultPassthroughServiceImplementation
. (מכשירים שהושקעו עם Android O הם מכשירים עם קישור מלא, ולכן אסור לפתוח שירות במצב העברה).
דקדוק HIDL
מעצם הגדרתה, שפת HIDL דומה לשפת C (אבל לא משתמשת במעבד התכנות המקדים של C). כל סימני הפיסוק שלא מתוארים בהמשך (מלבד השימוש הברור ב-=
וב-|
) הם חלק מהדקדוק.
הערה: פרטים על סגנון הקוד של HIDL מופיעים במדריך לסגנון קוד.
/** */
מציין תגובה במסמך העזרה. אפשר להחיל אותם רק על הצהרות של ערכים מסוגים, שיטות, שדות ומערכי ערכים./* */
מציין תגובה בכמה שורות.//
מציין תגובה לסוף השורה. מלבד//
, שורות חדשות הן כמו כל רווח לבן אחר.- בדוגמה הבאה של תחביר, הטקסט מ-
//
ועד סוף השורה הוא לא חלק מהתחביר, אלא תגובה עליו. [empty]
מציין שהמונח עשוי להיות ריק.- אם הערך
?
מופיע אחרי לטרל או מונח, המשמעות היא שהם אופציונליים. ...
מציין רצף שמכיל אפס או יותר פריטים עם סימני פיסוק מפרידים כפי שצוין. אין ארגומנטים וריאדיים ב-HIDL.- פסיקים מפרידים בין רכיבי הרצף.
- כל רכיב, כולל הרכיב האחרון, מסתיים בנקודות-פסיק.
- UPPERCASE הוא ביטוי לא סופי.
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