Extended Berkeley Packet Filter (eBPF) คือเครื่องเสมือนในเคอร์เนลที่เรียกใช้โปรแกรม eBPF ที่ผู้ใช้จัดหาให้เพื่อขยายฟังก์ชันการทำงานของเคอร์เนล โดยโปรแกรมเหล่านี้สามารถเชื่อมต่อกับโพรบหรือเหตุการณ์ในเคอร์เนล และใช้เพื่อรวบรวมสถิติเคอร์เนลที่เป็นประโยชน์ ตรวจสอบ และแก้ไขข้อบกพร่อง ระบบจะโหลดโปรแกรมลงในเคอร์เนลโดยใช้ syscall bpf(2) และผู้ใช้จะจัดหาโปรแกรมในรูปแบบไบนารีบล็อบของคำสั่งเครื่อง eBPF ระบบบิลด์ของ Android รองรับการคอมไพล์โปรแกรม C เป็น eBPF โดยใช้ไวยากรณ์ไฟล์บิลด์อย่างง่ายที่อธิบายไว้ในเอกสารนี้
ดูข้อมูลเพิ่มเติมเกี่ยวกับภายในและสถาปัตยกรรมของ eBPF ได้ที่ Brendan Gregg's eBPF page
Android มีตัวโหลดและไลบรารี eBPF ที่โหลดโปรแกรม eBPF ในเวลาบูต
ตัวโหลด BPF ของ Android
ระหว่างการบูต Android ระบบจะโหลดโปรแกรม eBPF ทั้งหมดที่อยู่ใน /system/etc/bpf/ โปรแกรมเหล่านี้เป็นออบเจ็กต์ไบนารีที่ระบบบิลด์ของ Android สร้างขึ้นจากโปรแกรม C และมาพร้อมกับไฟล์ Android.bp ในซอร์สทรีของ Android ระบบบิลด์จะจัดเก็บออบเจ็กต์ที่สร้างขึ้นไว้ที่ /system/etc/bpf และออบเจ็กต์เหล่านั้นจะกลายเป็นส่วนหนึ่งของอิมเมจระบบ
รูปแบบของโปรแกรม eBPF C ของ Android
โปรแกรม 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 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 | เชื่อมต่อ PROGFUNC กับคำสั่งเคอร์เนลโดยใช้โครงสร้างพื้นฐาน kprobe PROGNAME ต้องเป็นชื่อของฟังก์ชันเคอร์เนลที่กำลัง kprobe ดูข้อมูลเพิ่มเติมเกี่ยวกับ
kprobe ได้ในเอกสารประกอบเคอร์เนล kprobe
|
|---|---|
| tracepoint | เชื่อมต่อ PROGFUNC กับ tracepoint PROGNAME ต้องมี
รูปแบบ SUBSYSTEM/EVENT เช่น ส่วน tracepoint
สำหรับการแนบฟังก์ชันกับเหตุการณ์การสลับบริบทของตัวกำหนดเวลาจะเป็น
SEC("tracepoint/sched/sched_switch") โดยที่ sched คือ
ชื่อของระบบย่อยการติดตาม และ sched_switch คือชื่อ
ของเหตุการณ์การติดตาม ดูข้อมูลเพิ่มเติมเกี่ยวกับ tracepoint ได้ในเอกสารประกอบเคอร์เนลเหตุการณ์การติดตาม
|
| skfilter | ฟังก์ชันโปรแกรมเป็นตัวกรองซ็อกเก็ตเครือข่าย |
| schedcls | ฟังก์ชันโปรแกรมเป็นตัวแยกประเภทการจราจรของข้อมูลในเครือข่าย |
| cgroupskb, cgroupsock | โปรแกรมจะทำงานเมื่อใดก็ตามที่กระบวนการใน CGroup สร้างซ็อกเก็ต AF_INET หรือ AF_INET6 |
ดูประเภทเพิ่มเติม ได้ในซอร์สโค้ดตัวโหลด
ตัวอย่างเช่น โปรแกรม myschedtp.c ต่อไปนี้จะเพิ่มข้อมูลเกี่ยวกับ PID ของงานล่าสุดที่ทำงานใน CPU ที่เฉพาะเจาะจง โปรแกรมนี้บรรลุเป้าหมายโดยการสร้างแผนที่และกำหนดฟังก์ชัน tp_sched_switch ซึ่งสามารถแนบกับเหตุการณ์การติดตาม sched:sched_switch ได้ ดูข้อมูลเพิ่มเติมได้ที่
การแนบโปรแกรมกับ tracepoint
#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
หากต้องการให้ระบบบิลด์ของ 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 เมื่อบูต ระบบ Android จะโหลดโปรแกรม bpf_test.o ลงในเคอร์เนลโดยอัตโนมัติ
ไฟล์ที่พร้อมใช้งานใน sysfs
ระหว่างการบูต ระบบ Android จะโหลดออบเจ็กต์ eBPF ทั้งหมดจาก /system/etc/bpf/ โดยอัตโนมัติ สร้างแผนที่ที่โปรแกรมต้องการ และปักหมุดโปรแกรมที่โหลดด้วยแผนที่ลงในระบบไฟล์ BPF จากนั้นจะใช้ไฟล์เหล่านี้เพื่อโต้ตอบกับโปรแกรม eBPF หรืออ่านแผนที่เพิ่มเติมได้ ส่วนนี้อธิบายถึงรูปแบบการตั้งชื่อไฟล์เหล่านี้และตำแหน่งของไฟล์ใน sysfs
ระบบจะสร้างและปักหมุดไฟล์ต่อไปนี้
สำหรับโปรแกรมที่โหลด โดยสมมติว่า
PROGNAMEคือชื่อของโปรแกรม และFILENAMEคือชื่อของไฟล์ eBPF C ตัวโหลด Android จะสร้างและ ปักหมุดแต่ละโปรแกรมไว้ที่/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAMEตัวอย่างเช่น สำหรับตัวอย่าง tracepoint
sched_switchก่อนหน้านี้ในmyschedtp.cระบบจะสร้างไฟล์โปรแกรมและปักหมุดไว้ที่/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switchสำหรับแผนที่ที่สร้างขึ้น โดยสมมติว่า
MAPNAMEคือชื่อของแผนที่ และFILENAMEคือชื่อของไฟล์ eBPF C ตัวโหลด Android จะสร้างและ ปักหมุดแต่ละแผนที่ไว้ที่/sys/fs/bpf/map_FILENAME_MAPNAMEตัวอย่างเช่น สำหรับตัวอย่าง tracepoint
sched_switchก่อนหน้านี้ในmyschedtp.cระบบจะสร้างไฟล์แผนที่และปักหมุดไว้ที่/sys/fs/bpf/map_myschedtp_cpu_pid_mapbpf_obj_get()ในไลบรารี BPF ของ Android จะแสดงตัวบอกไฟล์จากไฟล์/sys/fs/bpfที่ปักหมุดไว้ ตัวระบุไฟล์นี้สามารถใช้สำหรับการดำเนินการเพิ่มเติม เช่น การอ่านแผนที่หรือการแนบโปรแกรมกับ tracepoint
ไลบรารี BPF ของ Android
ไลบรารี BPF ของ Android มีชื่อว่า libbpf_android.so และเป็นส่วนหนึ่งของอิมเมจระบบ ไลบรารีนี้มอบความสามารถ eBPF ระดับต่ำที่จำเป็นสำหรับการสร้างและอ่านแผนที่ สร้างโพรบ, tracepoint และบัฟเฟอร์ perf ให้แก่ผู้ใช้
แนบโปรแกรมกับ tracepoint
ระบบจะโหลดโปรแกรม tracepoint โดยอัตโนมัติเมื่อบูต หลังจากโหลดแล้ว คุณต้องเปิดใช้งานโปรแกรม tracepoint โดยทำตามขั้นตอนต่อไปนี้
- เรียก
bpf_obj_get()เพื่อรับfdของโปรแกรมจากตำแหน่งของไฟล์ที่ปักหมุดไว้ ดูข้อมูลเพิ่มเติมได้ที่ ไฟล์ที่พร้อมใช้งานใน sysfs - เรียก
bpf_attach_tracepoint()ในไลบรารี BPF โดยส่งfdของโปรแกรมและชื่อ tracepoint
ตัวอย่างโค้ดต่อไปนี้แสดงวิธีแนบ tracepoint 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 มีคลาส android::BpfMap ที่ใช้เทมเพลต C++
เพื่อสร้างอินสแตนซ์ BpfMap ตามประเภทคีย์และค่าสำหรับแผนที่ที่ต้องการ ตัวอย่างโค้ดก่อนหน้านี้แสดงการใช้ BpfMap ที่มีคีย์และค่าเป็นจำนวนเต็ม จำนวนเต็มอาจเป็นโครงสร้างโดยพลการก็ได้
ดังนั้น คลาส BpfMap ที่ใช้เทมเพลตจะช่วยให้คุณกำหนดออบเจ็กต์ BpfMap ที่กำหนดเองซึ่งเหมาะสำหรับแผนที่นั้นๆ ได้ จากนั้นคุณจะเข้าถึงแผนที่ได้โดยใช้ฟังก์ชันที่สร้างขึ้นเองซึ่งตระหนักถึงประเภท ทำให้โค้ดสะอาดขึ้น
ดูข้อมูลเพิ่มเติมเกี่ยวกับ BpfMap ได้ที่
ซอร์สของ Android
แก้ไขข้อบกพร่อง
ระหว่างการบูต ระบบจะบันทึกข้อความหลายรายการที่เกี่ยวข้องกับการโหลด BPF หากกระบวนการโหลดล้มเหลวไม่ว่าด้วยเหตุผลใดก็ตาม ระบบจะแสดงข้อความในบันทึกโดยละเอียดใน Logcat การกรองบันทึก logcat ตาม bpf จะพิมพ์ข้อความทั้งหมดและข้อผิดพลาดโดยละเอียดระหว่างเวลาโหลด เช่น ข้อผิดพลาดของเครื่องมือตรวจสอบ eBPF
ตัวอย่าง eBPF ใน Android
โปรแกรมต่อไปนี้ใน AOSP แสดงตัวอย่างเพิ่มเติมของการใช้ eBPF
โปรแกรม
netdeBPF C ใช้โดย daemon เครือข่าย (netd) ใน Android เพื่อวัตถุประสงค์ต่างๆ เช่น การกรองซ็อกเก็ตและการรวบรวมสถิติ หากต้องการดูวิธีใช้โปรแกรมนี้ โปรดดูซอร์สของเครื่องมือตรวจสอบการรับส่งข้อมูล eBPF monitorโปรแกรม
time_in_stateeBPF C จะคำนวณระยะเวลาที่แอป Android ใช้ที่ความถี่ CPU ต่างๆ ซึ่งใช้ในการคำนวณพลังงานใน Android 12
gpu_memโปรแกรม eBPF C จะติดตามการใช้งานหน่วยความจำ GPU ทั้งหมดสำหรับแต่ละกระบวนการและสำหรับทั้งระบบ โปรแกรมนี้ใช้สำหรับการสร้างโปรไฟล์หน่วยความจำ GPU
ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ eBPF เพื่อรวบรวมการจัดการหน่วยความจำเคอร์เนลที่มีค่าใช้จ่ายต่ำ
และฮิสโทแกรมเวลาในการตอบสนองด้านพลังงานสำหรับ statsd ได้ที่รวบรวมข้อมูลการวัดและส่งข้อมูลเคอร์เนลด้วย eBPF