Extended Berkeley Packet Filter (eBPF) は、ユーザーが提供した eBPF プログラムを実行してカーネル機能を拡張するカーネル内仮想マシンです。これらのプログラムは、カーネル内のプローブまたはイベントにフックして、有用なカーネル統計の収集、監視、およびデバッグに使用できます。プログラムは、 bpf(2)
システムコールを使用してカーネルにロードされ、eBPF マシン命令のバイナリ BLOB としてユーザーによって提供されます。 Android ビルド システムでは、このドキュメントで説明されている単純なビルド ファイル構文を使用して、C プログラムを eBPF にコンパイルすることがサポートされています。
eBPF の内部構造とアーキテクチャの詳細については、Brendan Gregg の 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 will also define 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
、コンパイル時に結果のファイルのセクションに配置される関数です。
kプローブ | kprobe インフラストラクチャを使用してPROGFUNC カーネル命令にフックします。 PROGNAME kprobe されるカーネル関数の名前である必要があります。 kprobe の詳細については、kprobe カーネルのドキュメントを参照してください。 |
---|---|
トレースポイント | PROGFUNC トレースポイントにフックします。 PROGNAME SUBSYSTEM/EVENT の形式である必要があります。たとえば、スケジューラ コンテキスト スイッチ イベントに関数を付加するためのトレースポイント セクションはSEC("tracepoint/sched/sched_switch") になります。ここで、 sched はトレース サブシステムの名前、 sched_switch はトレース イベントの名前です。トレースポイントの詳細については、トレース イベント カーネルのドキュメントを確認してください。 |
スクフィルター | プログラムはネットワークソケットフィルターとして機能します。 |
スケジュール | プログラムはネットワーク トラフィック分類子として機能します。 |
cgroupskb、cgroupsock | CGroup 内のプロセスが AF_INET または AF_INET6 ソケットを作成するたびに、プログラムが実行されます。 |
追加のタイプは、ローダーのソース コードにあります。
たとえば、次のmyschedtp.c
プログラムは、特定の CPU で実行された最新のタスク PID に関する情報を追加します。このプログラムは、マップを作成し、 sched:sched_switch
トレース イベントに付加できるtp_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 ファイルの形式
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
トレースポイントの例では、プログラム ファイルが作成され、/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch
に固定されます。作成されたマップについては、
MAPNAME
がマップの名前、FILENAME
が eBPF C ファイルの名前であると仮定すると、Android ローダーは各マップを作成し、/sys/fs/bpf/map_FILENAME_MAPNAME
に固定します。たとえば、
myschedtp.c
内の前述のsched_switch
トレースポイントの例では、マップ ファイルが作成され、/sys/fs/bpf/map_myschedtp_cpu_pid_map
に固定されます。Android BPF ライブラリの
bpf_obj_get()
は、固定された/sys/fs/bpf
ファイルからファイル記述子を返します。このファイル記述子は、マップの読み取りやトレースポイントへのプログラムの添付など、さらなる操作に使用できます。
Android BPF ライブラリ
Android BPF ライブラリはlibbpf_android.so
という名前で、システム イメージの一部です。このライブラリは、マップの作成と読み取り、プローブ、トレースポイント、およびパフォーマンス バッファーの作成に必要な低レベルの eBPF 機能をユーザーに提供します。
トレースポイントへのプログラムの接続
トレースポイント プログラムは起動時に自動的にロードされます。ロード後、次の手順を使用してトレースポイント プログラムをアクティブにする必要があります。
-
bpf_obj_get()
を呼び出して、固定されたファイルの場所からプログラムfd
を取得します。詳細については、 「 sysfs で利用可能なファイル」を参照してください。 - BPF ライブラリの
bpf_attach_tracepoint()
を呼び出し、プログラムfd
とトレースポイント名を渡します。
次のコード サンプルは、前のmyschedtp.c
ソース ファイルで定義されているsched_switch
トレースポイントを接続する方法を示しています (エラー チェックは示されていません)。
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++ テンプレートを使用して、問題のマップのキーと値の型に基づいてBpfMap
をインスタンス化するandroid::BpfMap
クラスが含まれています。前のコード サンプルは、キーと値を整数として持つBpfMap
の使用を示しています。整数は任意の構造にすることもできます。
したがって、テンプレート化されたBpfMap
クラスを使用すると、特定のマップに適したカスタムBpfMap
オブジェクトを簡単に定義できます。その後、型を認識するカスタム生成関数を使用してマップにアクセスできるため、よりクリーンなコードが得られます。
BpfMap
の詳細については、 Android のソースを参照してください。
デバッグの問題
ブート時に、BPF ロードに関連するいくつかのメッセージがログに記録されます。何らかの理由でロードプロセスが失敗した場合、詳細なログメッセージが logcat に提供されます。 logcat ログを「bpf」でフィルタリングすると、すべてのメッセージとロード時の詳細なエラー (eBPF 検証エラーなど) が出力されます。
Android での eBPF の例
AOSP の次のプログラムは、eBPF の使用例をさらに示しています。
netd
eBPF C プログラムは、ソケット フィルタリングや統計収集などのさまざまな目的で、Android のネットワーク デーモン (netd) によって使用されます。このプログラムがどのように使用されているかを確認するには、 eBPF トラフィック モニターのソースを確認してください。time_in_state
eBPF C プログラムは、 Android アプリがさまざまな CPU 周波数で費やす時間を計算し、電力の計算に使用されます。Android 12 では、
gpu_mem
eBPF C プログラムは、各プロセスおよびシステム全体の合計 GPU メモリ使用量を追跡します。このプログラムは GPU メモリ プロファイリングに使用されます。