AIDL ליעדי HAL

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

תהליכי HAL שמשתמשים ב-AIDL כדי לתקשר בין רכיבי framework, כמו אלה הרכיב system.img ורכיבי חומרה, כמו אלה שב-vendor.img, חייבים לפעול AIDL יציב. עם זאת, כדי לתקשר בתוך מחיצה, לדוגמה, אין הגבלה על מנגנון ה-IPC שבו ניתן להשתמש עם HAL.

מוטיבציה

טכנולוגיית AIDL קיימת במשך יותר זמן מ-HIDL, ומשמשת במקומות רבים נוספים, בין רכיבי Android framework או באפליקציות. עכשיו ל-AIDL יש יציבות תמיכה, אפשר ליישם סטאק שלם עם סביבת זמן ריצה אחת של IPC. ב-AIDL יש גם מערכת ניהול גרסאות טובה יותר מ-HIDL.

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

פיתוח בהתאם לסביבת זמן הריצה של AIDL

ל-AIDL יש שלושה קצוות עורפיים שונים: Java, NDK ו-CPP. כדי להשתמש ב-Stable AIDL, צריך להשתמש תמיד בעותק המערכת של Libbinder ב-system/lib*/libbinder.so ולדבר בתאריך /dev/binder. כשמדובר בקוד בתמונת הספק, המשמעות היא ש-libbinder (מ-VNDK) לא ניתן להשתמש: בספרייה הזו יש ממשק API לא יציב של C++ פנימיים ולא יציבים. במקום זאת, קוד הספק המקורי חייב להשתמש בקצה העורפי NDK של AIDL, קישור אל libbinder_ndk (שמגובה על ידי המערכת libbinder.so), וקישור לספריות ה-NDK שנוצרו על ידי רשומות aidl_interface. עבור את שמות המודולים המדויקים, כללים למתן שמות למודולים.

כתיבת ממשק AIDL HAL

כדי להשתמש בממשק AIDL בין המערכת לספק, הממשק צריך שני שינויים:

  • לכל הגדרה של סוג צריך להוסיף הערה @VintfStability.
  • ההצהרה aidl_interface צריכה לכלול stability: "vintf",.

רק הבעלים של הממשק יכול לבצע את השינויים האלה.

כשאתם מבצעים את השינויים האלה, הממשק חייב להיות מניפסט VINTF כדי לפעול. בדיקה זו (ושאלות קשורות כמו אימות שהממשקים המשוחררים הוקפאו) באמצעות בדיקת VTS vts_treble_vintf_vendor_test. אפשר להשתמש ב-@VintfStability ללא הדרישות האלו, על ידי קריאה AIBinder_forceDowngradeToLocalStability בקצה העורפי של NDK, android::Stability::forceDowngradeToLocalStability בקצה העורפי של C++, או android.os.Binder#forceDowngradeToSystemStability בקצה העורפי של Java באובייקט של קלסר לפני שהוא נשלח לתהליך אחר. שדרוג לאחור של שירות ליציבות הספק לא נתמכת ב-Java מפני שכל האפליקציות פועלות במערכת הקשר מסוים.

בנוסף, כדי לאפשר ניידות מקסימלית של הקוד וכדי להימנע מבעיות אפשריות, בתור ספריות נוספות לא נחוצות, משביתים את הקצה העורפי של ה-CPP.

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

    aidl_interface: {
        ...
        backends: {
            cpp: {
                enabled: false,
            },
        },
    }

חיפוש ממשקי AIDL HAL

ממשקי AOSP Stable AIDL ל-HAL נמצאים באותן ספריות בסיס כמו ממשקי HIDL, ב-aidl תיקיות.

  • חומרה/ממשקים
  • frameworks/חומרה/ממשקים
  • מערכת/חומרה/ממשקים

צריך להכניס ממשקי תוספים אל hardware/interfaces אחר ספריות משנה ב-vendor או ב-hardware.

ממשקי תוספים

ב-Android יש סדרה של ממשקי AOSP רשמיים בכל גרסה. מתי ב-Android שהשותפים רוצים להוסיף פונקציונליות לממשקים האלה, הם לא אמורים לשנות ישירות, כי המשמעות היא שזמן הריצה של Android לא תואם לסביבת זמן הריצה של Android ב-AOSP. עבור מכשירי GMS, נמנעים משינויים הוא גם מה שמבטיח שהתמונה של GSI יכולה להמשיך לפעול.

