Extended Berkeley Packet Filter (eBPF) היא מכונה וירטואלית בליבה שמריצה תוכניות eBPF שסופקו על ידי משתמשים כדי להרחיב את הפונקציונליות של הליבה. אפשר לקשר את התוכניות האלה למכשירי מעקב או לאירועים בליבה, ולהשתמש בהן כדי לאסוף נתונים סטטיסטיים שימושיים של הליבה, לעקוב אחריה ולנתח את הבאגים שלה. התוכנה נטענת בליבה באמצעות קריאת המערכת bpf(2)
, והמשתמש מספק אותה כקובץ blob בינארי של הוראות מכונה של eBPF. מערכת ה-build של Android תומכת בתרגום של תוכניות C ל-eBPF באמצעות תחביר פשוט של קובץ build שמתואר במסמך הזה.
מידע נוסף על הארכיטקטורה והרכיבים הפנימיים של eBPF זמין בדף של Brendan Gregg בנושא eBPF.
Android כולל מעבד וספרייה של eBPF שטעון תוכניות eBPF בזמן האתחול.
Android BPF loader
במהלך האתחול של Android, כל תוכניות ה-eBPF שנמצאות ב-/system/etc/bpf/
נטענות. התוכנות האלה הן אובייקטים בינאריים שנוצרו על ידי מערכת ה-build של Android מתוכנות C, ומלווים בקובצי Android.bp
בעץ המקור של Android. מערכת ה-build שומרת את האובייקטים שנוצרו ב-/system/etc/bpf
, והם הופכים לחלק מתמונת המערכת.
הפורמט של תוכנית C ב-eBPF ל-Android
תוכנית C ב-eBPF חייבת להיות בפורמט הבא:
#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 also defines 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 | ה-hook PROGFUNC מצורף להוראה בליבה באמצעות תשתית kprobe. PROGNAME חייב להיות השם של פונקציית הליבה שנבדקת באמצעות kprobe. מידע נוסף על kprobes זמין במסמכי התיעוד של הליבה של kprobe.
|
---|---|
tracepoint | ה-hook PROGFUNC מצורף לנקודת מעקב. PROGNAME חייב להיות בפורמט SUBSYSTEM/EVENT . לדוגמה, קטע של נקודת מעקב (tracepoint) להצמדת פונקציות לאירועי מעבר הקשר של מתזמן יהיה SEC("tracepoint/sched/sched_switch") , כאשר sched הוא שם מערכת המשנה למעקב ו-sched_switch הוא שם אירוע המעקב. מידע נוסף על נקודות מעקב זמין במסמכי העזרה של הליבה בנושא אירועי מעקב.
|
skfilter | התוכנית פועלת כמסנן שקע רשת. |
schedcls | התוכנית פועלת כסיווג של תעבורת הרשת. |
cgroupskb, cgroupsock | התוכנית פועלת בכל פעם שתהליכים ב-CGroup יוצרים שקע AF_INET או AF_INET6. |
סוגי קבצים נוספים מפורטים בקוד המקור של ה-Loader.
לדוגמה, תוכנית myschedtp.c
הבאה מוסיפה מידע על מזהה המשימה (PID) האחרון שפעל ב-CPU מסוים. התוכנית הזו משיגה את המטרה שלה על ידי יצירת מפה והגדרת פונקציית 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 תיצור תוכנית .c
של eBPF, צריך ליצור רשומה בקובץ Android.bp
של הפרויקט. לדוגמה, כדי ליצור תוכנית C ב-eBPF בשם bpf_test.c
, צריך להוסיף את הערך הבא לקובץ Android.bp
של הפרויקט:
bpf { name: "bpf_test.o", srcs: ["bpf_test.c"], cflags: [ "-Wall", "-Werror", ], }
הרשומה הזו מקמפלת את תוכנית ה-C שמתקבלת מהאובייקט /system/etc/bpf/bpf_test.o
. בזמן האתחול, מערכת Android טוענת באופן אוטומטי את התוכנית bpf_test.o
לליבת המערכת.
קבצים שזמינים ב-sysfs
במהלך האתחול, מערכת Android טוענת באופן אוטומטי את כל אובייקטי ה-eBPF מ-/system/etc/bpf/
, יוצרת את המפות הנדרשות לתוכנית ומצמידה את התוכנית שהוטענה עם המפות שלה למערכת הקבצים של BPF. לאחר מכן אפשר להשתמש בקבצים האלה לצורך אינטראקציה נוספת עם תוכנית ה-eBPF או לקריאת מפות. בקטע הזה מתוארים המוסכמים לבחירת השמות של הקבצים האלה והמיקומים שלהם ב-sysfs.
הקבצים הבאים נוצרים ומצורפים:
לכל תוכנית שנטענת, בהנחה ש-
PROGNAME
הוא שם התוכנית ו-FILENAME
הוא שם קובץ ה-C של eBPF, מערך האתחול של Android יוצר ומצמיד כל תוכנית אל/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME
.לדוגמה, בדוגמה הקודמת של נקודת המעקב
sched_switch
ב-myschedtp.c
, נוצר קובץ תוכנית שמקובע ל-/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch
.לכל המפות שנוצרות, בהנחה ש-
MAPNAME
הוא שם המפה ו-FILENAME
הוא שם קובץ ה-C של eBPF, מערך האפליקציות של 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
ספריית Android BPF נקראת libbpf_android.so
והיא חלק מתמונת המערכת. הספרייה הזו מספקת למשתמש יכולות eBPF ברמה נמוכה שנדרשות ליצירה ולקריאה של מפות, ליצירה של בדיקות, נקודות מעקב ומאגרי ביצועים.
צירוף תוכניות לנקודות מעקב
תוכניות של נקודות מעקב נטענות באופן אוטומטי בזמן האתחול. אחרי הטעינה, צריך להפעיל את תוכנית נקודות המעקב לפי השלבים הבאים:
- קוראים ל-
bpf_obj_get()
כדי לקבל את התוכניתfd
מהמיקום של הקובץ המוצמד. מידע נוסף זמין במאמר קבצים שזמינים ב-sysfs. - קוראים ל-
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 תומכות במבנים או בסוגים מורכבים שרירותיים של מפתחות וערכים. ספריית Android BPF כוללת את הכיתה android::BpfMap
שמשתמשת בתבניות C++ כדי ליצור מופע של BpfMap
על סמך סוג המפתח והערך של המפה הרלוונטית. בדוגמת הקוד הקודמת מוצג שימוש ב-BpfMap
עם מפתח
וערך כמספרים שלמים. המספרים השלמים יכולים להיות גם מבנים שרירותיים.
כך, באמצעות הכיתה BpfMap
בתבנית אפשר להגדיר אובייקט BpfMap
בהתאמה אישית שמתאים למפה הספציפית. לאחר מכן תוכלו לגשת למפה באמצעות הפונקציות שנוצרו בהתאמה אישית, שהן מותאמות לסוגים, וכך לקבל קוד נקי יותר.
מידע נוסף על BpfMap
זמין במקורות של Android.
לנפות באגים
במהלך תהליך האתחול, נרשמות ביומן כמה הודעות שקשורות לטעינה של BPF. אם תהליך הטעינה נכשל מסיבה כלשהי, תוצג הודעת יומן מפורטת ב-logcat. סינון היומנים של logcat לפי bpf
מדפיס את כל ההודעות ואת כל השגיאות המפורטות במהלך זמן הטעינה, כמו שגיאות של מאמת eBPF.
דוגמאות ל-eBPF ב-Android
בתוכניות הבאות ב-AOSP יש דוגמאות נוספות לשימוש ב-eBPF:
תוכנית ה-C של eBPF
netd
משמשת את הדימון של הרשת (netd) ב-Android למטרות שונות, כמו סינון שקעים ואיסוף נתונים סטטיסטיים. כדי לראות איך משתמשים בתוכנית הזו, תוכלו לעיין במקורות של eBPF traffic monitor.התוכנית של eBPF C
time_in_state
מחשבת את משך הזמן שבו אפליקציית Android נמצאת בתדרים שונים של מעבד, ומשמשת לחישוב צריכת האנרגיה.ב-Android 12, תוכנית ה-C של eBPF
gpu_mem
עוקבת אחרי השימוש הכולל בזיכרון ה-GPU לכל תהליך ולמערכת כולה. התוכנית הזו משמשת ליצירת פרופיל של זיכרון ה-GPU.