Bộ lọc gói Berkeley mở rộng (eBPF) là một máy ảo trong nhân hệ điều hành
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. Bạn có thể nối các chương trình này với các đầu dò hoặc sự kiện trong nhân và dùng để thu thập số liệu thống kê, theo dõi và gỡ lỗi hữu ích của nhân. Một chương trình được tải vào hạt nhân bằng lệnh gọi hệ thống bpf(2)
và do người dùng cung cấp dưới dạng một blob nhị phân của các lệnh 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ể xem thêm thông tin về cấu trúc và nội bộ eBPF tại Brendan Trang eBPF của Gregg.
Android bao gồm trình tải và thư viện eBPF tải các chương trình eBPF vào thời điểm 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/
sẽ được tải. Các chương trình này là các đối tượng nhị phân do 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 được 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 trên 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 license
Trong trường hợp:
name_of_my_map
là tên của biến bản đồ. Chiến dịch này tên thông báo cho trình tải BPF về loại bản đồ cần tạo và loại bản đồ cần tạo tham số. Định nghĩa cấu trúc này dobpf_helpers.h
đi kèm cung cấp .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 đó; quy trình đính kèm chương trình chỉ cần biết tên.PROGFUNC
là một hàm được đặt trong một phần của tệp kết quả khi được biên dịch.
kprobe | Gắn PROGFUNC vào một lệnh hạt nhân bằng
cơ sở hạ tầng kprobe. PROGNAME phải là tên của hàm hạt nhân đang được kprobe. Hãy tham khảo tài liệu về kernel kprobe để biết thêm thông tin về
kprobe.
|
---|---|
điểm theo dõi | Gắn PROGFUNC vào một điểm theo dõi. PROGNAME phải là
thuộ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 sự kiện chuyển đổi ngữ cảnh của trình lập lịch biểu 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 xem 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 | Chương trình hoạt động như một bộ lọc cổng mạng. |
schedcls | Chương trình hoạt động như một thuật toán 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 một ổ cắm AF_INET hoặc AF_INET6. |
Các loại khác có thể có trong Trình tải mã nguồn.
Ví dụ: chương trình myschedtp.c
sau đây sẽ 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 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 đ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 hạt nhân hay không khi chương trình sử dụng các hàm trợ giúp BPF do hạt 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 tạo chương trình .c
eBPF, bạn phải
hãy tạo một mục trong tệp Android.bp
của dự án. Ví dụ: để tạo một chương trình C eBPF có tên là bpf_test.c
, hãy tạo mục nhập sau trong tệp Android.bp
của dự án:
bpf { name: "bpf_test.o", srcs: ["bpf_test.c"], cflags: [ "-Wall", "-Werror", ], }
Mục nhập 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 hạt nhân.
Tệp có trong sysfs
Trong khi 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 bản đồ mà chương trình cần và ghim tệp đã tải
chương trình của mình với các bản đồ đến hệ thống tệp BPF. Sau đó, các tệp này có thể được dùng để
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 dùng để đặt tên cho các tệp này và vị trí của các tệp đó trong sysfs.
Các tệp sau đây sẽ được tạo và ghim:
Đối với mọi chương trình được tải, giả sử
PROGNAME
là tên của chương trình vàFILENAME
là tên của tệp C eBPF, 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_switch
trước đó trongmyschedtp.c
, một tệp chương trình sẽ đượ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ử
MAPNAME
là tên của bản đồ vàFILENAME
là tên của tệp C eBPF, 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ụ trước đó về điểm theo dõi
sched_switch
trongmyschedtp.c
, một tệp bản đồ sẽ được tạo và ghim vào/sys/fs/bpf/map_myschedtp_cpu_pid_map
.bpf_obj_get()
trong thư viện BPF của Android trả về một chỉ số mô tả tệp từ tệp/sys/fs/bpf
được ghim. Chỉ số mô tả tệp này có thể được dùng để giúp bạn chẳng hạn như đọc bản đồ hoặc đính kèm một chương trình vào một điểm theo dõi.
Thư viện Android BPF
Thư viện BPF của Android có tên là libbpf_android.so
và là một phần của hệ thống
hình ảnh. Thư viện này cung cấp cho người dùng các tính năng eBPF cấp thấp cần thiết
để tạo và đọc bản đồ, tạo đầu dò, điểm theo dõi và vùng đệm hiệu suất.
Đí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, bạn phải kích hoạt chương trình điểm theo dõi bằng các bước sau:
- Gọi
bpf_obj_get()
để lấy chương trìnhfd
từ giá trị của tệp đã ghim vị trí. Để biết thêm thông tin, hãy tham khảo Các tệp có trong sysfs. - Gọi
bpf_attach_tracepoint()
trong thư viện BPF, truyền vào đó chương trìnhfd
và 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ị tính năng 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 kiểu hoặc cấu trúc giá trị và khoá phức tạp tuỳ ý. Chiến lược phát hành đĩa đơn
Thư viện Android BPF có một lớp android::BpfMap
sử dụng C++
các mẫu để tạo thực thể BpfMap
dựa trên khoá và loại giá trị cho
bản đồ được đề cập. Mã mẫu ở trên minh hoạ cách sử dụng BpfMap
với khoá
và giá trị dưới dạng số nguyên. Số nguyên cũng có thể là cấu trúc tuỳ ý.
Do đó, lớp BpfMap
được tạo mẫu cho phép bạn xác định một 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ữ liệu, nhờ đó tạo ra mã gọn gàng hơn.
Để biết thêm thông tin về BpfMap
, hãy tham khảo nguồn Android.
Khắc phục 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 sẽ được ghi lại. Nếu quá trình tải không thành công vì bất kỳ lý do gì, thì thông báo 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ả thông điệp và
bất kỳ lỗi chi tiết nào 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 trong AOSP cung cấp thêm ví dụ về cách sử dụng eBPF:
netd
chương trình C eBPF đượ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 chương trình này được sử dụng, kiểm tra lưu lượng truy cập eBPF giám sát.time_in_state
Chương trình eBPF C tính toán khoảng thời gian một ứng dụng Android dành cho các tần số CPU khác nhau, được dùng để tính năng lượng.Trong Android 12,
gpu_mem
eBPF C chương trình theo 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. Chiến dịch này được dùng để phân tích bộ nhớ GPU.