תוספים יכולים להירשם בשתי דרכים שונות:

עם זאת, הרחבה רשומה כאשר היא ספציפית לספק (כלומר אינה חלק מ- ב-upstream AOSP) משתמשים בממשק, אין אפשרות למיזוג שיש התנגשות. עם זאת, כששינויים ב-downstream ברכיבי AOSP ב-upstream עלולות להתרחש התנגשויות במיזוג, ומומלץ להשתמש באסטרטגיות הבאות:

  • ניתן להעלות את השידור של התוספות לממשק ל-AOSP בגרסה הבאה
  • והוספות לממשקים שמאפשרות גמישות רבה יותר, ללא התנגשויות בלו"ז, תהיה זמינה ב-upstream בגרסה הבאה.

מתקנים לתוספים: ParcelableHolder

ParcelableHolder הוא Parcelable שיכול להכיל עוד Parcelable. התרחיש לדוגמה העיקרי של ParcelableHolder הוא ליצור Parcelable שניתן להרחיב. לדוגמה, תמונה שמטמיעי מכשירים מצפים שתהיה להם אפשרות להרחיב Parcelable, AospDefinedParcelable שמוגדר על ידי AOSP, כדי לכלול ערך מוסף לבינה מלאכותית גנרטיבית.

בעבר בלי ParcelableHolder, מטמיעי מכשירים לא יכלו לשנות ממשק AIDL יציב שמוגדר על ידי AOSP, כי זו תהיה שגיאה להוסיף עוד ממשקים שדות:

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

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

באמצעות ParcelableHolder, הבעלים של חבילה יכולים להגדיר תוסף ב-Parcelable.

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

לאחר מכן מכשירי הטמעה של המכשירים יוכלו להגדיר Parcelable משלהם לתוסף.

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

לסיום, ניתן לצרף את Parcelable החדש אל Parcelable המקורי באמצעות השדה ParcelableHolder.


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

שמות מכונות של שרתי AIDL HAL

לפי המוסכמה, לשירותי AIDL HAL יש שם של מכונה בפורמט הזה $package.$type/$instance לדוגמה, דוגמה של הרטט HAL רשום בתור android.hardware.vibrator.IVibrator/default.

כתיבה של שרת AIDL HAL

צריך להצהיר על @VintfStability שרתי AIDL במניפסט של VINTF, בשביל לדוגמה:

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

אחרת, הם צריכים לרשום שירות AIDL כרגיל. כשמפעילים VTS כל תכונות ה-AIDL HAL המוצהרות צפויות להיות זמינות.

כתיבה של לקוח AIDL

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

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

המרה של HAL קיים מ-HIDL ל-AIDL

משתמשים בכלי hidl2aidl כדי להמיר ממשק HIDL ל-AIDL.

התכונות של hidl2aidl:

  • יצירת קובצי .aidl על סמך קובצי ה-.hal של החבילה הנתונה
  • יצירת כללי build לחבילת AIDL החדשה שנוצרה עם כל הקצוות העורפיים מופעל
  • ליצור שיטות תרגום בקצה העורפי Java, CPP ו-NDK לתרגום מסוגים של HIDL לסוגי AIDL
  • יצירת כללי build לתרגום של ספריות עם יחסי תלות נדרשים
  • יוצרים הצהרות סטטיות כדי לוודא שלמספרי HIDL ו-AIDL זהים בקצוות העורפיים של CPP ו-NDK

