הרחבת הליבה עם eBPF

מסנן מנות מורחב של Berkeley (eBPF) הוא מכונה וירטואלית בתוך הליבה המריץ תוכניות eBPF שסופקו על ידי המשתמש כדי להרחיב את פונקציונליות הליבה. תוכניות אלו יכולות להיות מחוברות לבדיקות או אירועים בליבה ולהשתמש בהן כדי לאסוף נתונים סטטיסטיים שימושיים של ליבה, לנטר וניפוי באגים. תוכנית נטענת לתוך הליבה באמצעות syscall bpf(2) ומסופקת על ידי המשתמש כגוש בינארי של הוראות מכונה eBPF. למערכת הבנייה של אנדרואיד יש תמיכה בהידור תוכניות C ל-eBPF באמצעות תחביר קובץ בנייה פשוט המתואר במסמך זה.

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

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

מטעין אנדרואיד BPF

במהלך האתחול של אנדרואיד, כל תוכניות ה-eBPF הממוקמות ב- /system/etc/bpf/ נטענות. תוכניות אלו הן אובייקטים בינאריים שנבנו על ידי מערכת ה-build Android מתוכניות C ומלוות בקבצי Android.bp בעץ המקור של Android. מערכת ה-build מאחסנת את האובייקטים שנוצרו ב- /system/etc/bpf , ואובייקטים אלה הופכים לחלק מתמונת המערכת.

פורמט של תוכנית אנדרואיד eBPF C

תוכנית eBPF C חייבת להיות בפורמט הבא:

#include <bpf_helpers.h>

/* Define one or more maps in the maps section, for example
 * define a map of type array int -> uint32_t, with 10 entries
 */
DEFINE_BPF_MAP(name_of_my_map, ARRAY, int, uint32_t, 10);

/* this will also define type-safe accessors:
 *   value * bpf_name_of_my_map_lookup_elem(&key);
 *   int bpf_name_of_my_map_update_elem(&key, &value, flags);
 *   int bpf_name_of_my_map_delete_elem(&key);
 * as such it is heavily suggested to use lowercase *_map names.
 * Also note that due to compiler deficiencies you cannot use a type
 * of 'struct foo' but must instead use just 'foo'.  As such structs
 * must not be defined as 'struct foo {}' and must instead be
 * 'typedef struct {} foo'.
 */

DEFINE_BPF_PROG("PROGTYPE/PROGNAME", AID_*, AID_*, PROGFUNC)(..args..) {
   <body-of-code
    ... read or write to MY_MAPNAME
    ... do other things
   >
}

LICENSE("GPL"); // or other license

איפה:

  • name_of_my_map הוא השם של משתנה המפה שלך. שם זה מודיע למטעין BPF על סוג המפה שיש ליצור ועם אילו פרמטרים. הגדרת מבנה זו מסופקת על ידי הכותרת bpf_helpers.h הכלולה.
  • PROGTYPE/PROGNAME מייצגים את סוג התוכנית ושם התוכנית. סוג התוכנית יכול להיות כל אחד מאלה המפורטים בטבלה הבאה. כאשר סוג של תוכנית אינו מופיע ברשימה, אין מוסכמות שמות קפדנית עבור התוכנית; השם רק צריך להיות ידוע לתהליך שמצרף את התוכנית.

  • PROGFUNC היא פונקציה שכאשר היא מורכבת, ממוקמת בקטע של הקובץ שנוצר.

kprobe מחבר PROGFUNC בהוראה ליבה באמצעות תשתית kprobe. PROGNAME חייב להיות השם של פונקציית הליבה הנבדקת. עיין בתיעוד ליבת kprobe למידע נוסף על kprobes.
נקודת עקיבה מחבר PROGFUNC לנקודת עקיבה. PROGNAME חייב להיות בפורמט SUBSYSTEM/EVENT . לדוגמה, קטע נקודת מעקב לצירוף פונקציות לאירועי מתג ההקשר של מתזמן יהיה SEC("tracepoint/sched/sched_switch") , כאשר sched הוא השם של תת-מערכת המעקב, ו- sched_switch הוא השם של אירוע המעקב. עיין בתיעוד ליבת אירועי המעקב לקבלת מידע נוסף על נקודות מעקב.
skfilter התוכנית מתפקדת כמסנן שקעי רשת.
לוחות זמנים התוכנית מתפקדת כמסווג תעבורת רשת.
cgroupskb, cgroupsock התוכנית פועלת בכל פעם שתהליכים ב-CGroup יוצרים שקע AF_INET או AF_INET6.

