Bộ lọc gói Berkeley mở rộng (eBPF) là một máy ảo trong nhân chạy các chương trình eBPF do người dùng cung cấp để mở rộng chức năng của nhân. Các chương trình này có thể được liên kết với các sự kiện hoặc điểm thăm dò trong nhân và được dùng để thu thập số liệu thống kê hữu ích về nhân, giám sát và gỡ lỗi. Một chương trình được tải vào nhân bằng cách sử dụng lệnh gọi hệ thống bpf(2) và được người dùng cung cấp dưới dạng một blob nhị phân của các hướng dẫn máy eBPF. Hệ thống xây dựng Android hỗ trợ biên dịch các chương trình C sang eBPF bằng cú pháp tệp bản dựng đơn giản được mô tả trong tài liệu này.
Bạn có thể tìm thêm thông tin về nội bộ và kiến trúc eBPF trên trang eBPF của Brendan Gregg.
Android bao gồm một trình tải và thư viện eBPF tải các chương trình eBPF tại thời gian khởi động.
Trình tải BPF Android
Trong quá trình khởi động Android, tất cả các chương trình eBPF nằm tại /system/etc/bpf/ đều được tải. Các chương trình này là các đối tượng nhị phân được hệ thống xây dựng Android tạo từ các chương trình C và đi kèm với các tệp Android.bp trong cây nguồn Android. Hệ thống xây dựng lưu trữ các đối tượng đã tạo tại /system/etc/bpf và các đối tượng đó trở thành một phần của hình ảnh hệ thống.
Định dạng của chương trình eBPF C Android
Chương trình eBPF C phải có định dạng sau:
#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 licenseTrong trường hợp:
name_of_my_maplà tên của biến bản đồ. Tên này thông báo cho trình tải BPF về loại bản đồ cần tạo và các tham số. Định nghĩa cấu trúc này được cung cấp bởi tiêu đềbpf_helpers.hđi kèm.PROGTYPE/PROGNAMEđại diện cho loại chương trình và tên chương trình. Loại chương trình có thể là bất kỳ loại nào được liệt kê trong bảng sau. Khi một loại chương trình không được liệt kê, sẽ không có quy ước đặt tên nghiêm ngặt cho chương trình; tên chỉ cần được biết đến đối với quy trình đính kèm chương trình.PROGFUNClà một hàm mà khi được biên dịch, sẽ được đặt trong một phần của tệp kết quả.
| kprobe | Liên kết PROGFUNC với một hướng dẫn nhân bằng cách sử dụng cơ sở hạ tầng kprobe. PROGNAME phải là tên của hàm nhân đang được kprobed. Hãy tham khảo tài liệu về nhân kprobe để biết thêm thông tin về
kprobe.
|
|---|---|
| tracepoint | Liên kết PROGFUNC với một điểm theo dõi. PROGNAME phải có định dạng SUBSYSTEM/EVENT. Ví dụ: một phần điểm theo dõi
để đính kèm các hàm vào các sự kiện chuyển đổi ngữ cảnh của trình lập lịch sẽ là
SEC("tracepoint/sched/sched_switch"), trong đó sched là
tên của hệ thống con theo dõi và sched_switch là tên
của sự kiện theo dõi. Hãy kiểm tra tài liệu về nhân sự kiện theo dõi để biết thêm thông tin về các điểm theo dõi.
|
| skfilter | Các hàm chương trình dưới dạng bộ lọc ổ cắm mạng. |
| schedcls | Các hàm chương trình dưới dạng bộ phân loại lưu lượng truy cập mạng. |
| cgroupskb, cgroupsock | Chương trình chạy bất cứ khi nào các quy trình trong CGroup tạo ổ cắm AF_INET hoặc AF_INET6. |
Bạn có thể tìm thấy các loại bổ sung trong mã nguồn Trình tải.
Ví dụ: chương trình myschedtp.c sau đây thêm thông tin về PID tác vụ mới nhất đã chạy trên một CPU cụ thể. Chương trình này đạt được mục tiêu bằng cách tạo một bản đồ và xác định hàm tp_sched_switch có thể được đính kèm vào sự kiện theo dõi sched:sched_switch. Để biết thêm thông tin, hãy xem
Đính kèm chương trình vào các điểm theo dõi.
#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");
Macro LICENSE được dùng để xác minh xem chương trình có tương thích với giấy phép của nhân hay không khi chương trình sử dụng các hàm trợ giúp BPF do nhân cung cấp. Chỉ định tên giấy phép của chương trình ở dạng chuỗi, chẳng hạn như
LICENSE("GPL") hoặc LICENSE("Apache 2.0").
Định dạng của tệp Android.bp
Để hệ thống xây dựng Android xây dựng chương trình eBPF .c, bạn phải tạo một mục trong tệp Android.bp của dự án. Ví dụ: để xây dựng chương trình eBPF C có tên bpf_test.c, hãy tạo mục sau trong tệp Android.bp của dự án:
bpf {
name: "bpf_test.o",
srcs: ["bpf_test.c"],
cflags: [
"-Wall",
"-Werror",
],
}Mục này biên dịch chương trình C dẫn đến đối tượng /system/etc/bpf/bpf_test.o. Khi khởi động, hệ thống Android sẽ tự động tải chương trình bpf_test.o vào nhân.
Các tệp có trong sysfs
Trong quá trình khởi động, hệ thống Android sẽ tự động tải tất cả các đối tượng eBPF từ /system/etc/bpf/, tạo các bản đồ mà chương trình cần và ghim chương trình đã tải bằng các bản đồ của chương trình vào hệ thống tệp BPF. Sau đó, bạn có thể sử dụng các tệp này để tương tác thêm với chương trình eBPF hoặc đọc bản đồ. Phần này mô tả các quy ước được dùng để đặt tên cho các tệp này và vị trí của chúng trong sysfs.
Các tệp sau đây được tạo và ghim:
Đối với mọi chương trình được tải, giả sử
PROGNAMElà tên của chương trình vàFILENAMElà tên của tệp eBPF C, trình tải Android sẽ tạo và ghim từng chương trình tại/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME.Ví dụ: đối với ví dụ về điểm theo dõi
sched_switchtrước đó trongmyschedtp.c, một tệp chương trình được tạo và ghim vào/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch.Đối với mọi bản đồ được tạo, giả sử
MAPNAMElà tên của bản đồ vàFILENAMElà tên của tệp eBPF C, trình tải Android sẽ tạo và ghim từng bản đồ vào/sys/fs/bpf/map_FILENAME_MAPNAME.Ví dụ: đối với ví dụ về điểm theo dõi
sched_switchtrước đó trongmyschedtp.c, một tệp bản đồ được tạo và ghim vào/sys/fs/bpf/map_myschedtp_cpu_pid_map.bpf_obj_get()trong thư viện BPF Android trả về một chỉ số mô tả tệp từ tệp/sys/fs/bpfđược ghim. Bạn có thể sử dụng chỉ số mô tả tệp này cho các thao tác khác, chẳng hạn như đọc bản đồ hoặc đính kèm chương trình vào một điểm theo dõi.
Thư viện BPF Android
Thư viện BPF Android có tên là libbpf_android.so và là một phần của hình ảnh hệ thống. Thư viện này cung cấp cho người dùng các khả năng eBPF cấp thấp cần thiết để tạo và đọc bản đồ, tạo điểm thăm dò, điểm theo dõi và bộ đệm perf.
Đính kèm chương trình vào các điểm theo dõi
Các chương trình điểm theo dõi được tải tự động khi khởi động. Sau khi tải, chương trình điểm theo dõi phải được kích hoạt bằng các bước sau:
- Gọi
bpf_obj_get()để lấy chương trìnhfdtừ vị trí của tệp được ghim. Để biết thêm thông tin, hãy tham khảo bài viết Các tệp có trong sysfs. - Gọi
bpf_attach_tracepoint()trong thư viện BPF, truyền cho thư viện này chương trìnhfdvà tên điểm theo dõi.
Mã mẫu sau đây cho biết cách đính kèm điểm theo dõi sched_switch được xác định trong tệp nguồn myschedtp.c trước đó (không hiển thị việc kiểm tra lỗi):
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));
Đọc từ bản đồ
Bản đồ BPF hỗ trợ các cấu trúc hoặc loại khoá và giá trị phức tạp tuỳ ý. Thư viện BPF Android bao gồm một lớp android::BpfMap sử dụng các mẫu C++
để khởi tạo BpfMap dựa trên loại khoá và giá trị cho bản đồ được đề cập. Mã mẫu trước đó minh hoạ việc sử dụng BpfMap với khoá và giá trị là số nguyên. Các số nguyên cũng có thể là các cấu trúc tuỳ ý.
Do đó, lớp BpfMap được tạo mẫu cho phép bạn xác định đối tượng BpfMap tuỳ chỉnh phù hợp với bản đồ cụ thể. Sau đó, bạn có thể truy cập vào bản đồ bằng các hàm được tạo tuỳ chỉnh, các hàm này nhận biết được loại, dẫn đến mã sạch hơn.
Để biết thêm thông tin về BpfMap, hãy tham khảo các
nguồn Android.
Gỡ lỗi vấn đề
Trong thời gian khởi động, một số thông báo liên quan đến việc tải BPF được ghi lại. Nếu quá trình tải không thành công vì bất kỳ lý do nào, thì một thông điệp nhật ký chi tiết sẽ được cung cấp trong logcat. Việc lọc nhật ký logcat theo bpf sẽ in tất cả các thông báo và mọi lỗi chi tiết trong thời gian tải, chẳng hạn như lỗi trình xác minh eBPF.
Ví dụ về eBPF trong Android
Các chương trình sau đây trong AOSP cung cấp thêm ví dụ về việc sử dụng eBPF:
Chương trình
netdeBPF C được trình nền mạng (netd) trong Android sử dụng cho nhiều mục đích, chẳng hạn như lọc ổ cắm và thu thập số liệu thống kê. Để xem cách sử dụng chương trình này, hãy kiểm tra các nguồn giám sát lưu lượng truy cập eBPF.Chương trình
time_in_stateeBPF C tính toán khoảng thời gian mà ứng dụng Android dành cho các tần số CPU khác nhau, được dùng để tính toán mức sử dụng năng lượng.Trong Android 12, chương trình eBPF C
gpu_memtheo dõi tổng mức sử dụng bộ nhớ GPU cho từng quy trình và cho toàn bộ hệ thống. Chương trình này được dùng để lập hồ sơ bộ nhớ GPU.