Ç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 araştırmalara veya olaylara bağlanabilir ve yararlı çekirdek istatistikleri toplamak, izlemek ve hata ayıklamak için kullanılabilir. Bir program bpf(2) sistem çağrısını kullanarak çekirdeğe yüklenir ve kullanıcı tarafından eBPF makine talimatlarının ikili bloğu olarak sağlanır. Android derleme sistemi, bu belgede açıklanan basit derleme dosyası sözdizimini kullanarak C programlarını eBPF'ye derleme desteğine sahiptir.

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

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

Android BPF yükleyici

Android önyüklemesi sırasında /system/etc/bpf/ konumunda bulunan 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 sunulur. Yapı sistemi, oluşturulan nesneleri /system/etc/bpf konumunda saklar ve bu nesneler sistem görüntüsünün parçası haline gelir.

Android eBPF C programının formatı

Bir eBPF C programı aşağıdaki formatta 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 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

Nerede:

  • name_of_my_map harita değişkeninizin adıdır. Bu ad, BPF yükleyicisine haritanın türü ve hangi parametrelerle oluşturulacağı konusunda bilgi verir. Bu yapı tanımı, dahil edilen bpf_helpers.h başlığı tarafından sağlanır.
  • PROGTYPE/PROGNAME programın türünü ve program adını temsil eder. Programın türü aşağıdaki tabloda listelenenlerden herhangi biri olabilir. Bir program türü listelenmediğinde, program için kesin bir adlandırma kuralı yoktur; adın yalnızca programı ekleyen süreç tarafından bilinmesi gerekir.

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

kprobe PROGFUNC kprobe altyapısını kullanarak bir çekirdek talimatına bağlar. PROGNAME kproblanan çekirdek fonksiyonunun adı olmalıdır. Kprobe'lar hakkında daha fazla bilgi için kprobe çekirdek belgelerine bakın.
izleme noktası PROGFUNC bir izleme noktasına bağlar. PROGNAME ALT SUBSYSTEM/EVENT biçiminde olmalıdır. Örneğin, zamanlayıcı bağlam anahtarı olaylarına işlevler eklemek için bir izleme noktası bölümü SEC("tracepoint/sched/sched_switch") olacaktır; burada sched , izleme alt sisteminin adıdır ve sched_switch , izleme olayının adıdır. İzleme noktaları hakkında daha fazla bilgi için izleme olayları çekirdeği belgelerine bakın.
sk filtresi Program bir ağ soketi filtresi olarak işlev görür.
programlar Program bir ağ trafiği sınıflandırıcısı olarak işlev görür.
cgroupskb, cgroupsock Program, bir CGruptaki işlemler bir AF_INET veya AF_INET6 soketi oluşturduğunda çalışır.

Ek türler Yükleyici kaynak kodunda bulunabilir.

Örneğin, aşağıdaki myschedtp.c programı, belirli bir CPU üzerinde çalışan en son görev PID'si hakkında bilgi ekler. Bu program amacına bir harita oluşturarak ve sched:sched_switch trace olayına eklenebilecek bir tp_sched_switch fonksiyonu tanımlayarak ulaşır. Daha fazla bilgi için bkz. İzleme noktalarına program ekleme .

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

LİSANS makrosu, program çekirdek tarafından sağlanan BPF yardımcı işlevlerini kullandığında programın çekirdeğin lisansıyla uyumlu olup olmadığını doğrulamak için kullanılır. Program lisansınızın adını LICENSE("GPL") veya LICENSE("Apache 2.0") gibi dize biçiminde belirtin.

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 adında 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 girdi, /system/etc/bpf/bpf_test.o nesnesiyle sonuçlanan C programını derler. Önyükleme sırasında Android sistemi bpf_test.o programını çekirdeğe otomatik olarak yükler.

Sysfs'de bulunan dosyalar

Önyükleme sırasında, Android sistemi tüm eBPF nesnelerini /system/etc/bpf/ adresinden 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 bunların 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ğunu varsayarsak, Android yükleyici her programı /sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME konumunda oluşturur ve sabitler.

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

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

    Örneğin, myschedtp.c dosyasındaki önceki sched_switch izleme noktası örneği için bir harita dosyası oluşturulur ve /sys/fs/bpf/map_myschedtp_cpu_pid_map dosyasına 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ı okumak veya bir programı bir izleme noktasına eklemek gibi daha ileri 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ıya haritalar oluşturmak ve okumak, problar, izleme noktaları ve mükemmel tamponlar oluşturmak için gereken düşük seviyeli eBPF işlevselliğini sağlar.

Programları izleme noktalarına ekleme

Tracepoint programları açılışta otomatik olarak yüklenir. Yüklemeden 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() ı çağırın. Daha fazla bilgi için sysfs'de bulunan Dosyalara bakın.
  2. BPF kitaplığında bpf_attach_tracepoint() çağırın ve ona fd programını ve izleme noktası adını iletin.

Aşağıdaki kod örneği, önceki myschedtp.c kaynak dosyasında tanımlanan sched_switch izleme noktasının nasıl ekleneceğini gösterir (hata denetimi 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 harita için anahtar ve değer türüne göre BpfMap başlatmak için C++ şablonlarından yararlanan bir android::BpfMap sınıfı içerir. Önceki kod örneği, anahtar ve değerin tamsayı olarak kullanıldığı bir BpfMap kullanımını göstermektedir. Tamsayılar aynı zamanda keyfi yapılar da olabilir.

Böylece şablonlaştırılmış BpfMap sınıfı, belirli bir haritaya uygun özel bir BpfMap nesnesinin tanımlanmasını kolaylaştırır. Daha sonra haritaya, tür farkında olan ve daha temiz kod sağlayan özel olarak oluşturulmuş işlevler kullanılarak erişilebilir.

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

Hata ayıklama sorunları

Önyükleme sırasında BPF yüklemesiyle ilgili çeşitli mesajlar günlüğe kaydedilir. Yükleme işlemi herhangi bir nedenle başarısız olursa logcat'te ayrıntılı bir log mesajı sağlanır. Logcat günlüklerini "bpf" ile filtrelemek, tüm mesajları ve eBPF doğrulayıcı hataları gibi yükleme süresi sırasındaki tüm ayrıntılı hataları yazdırır.

Android'deki eBPF örnekleri

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

  • netd eBPF C programı, Android'deki ağ oluşturma arka plan programı (netd) tarafından soket filtreleme ve istatistik toplama gibi çeşitli amaçlar için 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 gücü hesaplamak için kullanılan farklı CPU frekanslarında harcadığı süreyi hesaplar.

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