ניתן למצוא סוגים נוספים בקוד המקור של Loader .

לדוגמה, התוכנית הבאה myschedtp.c מוסיפה מידע על ה-PID האחרון של המשימה שהופעל על מעבד מסוים. תוכנית זו משיגה את מטרתה על ידי יצירת מפה והגדרת פונקציית tp_sched_switch אשר ניתן לצרף לאירוע המעקב sched:sched_switch . למידע נוסף, ראה חיבור תוכניות לנקודות מעקב .

#include <linux/bpf.h>
#include <stdbool.h>
#include <stdint.h>
#include <bpf_helpers.h>

DEFINE_BPF_MAP(cpu_pid_map, ARRAY, int, uint32_t, 1024);

struct switch_args {
    unsigned long long ignore;
    char prev_comm[16];
    int prev_pid;
    int prev_prio;
    long long prev_state;
    char next_comm[16];
    int next_pid;
    int next_prio;
};

DEFINE_BPF_PROG("tracepoint/sched/sched_switch", AID_ROOT, AID_SYSTEM, tp_sched_switch)
(struct switch_args *args) {
    int key;
    uint32_t val;

    key = bpf_get_smp_processor_id();
    val = args->next_pid;

    bpf_cpu_pid_map_update_elem(&key, &val, BPF_ANY);
    return 1; // return 1 to avoid blocking simpleperf from receiving events
}

LICENSE("GPL");

המאקרו LICENSE משמש כדי לוודא אם התוכנית תואמת לרישיון הליבה כאשר התוכנית עושה שימוש בפונקציות מסייעות של BPF המסופקות על ידי הליבה. ציין את שם הרישיון של התוכנית שלך בצורת מחרוזת, כגון LICENSE("GPL") או LICENSE("Apache 2.0") .

פורמט הקובץ Android.bp

כדי שמערכת ה-Build של Android תבנה תוכנית eBPF .c , עליך ליצור ערך בקובץ Android.bp של הפרויקט. לדוגמה, כדי לבנות תוכנית eBPF C בשם bpf_test.c , בצע את הערך הבא בקובץ Android.bp של הפרויקט שלך:

bpf {
    name: "bpf_test.o",
    srcs: ["bpf_test.c"],
    cflags: [
        "-Wall",
        "-Werror",
    ],
}

ערך זה מרכיב את תוכנית C וכתוצאה מכך האובייקט /system/etc/bpf/bpf_test.o . בעת האתחול, מערכת אנדרואיד טוענת אוטומטית את תוכנית bpf_test.o לתוך הליבה.

קבצים זמינים ב-sysfs

במהלך האתחול, מערכת אנדרואיד טוענת אוטומטית את כל אובייקטי ה-eBPF מ- /system/etc/bpf/ , יוצרת את המפות שהתוכנית צריכה, ומצמידה את התוכנית הנטענת עם המפות שלה למערכת הקבצים BPF. לאחר מכן ניתן להשתמש בקבצים אלה להמשך אינטראקציה עם תוכנית eBPF או קריאת מפות. סעיף זה מתאר את המוסכמות המשמשות למתן שמות לקבצים אלה ואת מיקומם ב-sysfs.

