שימוש ב-IPC של קלסר

בדף הזה מתוארים השינויים במנהל ה-binder ב-Android 8, מפורט השימוש ב-binder IPC ומפורטים כללי המדיניות הנדרשים של SELinux.

שינויים ב-binder driver

החל מגרסה 8 של Android, מסגרת Android ו-HALs מתקשרים ביניהם באמצעות binder. התקשורת הזו מגדילה באופן משמעותי את נפח התנועה ב-binder, ולכן ב-Android 8 יש כמה שיפורים שנועדו לשמור על מהירות ה-IPC ב-binder. ספקי SoC ויצרני ציוד מקורי צריכים למזג ישירות מההסתעפויות הרלוונטיות של android-4.4,‏ android-4.9 וגרסאות מתקדמות יותר של הפרויקט kernel/common.

מספר דומיינים (הקשרים) של מקשרים

Common-4.4 ואילך, כולל מקורות קוד

כדי לפצל בצורה נקייה את תעבורת הנתונים של ה-binder בין קוד של מסגרת (לא תלוי במכשיר) לבין קוד של ספק (ספציפי למכשיר), ב-Android 8 הושק הרעיון של הקשר של ה-binder. לכל הקשר של מקשר יש צומת מכשיר משלו ומנהל הקשר (השירות) משלו. אפשר לגשת למנהל ההקשר רק דרך צומת המכשיר שאליו הוא שייך. כשמעבירים צומת קישור דרך הקשר מסוים, אפשר לגשת אליו מאותו הקשר רק באמצעות תהליך אחר, וכך מבודדים לחלוטין את הדומיינים זה מזה. פרטים על השימוש מופיעים במאמרים vndbinder ו-vndservicemanager.

Scatter-gather

Common-4.4 ואילך, כולל מקורות קוד

בגרסאות קודמות של Android, כל נתון בקריאה ל-binder הועתק שלוש פעמים:

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

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

נעילה ברמת פירוט גבוהה

Common-4.4 ואילך, כולל מקורות קוד

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

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

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

ירושה של עדיפות בזמן אמת

Common-4.4 ו-common-4.9 (בקרוב ב-upstream)

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

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

שינויים במרחב המשתמש

Android 8 כולל את כל השינויים במרחב המשתמש שנדרשים כדי לעבוד עם מנהל הקישור הנוכחי בליבה המשותפת, מלבד חריג אחד: ההטמעה המקורית להשבתת ירושה של תעדוף בזמן אמת עבור /dev/binder השתמשה ב-ioctl. במהלך הפיתוח, העברנו את השליטה בירושה של תעדוף לשיטה מפורטת יותר שמוגדרת לכל מצב של קישור (ולא לכל הקשר). לכן, ה-ioctl לא נמצא בהסתעפות המשותפת של Android, אלא נשלח בליבות המשותפות שלנו.

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

חותמות SHA לליבות נפוצות

כדי לקבל את השינויים הנדרשים בנהג ה-binder, מסנכרנים עם ה-SHA המתאים:

  • Common-3.18
    cc8b90c121de ANDROID: binder: don't check prio permissions on restore.
  • Common-4.4
    76b376eac7a2 ANDROID: binder: don't check prio permissions on restore.
  • Common-4.9
    ecd972d4f9b5 ANDROID: binder: don't check prio permissions on restore.

עבודה עם IPC של מסמכים

בעבר, תהליכים של ספקים השתמשו בתקשורת בין תהליכים (IPC) של Binder כדי לתקשר. ב-Android 8, צומת המכשיר /dev/binder הופך להיות בלעדי לתהליכי המסגרת, כלומר לתהליכים של ספקים אין יותר גישה אליו. תהליכים של ספקים יכולים לגשת ל-/dev/hwbinder, אבל הם חייבים להמיר את ממשקי ה-AIDL שלהם לשימוש ב-HIDL. ספקים שרוצים להמשיך להשתמש בממשקי AIDL בין תהליכי הספק, יכולים להשתמש ב-Android עם תמיכה ב-binder IPC כפי שמתואר בהמשך. ב-Android 10, Stable AIDL מאפשר לכל התהליכים להשתמש ב-/dev/binder, תוך פתרון של הבעיות שקשורות להבטחת היציבות של HIDL ושל /dev/hwbinder. במאמר AIDL ל-HALs מוסבר איך משתמשים ב-AIDL יציב.

vndbinder

ב-Android 8 יש תמיכה בדומיין חדש של binder לשימוש בשירותי ספקים. אפשר לגשת לדומיין הזה באמצעות /dev/vndbinder במקום /dev/binder. בעקבות ההוספה של /dev/vndbinder, ל-Android יש עכשיו את שלושת הדומיינים הבאים של IPC:

דומיין IPC תיאור
/dev/binder IPC בין תהליכים של מסגרת/אפליקציה באמצעות ממשקי AIDL
/dev/hwbinder IPC בין תהליכים של מסגרת/ספק עם ממשקי HIDL
IPC בין תהליכים של ספקים עם ממשקי HIDL
/dev/vndbinder IPC בין ספקים/תהליכי ספקים באמצעות ממשקי AIDL

