eBPF(Extended Berkeley Packet Filter)는 사용자 제공 eBPF 프로그램을 실행하여 커널 기능을 확장하는 커널 내 가상 머신입니다. 이러한 프로그램은 커널의 프로브나 이벤트에 연결하여 유용한 커널 통계를 수집하고 모니터링하고 디버그하는 데 사용할 수 있습니다. 프로그램은 bpf(2)
syscall을 사용하여 커널로 로드되며 사용자가 eBPF 머신 명령의 바이너리 blob으로 제공합니다. Android 빌드 시스템은 이 문서에 설명된 간단한 빌드 파일 문법을 사용하여 C 프로그램을 eBPF로 컴파일하는 기능을 지원합니다.
eBPF 내부 기능 및 아키텍처에 관한 자세한 내용은 브렌든 그레그의 eBPF 페이지를 참고하세요.
Android에는 부팅 시 eBPF 프로그램을 로드하는 eBPF 로더와 라이브러리가 포함되어 있습니다.
Android BPF 로더
Android 부팅 중에는 /system/etc/bpf/
에 있는 모든 eBPF 프로그램이 로드됩니다. 이러한 프로그램은 C 프로그램에서 Android 빌드 시스템이 빌드한 바이너리 객체이며 Android 소스 트리의 Android.bp
파일과 함께 제공됩니다. 빌드 시스템은 생성된 객체를 /system/etc/bpf
에 저장하며 이러한 객체는 시스템 이미지의 일부가 됩니다.
Android 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 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 | kprobe 인프라를 사용하여 커널 명령에 PROGFUNC 를 연결합니다. 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
프로그램은 특정 CPU에서 실행된 최신 작업 PID에 관한 정보를 추가합니다. 이 프로그램은 맵을 만들고 sched:sched_switch
트레이스 이벤트에 연결할 수 있는 tp_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
파일에 항목을 만들어야 합니다. 예를 들어 이름이 bpf_test.c
인 eBPF 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 시스템은 /system/etc/bpf/
에서 모든 eBPF 객체를 자동으로 로드하고 프로그램에 필요한 맵을 만들며, 로드된 프로그램을 맵과 함께 bpf 파일 시스템에 고정합니다. 이러한 파일은 eBPF 프로그램과 추가로 상호작용하거나 맵을 읽는 데 사용할 수 있습니다. 이 섹션에서는 이러한 파일의 이름을 지정하는 데 사용되는 규칙과 sysfs에서의 파일 위치를 설명합니다.
다음 파일이 생성되어 고정됩니다.
로드된 모든 프로그램에서
PROGNAME
은 프로그램의 이름으로,FILENAME
은 eBPF C 파일의 이름으로 가정하고 Android 로더는/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME
에서 각 프로그램을 생성하고 고정합니다.예를 들어
myschedtp.c
에서 위의sched_switch
tracepoint 예의 경우 프로그램 파일이 생성되어/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch
에 고정됩니다.생성된 모든 맵의 경우
MAPNAME
은 맵의 이름으로,FILENAME
은 eBPF C 파일의 이름으로 가정하고 Android 로더는 각 맵을 생성해/sys/fs/bpf/map_FILENAME_MAPNAME
에 고정합니다.예를 들어
myschedtp.c
에서 위의sched_switch
tracepoint 예의 경우 맵 파일이 생성되어/sys/fs/bpf/map_myschedtp_cpu_pid_map
에 고정됩니다.Android BPF 라이브러리의
bpf_obj_get()
은 고정된/sys/fs/bpf
파일에서 파일 설명자를 반환합니다. 이 파일 설명자는 맵을 읽거나 프로그램을 tracepoint에 연결하는 등의 추가 작업에 사용할 수 있습니다.
Android BPF 라이브러리
Android BPF 라이브러리의 이름은 libbpf_android.so
이며 시스템 이미지의 일부입니다. 이 라이브러리는 사용자에게 맵 생성 및 읽기, 프로브, tracepoint, perf 버퍼 생성에 필요한 하위 수준 eBPF 기능을 제공합니다.
tracepoint에 프로그램 연결
tracepoint 프로그램은 부팅 시 자동으로 로드됩니다. 로드한 후에는 다음 단계를 사용하여 tracepoint 프로그램을 활성화해야 합니다.
bpf_obj_get()
을 호출하여 고정된 파일의 위치에서 프로그램fd
를 가져옵니다. 자세한 내용은 sysfs에서 사용할 수 있는 파일을 참고하세요.- BPF 라이브러리에서
bpf_attach_tracepoint()
를 호출하여 프로그램fd
와 tracepoint 이름을 전달합니다.
다음 코드 샘플은 이전 myschedtp.c
소스 파일에 정의된 sched_switch
tracepoint를 연결하는 방법을 보여줍니다(오류 검사는 표시되지 않음).
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 라이브러리에는 C++를 사용하는 android::BpfMap
클래스가 포함되어 있습니다.
키 및 값 유형을 기반으로 BpfMap
을 인스턴스화하는 템플릿
있습니다. 이전 코드 샘플은 키와 값이 정수인 BpfMap
을 사용하는 방법을 보여줍니다. 정수는 임의의 구조일 수도 있습니다.
따라서 템플릿화된 BpfMap
클래스를 사용하면 맞춤 BpfMap
를 정의할 수 있습니다.
객체를 정의합니다. 그런 다음 유형을 인식하는 맞춤 생성된 함수를 사용하여 맵에 액세스할 수 있는데, 덕분에 코드가 더 깔끔하게 정리됩니다.
BpfMap
에 관한 자세한 내용은 Android 소스를 참조하세요.
문제 디버그
부팅 중에는 BPF 로드와 관련된 여러 메시지가 로깅됩니다. 어떠한 이유로든 로드 프로세스에 실패하는 경우 자세한 로그 메시지가 logcat에 제공됩니다. bpf
로 logcat 로그를 필터링하면 모든 메시지가 출력되고
로드 중 발생한 자세한 오류(예: eBPF 인증기 오류)
Android의 eBPF 예
AOSP의 다음 프로그램은 eBPF를 사용하는 추가 예를 제공합니다.
netd
eBPF C 프로그램 다음과 같은 다양한 목적으로 Android의 네트워킹 데몬 (netd)에서 사용합니다. 소켓 필터링 및 통계 수집이 포함됩니다. 이 프로그램의 사용 방법을 보려면 eBPF 트래픽 모니터 소스를 확인하세요.time_in_state
eBPF C 프로그램은 전력을 계산하는 데 사용된 여러 CPU 주파수에서 Android 앱이 소비한 시간을 계산합니다.Android 12에서
gpu_mem
eBPF C 프로그램 각 프로세스 및 전체 시스템의 총 GPU 메모리 사용량을 추적합니다. 이 프로그램은 GPU 메모리 프로파일링에 사용됩니다.