כדי להמיר חבילה של קובצי .hal לקובצי .aidl, פועלים לפי השלבים הבאים:

  1. בניית הכלי נמצאת ב-system/tools/hidl/hidl2aidl.

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

    m hidl2aidl
    
  2. מריצים את הכלי עם ספריית פלט ואחריה החבילה שרוצים שהומר.

    אפשר להשתמש בארגומנט -l כדי להוסיף את התוכן של קובץ רישיון חדש לחלק העליון של כל הקבצים שנוצרו. צריך להקפיד להשתמש ברישיון ובתאריך הנכונים.

    hidl2aidl -o <output directory> -l <file with license> <package>
    

    לדוגמה:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. קוראים את הקבצים שנוצרו ומתקנים בעיות בהמרה.

    • בכלי conversion.log יש בעיות שלא טופלו שצריך לתקן קודם.
    • יכול להיות שב-.aidl הקבצים שנוצרו יש אזהרות והצעות נדרשת פעולה. התגובות האלה מתחילות ב-//.
    • כדאי לנצל את ההזדמנות כדי לפנות מקום ולשפר את החבילה.
    • כדאי לבדוק את @JavaDerive הערה לגבי תכונות שאולי נחוצות, כמו toString או equals
  4. בונים רק את היעדים הנחוצים.

    • השבתת קצוות עורפיים שלא יהיו בשימוש. עדיף להשתמש בקצה העורפי NDK על פני ה-CPP בקצה העורפי, ראו בחירת זמן ריצה.
    • מסירים את ספריות התרגום או כל קוד שנוצר בהן שלא יהיה בשימוש.
  5. ראו הבדלים משמעותיים ב-AIDL/HIDL.

    • השימוש ב-Status ובחריגים המובנים של AIDL בדרך כלל משפרים את ולהסיר את הצורך בסוג סטטוס אחר שספציפי לממשק.
    • כברירת מחדל, ארגומנטים של ממשק AIDL ב-method הם לא @nullable כמו שהם היו ב-HIDL.

SEPolicy ל-AIDL HALs

סוג שירות AIDL שגלוי לקוד הספק חייב לכלול מאפיין hal_service_type. אחרת, ההגדרות האישיות של המדיניות יהיו זהות כמו כל שירות AIDL אחר (למרות שיש מאפיינים מיוחדים ל-HAL). כאן היא הגדרה לדוגמה להקשר של שירות HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

ברוב השירותים שהפלטפורמה מגדירה, הקשר שירות עם מסוג כבר התווסף (לדוגמה, android.hardware.foo.IFoo/default יוסיף כבר סומן כ-hal_foo_service). אבל, אם לקוח framework תומך כמה שמות של מכונות, צריך להוסיף שמות של מכונות קובצי service_contexts ספציפיים למכשיר.

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

צריך להוסיף מאפייני HAL כשאנחנו יוצרים סוג חדש של HAL. רמת HAL ספציפית משויך לכמה סוגי שירותים (כל אחד מהם עשוי יש כמה מקרים, כמו שהסברנו קודם לכן). ל-HAL, foo, יש לנו hal_attribute(foo). המאקרו הזה מגדיר את המאפיינים hal_foo_client hal_foo_server. עבור דומיין נתון, הערכים hal_client_domain ו- פקודות המאקרו hal_server_domain משייכות דומיין למאפיין HAL נתון. עבור לדוגמה, שרת מערכת שהוא לקוח של ה-HAL הזה תואם למדיניות hal_client_domain(system_server, hal_foo) שרת HAL כולל באופן דומה hal_server_domain(my_hal_domain, hal_foo) בדרך כלל, כשמדובר ב-HAL נתון אנחנו יוצרים גם דומיין כמו hal_foo_default לעיון HAL לדוגמה. עם זאת, מכשירים מסוימים משתמשים בדומיינים האלה עבור השרתים שלהם. הבחנה בין דומיינים של שרתים מרובים חשובה רק אם יש לנו כמה שרתים שמציגים את אותו הממשק וצריכים הרשאה שונה שהוגדרו בהטמעות שלהם. בכל פקודות המאקרו האלה, hal_foo אינו למעשה אובייקט Sepolicy. במקום זאת, בפקודות המאקרו משתמשים באסימון הזה כדי להפנות קבוצת המאפיינים שמשויכים לצמד שרתי לקוח.

עם זאת, עד עכשיו לא שויכו בין hal_foo_service לבין hal_foo (צמד המאפיינים מ-hal_attribute(foo)). מתבצע שיוך של מאפיין HAL עם שירותי AIDL HAL באמצעות המאקרו hal_attribute_service (שימוש ב-HIDL HALs) מאקרו hal_attribute_hwservice). לדוגמה, hal_attribute_service(hal_foo, hal_foo_service) המשמעות היא תהליכים של hal_foo_client יכולים להשיג תקן HAL ו-hal_foo_server יכולים לרשום את האלמנט HAL. האכיפה של כללי הרישום האלה היא נעשה על ידי מנהל ההקשר (servicemanager). שימו לב, שמות של שירותים לא תמיד תואמות למאפייני HAL. לדוגמה, אנחנו עשויים לראות hal_attribute_service(hal_foo, hal_foo2_service) עם זאת, באופן כללי, הדבר מרמז על כך שהשירותים תמיד בשימוש יחד, לכן אולי כדאי להסיר hal_foo2_service ושימוש ב-hal_foo_service בכל השירותים שלנו הקשרים שונים. רוב יעדי HAL שמגדירים כמה hal_attribute_service הם כי השם של מאפיין HAL המקורי אינו כללי מספיק ואי אפשר לשנות אותו.

