Çekirdeği eBPF ile genişletme

Genişletilmiş Berkeley Paket Filtresi (eBPF), çekirdek işlevselliğini genişletmek için kullanıcı tarafından sağlanan eBPF programlarını çalıştıran çekirdek içi bir sanal makinedir. Bu programlar, çekirdekteki sondalara veya etkinliklere bağlanabilir ve faydalı çekirdek istatistiklerini toplamak, izlemek ve hata ayıklamak için kullanılabilir. Bir program, bpf(2) syscall kullanılarak çekirdeğe yüklenir ve kullanıcı tarafından eBPF makine talimatlarının ikili blobu olarak sağlanır. Android derleme sistemi, bu belgede açıklanan basit derleme dosyası söz dizimini kullanarak C programlarını eBPF'ye derlemeyi destekler.

eBPF'nin iç işleyişi ve mimarisi hakkında daha fazla bilgiyi Brendan Gregg'in eBPF sayfasında bulabilirsiniz.

Android, önyükleme sırasında eBPF programlarını yükleyen bir eBPF yükleyici ve kitaplığı içerir.

Android BPF yükleyici

Android başlatma sırasında /system/etc/bpf/ konumundaki tüm eBPF programları yüklenir. Bu programlar, Android derleme sistemi tarafından C programlarından oluşturulan ikili nesnelerdir ve Android kaynak ağacında Android.bp dosyalarıyla birlikte gelir. Derleme sistemi, oluşturulan nesneleri /system/etc/bpf konumunda depolar ve bu nesneler sistem görüntüsünün bir parçası haline gelir.

Android eBPF C programının biçimi

Bir eBPF C programı aşağıdaki biçimde olmalıdır:

#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

Nerede:

  • name_of_my_map, harita değişkeninizin adıdır. Bu ad, BPF yükleyicisine oluşturulacak haritanın türü ve hangi parametrelerle oluşturulacağı hakkında bilgi verir. Bu yapı tanımı, dahil edilen bpf_helpers.h üstbilgisi tarafından sağlanır.
  • PROGTYPE/PROGNAME, programın türünü ve program adını gösterir. Program türü, aşağıdaki tabloda listelenenlerden herhangi biri olabilir. Bir program türü listelenmiyorsa program için katı bir adlandırma kuralı yoktur. Adın yalnızca programı ekleyen işlem tarafından bilinmesi gerekir.

  • PROGFUNC, derlendiğinde sonuç dosyasının bir bölümüne yerleştirilen bir işlevdir.

kprobe kprobe altyapısını kullanarak bir çekirdek talimatına PROGFUNC bağlanır. PROGNAME, kprobed edilen çekirdek işlevinin adı olmalıdır. Kprobes hakkında daha fazla bilgi için kprobe çekirdek belgelerine bakın.
izleme noktası Bir izleme noktasına PROGFUNC kancalar. PROGNAME, SUBSYSTEM/EVENT biçiminde olmalıdır. Örneğin, işlevleri zamanlayıcı bağlamı değiştirme etkinliklerine eklemek için kullanılan bir izleme noktası bölümü SEC("tracepoint/sched/sched_switch") olur. Burada sched, izleme alt sisteminin adı, sched_switch ise izleme etkinliğinin adıdır. İzleme noktaları hakkında daha fazla bilgi için trace events kernel documentation (İzleme etkinlikleri çekirdeği belgeleri) başlıklı makaleyi inceleyin.
skfilter Program, ağ soketi filtresi olarak işlev görür.
schedcls Program, ağ trafiği sınıflandırıcı olarak işlev görür.
cgroupskb, cgroupsock Program, bir CGroup'taki işlemler AF_INET veya AF_INET6 soketi oluşturduğunda çalışır.

Ek türleri Loader kaynak kodunda bulabilirsiniz.

Örneğin, aşağıdaki myschedtp.c programı, belirli bir CPU'da çalıştırılan en son görev PID'si hakkında bilgi ekler. Bu program, bir harita oluşturup tp_sched_switch izleme etkinliğine eklenebilen bir sched:sched_switch işlevi tanımlayarak amacına ulaşır. Daha fazla bilgi için Programları izleme noktalarına ekleme başlıklı makaleyi inceleyin.

#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 makrosu, program çekirdek tarafından sağlanan BPF yardımcı işlevlerini kullandığında programın çekirdek lisansıyla uyumlu olup olmadığını doğrulamak için kullanılır. Programınızın lisansının adını dize biçiminde belirtin (ör. LICENSE("GPL") veya LICENSE("Apache 2.0")).

Android.bp dosyasının biçimi

Android derleme sisteminin bir eBPF .c programı oluşturması için projenin Android.bp dosyasında bir giriş oluşturmanız gerekir. Örneğin, bpf_test.c adlı bir eBPF C programı oluşturmak için projenizin Android.bp dosyasına aşağıdaki girişi yapın:

bpf {
    name: "bpf_test.o",
    srcs: ["bpf_test.c"],
    cflags: [
        "-Wall",
        "-Werror",
    ],
}

Bu giriş, /system/etc/bpf/bpf_test.o nesnesiyle sonuçlanan C programını derler. Android sistemi, başlatma sırasında bpf_test.o programını çekirdeğe otomatik olarak yükler.