הקבצים הבאים נוצרים ומוצמדים:

  • עבור כל תוכנה שנטענת, בהנחה ש- PROGNAME הוא שם התוכנית ו- FILENAME הוא השם של קובץ eBPF C, טוען Android יוצר ומצמיד כל תוכנית ב- /sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME .

    לדוגמה, עבור הדוגמה הקודמת sched_switch tracepoint ב- myschedtp.c , נוצר קובץ תוכנית ומוצמד אל /sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch .

  • עבור כל מפות שנוצרו, בהנחה ש- MAPNAME הוא שם המפה ו- FILENAME הוא השם של קובץ eBPF C, טוען Android יוצר ומצמיד כל מפה ל- /sys/fs/bpf/map_FILENAME_MAPNAME .

    לדוגמה, עבור הדוגמה הקודמת של נקודת המעקב sched_switch ב- myschedtp.c , נוצר קובץ מפה ומוצמד ל- /sys/fs/bpf/map_myschedtp_cpu_pid_map .

  • bpf_obj_get() בספריית Android BPF מחזיר מתאר קובץ מהקובץ /sys/fs/bpf המוצמד. ניתן להשתמש במתאר קובץ זה לפעולות נוספות, כגון קריאת מפות או חיבור תוכנית לנקודת עקיבה.

ספריית BPF של אנדרואיד

ספריית Android BPF נקראת libbpf_android.so והיא חלק מתמונת המערכת. ספרייה זו מספקת למשתמש פונקציונליות eBPF ברמה נמוכה הדרושה ליצירה ולקריאת מפות, יצירת בדיקות, נקודות עקיבה ומאגרי התאמה.

צירוף תוכניות לנקודות עקיבה

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

  1. התקשר bpf_obj_get() כדי להשיג את התוכנית fd ממיקום הקובץ המוצמד. למידע נוסף, עיין בקבצים הזמינים ב-sysfs .
  2. התקשר bpf_attach_tracepoint() בספריית BPF, והעביר לה את התוכנית fd ואת שם נקודת העקיבה.

דוגמת הקוד הבאה מראה כיצד לצרף את נקודת העקיבה sched_switch שהוגדרה בקובץ המקור הקודם myschedtp.c (בדיקת שגיאות לא מוצגת):

  char *tp_prog_path = "/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch";
  char *tp_map_path = "/sys/fs/bpf/map_myschedtp_cpu_pid";

  // Attach tracepoint and wait for 4 seconds
  int mProgFd = bpf_obj_get(tp_prog_path);
  int mMapFd = bpf_obj_get(tp_map_path);
  int ret = bpf_attach_tracepoint(mProgFd, "sched", "sched_switch");
  sleep(4);

  // Read the map to find the last PID that ran on CPU 0
  android::bpf::BpfMap<int, int> myMap(mMapFd);
  printf("last PID running on CPU %d is %d\n", 0, myMap.readValue(0));

קריאה מהמפות

מפות BPF תומכות במבני מפתח וערכים מורכבים שרירותיים. ספריית BPF של אנדרואיד כוללת מחלקת android::BpfMap שעושה שימוש בתבניות C++ ליצירת BpfMap בהתבסס על המפתח והסוג של המפה המדוברת. דגימת הקוד הקודמת מדגימה שימוש ב- BpfMap עם מפתח וערך כמספרים שלמים. המספרים השלמים יכולים להיות גם מבנים שרירותיים.

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

למידע נוסף על BpfMap , עיין במקורות אנדרואיד .

בעיות באגים

במהלך זמן האתחול, נרשמות מספר הודעות הקשורות לטעינת BPF. אם תהליך הטעינה נכשל מסיבה כלשהי, הודעת יומן מפורטת מסופקת ב-logcat. סינון יומני ה-logcat לפי "bpf" מדפיס את כל ההודעות וכל שגיאה מפורטת במהלך זמן הטעינה, כגון שגיאות eBPF מאמת.

דוגמאות של eBPF באנדרואיד

התוכניות הבאות ב-AOSP מספקות דוגמאות נוספות לשימוש ב-eBPF:

  • תוכנית netd eBPF C משמשת את דמון הרשת (netd) באנדרואיד למטרות שונות כגון סינון שקעים ואיסוף סטטיסטיקות. כדי לראות כיצד נעשה שימוש בתוכנית זו, בדוק את מקורות מעקב התעבורה eBPF .

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

  • באנדרואיד 12, תוכנית gpu_mem eBPF C עוקבת אחר השימוש הכולל בזיכרון GPU עבור כל תהליך ועבור המערכת כולה. תוכנית זו משמשת לפרופיל זיכרון GPU.