סיכום של כל המידע הזה, לדוגמה HAL, נראה כך:

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, hal_service_type, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_use(hal_foo_server)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    hal_server_domain(some_hal_server_domain, hal_foo)

ממשקי תוספים מצורפים

אפשר לצרף תוסף לכל ממשק של binder, לא משנה אם הוא ברמה העליונה רשום ישירות במנהל השירות, או שהוא ממשק משנה. כשמקבלים תוסף, צריך לוודא שסוג התוסף הוא מה מצופה. ניתן להגדיר תוספים רק בתהליך ההצגה של קלסר.

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

כדי להגדיר תוסף ב-binder, צריך להשתמש בממשקי ה-API הבאים:

  • בקצה העורפי של NDK: AIBinder_setExtension
  • בקצה העורפי של Java: android.os.Binder.setExtension
  • בקצה העורפי של CPP: android::Binder::setExtension
  • בקצה העורפי של Rust: binder::Binder::set_extension

כדי לקבל הארכה ב-Binder, צריך להשתמש בממשקי ה-API הבאים:

  • בקצה העורפי של NDK: AIBinder_getExtension
  • בקצה העורפי של Java: android.os.IBinder.getExtension
  • בקצה העורפי של CPP: android::IBinder::getExtension
  • בקצה העורפי של Rust: binder::Binder::get_extension

מידע נוסף על ממשקי ה-API האלה זמין במסמכי התיעוד של getExtension בקצה העורפי המתאים. דוגמה לאופן השימוש אפשר למצוא את התוספים hardware/interfaces/tests/extension/vibrator.

הבדלים משמעותיים ב-AIDL וב-HIDL

כשמשתמשים ב-AIDL HAL או בממשקי AIDL HAL, חשוב לשים לב להבדלים בהשוואה לכתיבת HIDL HAL.

  • התחביר של שפת AIDL יותר קרוב ל-Java. תחביר HIDL דומה ל-C++.
  • לכל ממשקי AIDL יש סטטוסים מובנים של שגיאות. במקום ליצור תבניות מותאמות אישית סוגי הסטטוס, ליצור אובייקטים של סטטוס קבוע בקובצי ממשק ולהשתמש EX_SERVICE_SPECIFIC בקצה העורפי של CPP/NDK ו-ServiceSpecificException בקצה העורפי של Java. להצגת שגיאה טיפול.
  • טכנולוגיית AIDL לא מפעילה מאגרי שרשורים באופן אוטומטי כשאובייקטים של קישור נשלחים. צריך להפעיל אותם באופן ידני שרשור ניהול).
  • המערכת של AIDL לא מבטלת שגיאות תעבורה לא מסומנות (HIDL Return מבטל את אם התיבה לא מסומנת).
  • ב-AIDL אפשר להצהיר רק על סוג אחד בכל קובץ.
  • אפשר לציין ארגומנטים של AIDL בתור 'in/out'/'in' בנוסף לפלט (אין 'קריאות חוזרות סינכרוניות').
  • AIDL משתמש ב-fd כסוג הראשוני במקום בכינוי.
  • HIDL משתמשת בגרסאות ראשיות לשינויים לא תואמים ובגרסאות משניות עבור ושינויים תואמים. ב-AIDL, מתבצעים שינויים שתואמים לאחור. ב-AIDL אין קונספט מפורש של גרסאות ראשיות. במקום זאת, ישולבו בשמות החבילות. לדוגמה, AIDL עשוי להשתמש בשם החבילה bluetooth2
  • כברירת מחדל, מערכת AIDL לא מקבלת עדיפות בזמן אמת. setInheritRt כדי לאפשר ירושה בעדיפות גבוהה בזמן אמת, צריך להשתמש בפונקציה לכל הקשרר.