sysfs'de kullanılabilen dosyalar

Android sistemi, başlatma sırasında /system/etc/bpf/ konumundaki tüm eBPF nesnelerini otomatik olarak yükler, programın ihtiyaç duyduğu haritaları oluşturur ve yüklenen programı haritalarıyla birlikte BPF dosya sistemine sabitler. Bu dosyalar daha sonra eBPF programıyla daha fazla etkileşim kurmak veya haritaları okumak için kullanılabilir. Bu bölümde, bu dosyaları adlandırmak için kullanılan kurallar ve sysfs'deki konumları açıklanmaktadır.

Aşağıdaki dosyalar oluşturulur ve sabitlenir:

  • Yüklenen tüm programlar için (PROGNAME programın adı ve FILENAME eBPF C dosyasının adı olmak üzere) Android yükleyici, her programı /sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME konumunda oluşturup sabitler.

    Örneğin, myschedtp.c içindeki önceki sched_switch izleme noktası örneği için bir program dosyası oluşturulur ve /sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch'ye sabitlenir.

  • Oluşturulan tüm haritalar için, MAPNAME haritanın adı ve FILENAME eBPF C dosyasının adı olmak üzere Android yükleyici, her haritayı oluşturup /sys/fs/bpf/map_FILENAME_MAPNAME'ye sabitler.

    Örneğin, myschedtp.c bölümündeki önceki sched_switch izleme noktası örneği için bir harita dosyası oluşturulur ve /sys/fs/bpf/map_myschedtp_cpu_pid_map konumuna sabitlenir.

  • Android BPF kitaplığındaki bpf_obj_get(), sabitlenmiş /sys/fs/bpf dosyasından bir dosya tanımlayıcısı döndürür. Bu dosya tanımlayıcısı, haritaları okuma veya bir programı izleme noktasına ekleme gibi başka işlemler için kullanılabilir.

Android BPF kitaplığı

Android BPF kitaplığı libbpf_android.so olarak adlandırılır ve sistem görüntüsünün bir parçasıdır. Bu kitaplık, kullanıcılara harita oluşturma ve okuma, prob oluşturma, izleme noktası oluşturma ve perf arabellekleri için gereken düşük düzeyli eBPF özellikleri sağlar.

Programları izleme noktalarına ekleme

İzleme noktası programları, önyükleme sırasında otomatik olarak yüklenir. Yüklendikten sonra, izleme noktası programı aşağıdaki adımlar kullanılarak etkinleştirilmelidir:

  1. Sabitlenmiş dosyanın konumundan fd programını almak için bpf_obj_get() numarasını arayın. Daha fazla bilgi için sysfs'de bulunan dosyalar başlıklı makaleyi inceleyin.
  2. BPF kitaplığında bpf_attach_tracepoint() işlevini çağırın ve programa fd ile izleme noktası adını iletin.

Aşağıdaki kod örneğinde, önceki myschedtp.c kaynak dosyasında tanımlanan sched_switch izleme noktasının nasıl ekleneceği gösterilmektedir (hata kontrolü gösterilmemiştir):

  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));

Haritalardan okuma

BPF haritaları, rastgele karmaşık anahtar ve değer yapılarını ya da türlerini destekler. Android BPF kitaplığı, söz konusu haritadaki anahtar ve değer türüne göre BpfMap öğesini oluşturmak için C++ şablonlarından yararlanan bir android::BpfMap sınıfı içerir. Önceki kod örneğinde, anahtar ve değer olarak tam sayıların kullanıldığı bir BpfMap gösterilmektedir. Tam sayılar rastgele yapılar da olabilir.

Bu nedenle, şablonlaştırılmış BpfMap sınıfı, belirli haritaya uygun özel bir BpfMap nesnesi tanımlamanıza olanak tanır. Haritaya daha sonra, tür bilgisi içeren ve daha temiz bir kod oluşturan özel olarak oluşturulmuş işlevler kullanılarak erişilebilir.

BpfMap hakkında daha fazla bilgi için Android kaynakları başlıklı makaleyi inceleyin.

Hataları ayıklama

Önyükleme sırasında BPF yüklemeyle ilgili çeşitli mesajlar kaydedilir. Yükleme işlemi herhangi bir nedenle başarısız olursa logcat'te ayrıntılı bir günlük mesajı sağlanır. Günlükleri bpf ile filtrelemek, tüm mesajları ve yükleme sırasındaki ayrıntılı hataları (ör. eBPF doğrulayıcı hataları) yazdırır.

Android'de eBPF örnekleri

AOSP'deki aşağıdaki programlar, eBPF kullanımına ilişkin ek örnekler sunar:

  • netd eBPF C programı, Android'de ağ arka plan programı (netd) tarafından soket filtreleme ve istatistik toplama gibi çeşitli amaçlarla kullanılır. Bu programın nasıl kullanıldığını görmek için eBPF trafik izleme kaynaklarını kontrol edin.

  • time_in_state eBPF C programı, bir Android uygulamasının farklı CPU frekanslarında geçirdiği süreyi hesaplar. Bu süre, gücü hesaplamak için kullanılır.

  • Android 12'de gpu_mem eBPF C programı her işlem ve tüm sistem için toplam GPU bellek kullanımını izler. Bu program, GPU bellek profili oluşturma için kullanılır.