Çekirdeği eBPF ile genişletme

Extended Berkeley Packet Filter (eBPF), çekirdek işlevlerini genişletmek için kullanıcı tarafından sağlanan eBPF programlarını çalıştıran bir çekirdek içi sanal makinedir. Bu programlar, çekirdekteki sondalara veya etkinliklere bağlanabilir ve yararlı çekirdek istatistiklerini toplamak, izlemek ve hata ayıklamak için kullanılabilir. Program, bpf(2) sistem çağrısı kullanılarak çekirdeğe yüklenir ve kullanıcı tarafından eBPF makine talimatlarının ikili blob'u 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 dahili özellikleri 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ık içerir.

Android BPF yükleyici

Android başlatılırken /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 bulunur. Derleme sistemi, oluşturulan nesneleri /system/etc/bpf konumunda depolar ve bu nesneler sistem görüntüsünün bir parçası olur.

Android eBPF C programının biçimi

Bir eBPF C programı şu 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ükleyiciye hangi tür haritanın hangi parametrelerle oluşturulacağını bildirir. Bu yapı tanımı, dahil edilen bpf_helpers.h başlığı tarafından sağlanır.
  • PROGTYPE/PROGNAME, programın türünü ve adını temsil eder. Program türü, aşağıdaki tabloda listelenenlerden herhangi biri olabilir. Bir program türü listelenmediğinde program için katı bir adlandırma kuralı yoktur. Adın, programı ekleyen işlem tarafından bilinmesi yeterlidir.

  • 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, kprobe uygulanan çekirdek işlevinin adı olmalıdır. kprobe'lar hakkında daha fazla bilgi için kprobe çekirdek belgelerine bakın.
izleme noktası Bir izleme noktasına PROGFUNC bağlanır. PROGNAME, SUBSYSTEM/EVENT biçiminde olmalıdır. Örneğin, işlevleri planlayıcı bağlam değiştirme etkinliklerine eklemek için 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 izleme etkinlikleri çekirdek belgelerine bakın.
skfilter Program, ağ soketi filtresi olarak çalışı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.

Diğer türleri Loader kaynak kodunda bulabilirsiniz.

Örneğin, aşağıdaki myschedtp.c programı, belirli bir CPU'da çalışan en son görev PID'si hakkında bilgi ekler. Bu program, bir harita oluşturarak ve sched:sched_switch izleme etkinliğine eklenebilecek bir tp_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ın çekirdek tarafından sağlanan BPF yardımcı işlevlerini kullandığı durumlarda 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ına bir giriş eklemeniz 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 ekleyin:

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

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

sysfs'de bulunan 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ı olduğu varsayıldığında) 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 dizinine sabitlenir.

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

    Örneğin, myschedtp.c içindeki önceki sched_switch izleme noktası örneği için bir eşleme dosyası oluşturulur ve /sys/fs/bpf/map_myschedtp_cpu_pid_map dizinine sabitlenir.

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

Android BPF kitaplığı

Android BPF kitaplığının adı libbpf_android.so olup sistem görüntüsünün bir parçasıdır. Bu kitaplık, kullanıcıya harita oluşturma ve okuma, problar, izleme noktaları ve performans arabellekleri oluşturma için gereken alt düzey eBPF özelliklerini sağlar.

İzleme noktalarına program ekleme

Tracepoint programları, başlatma sırasında otomatik olarak yüklenir. Yükleme işleminden sonra, izleme noktası programı aşağıdaki adımlar kullanılarak etkinleştirilmelidir:

  1. Sabitlenen dosyanın konumundan fd programını almak için bpf_obj_get() işlevini çağırı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ı veya türlerini destekler. Android BPF kitaplığı, söz konusu eşlemenin anahtar ve değer türüne göre BpfMap örneklendirmek için C++ şablonlarını kullanan bir android::BpfMap sınıfı içerir. Önceki kod örneğinde, anahtar ve değerin tam sayı olarak 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 bir haritaya uygun özel bir BpfMap nesnesi tanımlamanıza olanak tanır. Ardından, türe duyarlı olan özel olarak oluşturulmuş işlevler kullanılarak haritaya erişilebilir. Bu da daha temiz bir kod sağlar.

BpfMap hakkında daha fazla bilgi için Android kaynaklarına bakın.

Hataları ayıklama

Başlatma sırasında, BPF yüklemeyle ilgili birkaç mesaj günlüğe 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. Logcat günlüklerini bpf ile filtrelemek, tüm mesajları ve yükleme süresindeki 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ıyla ilgili ek örnekler sunar:

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

  • 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üç hesaplamasında 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 belleği profil oluşturma için kullanılır.

statsd için düşük ek yük içeren çekirdek bellek yönetimi ve güç gecikmesi histogramları toplamak üzere eBPF'yi kullanma hakkında daha fazla bilgi edinmek istiyorsanız eBPF ile çekirdek telemetrisi toplama başlıklı makaleyi inceleyin.