כדי ש-/dev/vndbinder יופיע, צריך לוודא שפריט ההגדרה של הליבה CONFIG_ANDROID_BINDER_DEVICES מוגדר ל-"binder,hwbinder,vndbinder" (זוהי ברירת המחדל בעצים הנפוצים של הליבה ב-Android).

בדרך כלל, תהליכי הספקים לא פותחים את מנהל ההתקן של ה-binder ישירות, אלא מקשרים לספריית המרחב המשותף של המשתמש libbinder, שמפתחת את מנהל ההתקן של ה-binder. הוספת שיטה ל-::android::ProcessState() בוחרת את מנהל הקישור ל-libbinder. תהליכים של ספקים צריכים לבצע קריאה לשיטה הזו לפני הקריאה ל-ProcessState, IPCThreadState, או לפני ביצוע כל קריאה ל-binder באופן כללי. כדי להשתמש ב-API, צריך להוסיף את הקריאה הבאה אחרי ה-main() של תהליך של ספק (לקוח ושרת):

ProcessState::initWithDriver("/dev/vndbinder");

vndservicemanager

בעבר, שירותי הקישור היו רשומים ב-servicemanager, שם תהליכים אחרים יכלו לאחזר אותם. ב-Android 8,‏ servicemanager משמש עכשיו אך ורק תהליכי framework ואפליקציות, ותהליכי ספקים כבר לא יכולים לגשת אליו.

עם זאת, שירותי ספקים יכולים להשתמש עכשיו ב-vndservicemanager, מופע חדש של servicemanager שמשתמש ב-/dev/vndbinder במקום ב-/dev/binder, שנוצר מאותם מקורות כמו מסגרת servicemanager. תהליכים של ספקים לא צריכים לבצע שינויים כדי לדבר עם vndservicemanager. כשתהליך של ספק פותח את ‎/dev/vndbinder, חיפושי השירותים מועברים אוטומטית אל vndservicemanager.

קובץ ה-binary של vndservicemanager נכלל בקובצי ה-makefile של המכשיר שמוגדרים כברירת מחדל ב-Android.

מדיניות SELinux

תהליכים של ספקים שרוצים להשתמש בפונקציונליות של Binder כדי לתקשר ביניהם צריכים את הדברים הבאים:

  1. גישה אל /dev/vndbinder.
  2. הקישור {transfer, call} מתחבר ל-vndservicemanager.
  3. binder_call(A, B) לכל דומיין של ספק A שרוצים לבצע קריאה לדומיין של ספק B דרך ממשק ה-Vendor Binder.
  4. הרשאה לשירותי {add, find} ב-vndservicemanager.

כדי לעמוד בדרישות 1 ו-2, משתמשים במאקרו vndbinder_use():

vndbinder_use(some_vendor_process_domain);

כדי לעמוד בדרישות 3, אפשר להשאיר את binder_call(A, B) של תהליכי הספקים A ו-B שצריכים לתקשר דרך ה-binder, ולא צריך לשנות את השם שלו.

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

פרטים על SELinux זמינים במאמר Security-Enhanced Linux ב-Android. פרטים על SELinux ב-Android 8.0 זמינים במאמר SELinux ל-Android 8.0.

שמות השירותים

בעבר, הספקים עיבד את שמות השירותים הרשומים בקובץ service_contexts והוסיפו כללים מתאימים לגישה לקובץ הזה. קובץ service_contexts לדוגמה מ-device/google/marlin/sepolicy:

AtCmdFwd                              u:object_r:atfwd_service:s0
cneservice                            u:object_r:cne_service:s0
qti.ims.connectionmanagerservice      u:object_r:imscm_service:s0
rcs                                   u:object_r:radio_service:s0
uce                                   u:object_r:uce_service:s0
vendor.qcom.PeripheralManager         u:object_r:per_mgr_service:s0

ב-Android 8, vndservicemanager טוען במקום זאת את הקובץ vndservice_contexts. שירותי ספקים שעוברים ל-vndservicemanager (וכבר נמצאים בקובץ הישן service_contexts) צריך להוסיף לקובץ החדש vndservice_contexts.

תוויות שירות

בעבר, תוויות שירות כמו u:object_r:atfwd_service:s0 היו מוגדרות בקובץ service.te. דוגמה:

type atfwd_service,      service_manager_type;

ב-Android 8, צריך לשנות את הסוג ל-vndservice_manager_type ולהעביר את הכלל לקובץ vndservice.te. דוגמה:

type atfwd_service,      vndservice_manager_type;

כללי servicemanager

בעבר, כללים העניקו לדומיינים גישה להוספה או לחיפוש של שירותים מ-servicemanager. דוגמה:

allow atfwd atfwd_service:service_manager find;
allow some_vendor_app atfwd_service:service_manager add;

ב-Android 8, כללים כאלה יכולים להישאר במקום ולהשתמש באותה כיתה. דוגמה:

allow atfwd atfwd_service:service_manager find;
allow some_vendor_app atfwd_service:service_manager add;