שפת ההגדרה של ממשק 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