Pengantar singkat tentang BPF dan eBPF

Halo, Habr! Kami memberi tahu Anda bahwa kami sedang bersiap untuk merilis buku " Linux Observability with BPF ".


Ketika mesin virtual BPF terus berkembang dan diterapkan secara aktif dalam praktiknya, kami telah menerjemahkan sebuah artikel untuk Anda yang menjelaskan fitur-fitur utama dan status saat ini.

Dalam beberapa tahun terakhir, alat dan teknik pemrograman mulai mendapatkan popularitas, yang dirancang untuk mengompensasi keterbatasan kernel Linux dalam kasus-kasus di mana pemrosesan paket berkinerja tinggi diperlukan. Salah satu metode yang paling populer dari jenis ini disebut bypassing kernel (kernel bypass) dan memungkinkan lewat core lapisan jaringan melakukan semua pemrosesan paket dari ruang pengguna. Melewati kernel juga melibatkan pengelolaan kartu jaringan dari ruang pengguna . Dengan kata lain, ketika bekerja dengan kartu jaringan, kami bergantung pada driver ruang pengguna .

Dengan mentransfer kendali penuh atas kartu jaringan ke program dari ruang pengguna, kami mengurangi biaya yang terkait dengan kernel (pengalihan konteks, pemrosesan tingkat jaringan, interupsi, dll.), Yang cukup penting ketika bekerja pada kecepatan 10Gb / dtk atau lebih tinggi. Pemintas kernel ditambah kombinasi fitur lain ( pemrosesan batch ) dan penyetelan kinerja yang akurat ( penghitungan NUMA , isolasi CPU , dll.) Sesuai dengan dasar-dasar pemrosesan jaringan berkinerja tinggi di ruang pengguna. Mungkin contoh model dari pendekatan baru dalam pemrosesan paket ini adalah DPDK ( Kit Pengembangan Data Pesawat) Intel), meskipun ada alat dan teknik terkenal lainnya, termasuk VPP dari Cisco (Vector Packet Processing), Netmap dan, tentu saja, Snabb .

Organisasi interaksi jaringan di ruang pengguna memiliki sejumlah kelemahan:

  • Kernel OS adalah tingkat abstraksi untuk sumber daya perangkat keras. Karena program ruang pengguna harus mengelola sumber dayanya secara langsung, mereka juga harus mengelola perangkat keras mereka sendiri. Ini sering berarti bahwa Anda perlu memprogram driver Anda sendiri.
  • , , . , , , .
  • , .

Intinya, ketika mengatur interaksi jaringan di ruang pengguna, peningkatan produktivitas dicapai dengan mentransfer pemrosesan paket dari kernel ke ruang pengguna. XDP melakukan yang sebaliknya: memindahkan program jaringan dari ruang pengguna (filter, konverter, perutean, dll.) Ke area kernel. XDP memungkinkan kita untuk melakukan fungsi jaringan segera setelah paket tiba di antarmuka jaringan dan sebelum mulai bergerak naik ke subsistem jaringan kernel. Akibatnya, kecepatan pemrosesan paket meningkat secara signifikan. Namun, bagaimana kernel memungkinkan pengguna untuk menjalankan program mereka di ruang kernel? Sebelum menjawab pertanyaan ini, mari kita lihat apa itu BPF.

BPF dan eBPF

Meskipun nama BPF tidak begitu jelas (Packet Filtering, Berkeley), itu sebenarnya adalah model mesin virtual. Mesin virtual ini awalnya dirancang untuk menangani penyaringan paket, oleh karena itu namanya.

Salah satu alat paling terkenal menggunakan BPF adalah tcpdump. Saat mengambil paket dengan, tcpdumppengguna dapat menentukan ekspresi untuk memfilter paket. Hanya paket yang cocok dengan ungkapan ini yang akan ditangkap. Sebagai contoh, ekspresi " tcp dst port 80" berlaku untuk semua paket TCP yang tiba di port 80. Kompiler dapat mempersingkat ekspresi ini dengan mengubahnya menjadi bytecode BPF. Inilah yang pada dasarnya dilakukan oleh program di atas:

$ sudo tcpdump -d "tcp dst port 80"
(000) ldh [12]
(001) jeq #0x86dd jt 2 jf 6
(002) ldb [20]
(003) jeq #0x6 jt 4 jf 15
(004) ldh [56]
(005) jeq #0x50 jt 14 jf 15
(006) jeq #0x800 jt 7 jf 15
(007) ldb [23]
(008) jeq #0x6 jt 9 jf 15
(009) ldh [20]
(010) jset #0x1fff jt 15 jf 11
(011) ldxb 4*([14]&0xf)
(012) ldh [x + 16]
(013) jeq #0x50 jt 14 jf 15
(014) ret #262144
(015) ret #0




  • Instruction (000): mengunduh paket pada offset 12 sebagai kata 16-bit ke dalam baterai. Offset 12 sesuai dengan paket ethertype.
  • (001): 0x86dd, , ethertype- IPv6. true, (002), – (006).
  • (006): 0x800 (ethertype- IPv4). true, (007), – (015).

Dan seterusnya, hingga program penyaringan paket mengembalikan hasilnya. Ini biasanya bulean. Mengembalikan nilai bukan nol (instruksi (014)) berarti bahwa paket telah mendekati, dan mengembalikan nol (instruksi (015)) berarti bahwa paket belum tiba.

Mesin virtual BPF dan kode byanya diusulkan oleh Steve McCann dan Van Jacobson pada akhir 1992 ketika artikel mereka BSD Packet Filter: Arsitektur Baru untuk Pengambilan Paket pada Tingkat Pengguna , pertama kali diperkenalkan pada konferensi Usenix pada musim dingin tahun 1993.

Karena BPF adalah mesin virtual, BPF mendefinisikan lingkungan tempat program dijalankan. Selain bytecode, ia juga mendefinisikan model memori batch (instruksi boot secara implisit diterapkan pada paket), register (A dan X; register baterai dan indeks), penyimpanan memori awal, dan penghitung program implisit. Menariknya, bytecode BPF dimodelkan setelah Motorola 6502 ISA. Seperti yang diingat Steve McCann dalam pidato pleno di Sharkfest '11, ia telah terbiasa dengan bangunan 6502 sejak sekolah menengah ketika ia memprogram di Apple II, dan pengetahuan ini memengaruhi pekerjaannya dalam merancang bytecode BPF.

Dukungan untuk BPF diimplementasikan dalam kernel Linux dalam versi v2.5 dan lebih tinggi, ditambahkan terutama oleh upaya Jay Schullist. Kode BPF tetap tidak berubah hingga 2011, ketika Eric Dumazett redid penerjemah BPF untuk bekerja dalam mode JIT (Sumber: JIT untuk filter paket ). Setelah itu, kernel, alih-alih menginterpretasikan kode byte BPF, bisa langsung mengonversi program BPF ke arsitektur target: x86, ARM, MIPS, dll.

Kemudian, pada 2014, Alexey Starovoitov mengusulkan mekanisme JIT baru untuk BPF. Bahkan, JIT baru ini telah menjadi arsitektur berbasis BPF baru dan disebut eBPF. Saya berpikir bahwa untuk beberapa waktu kedua mesin virtual hidup berdampingan, tetapi saat ini packet filtering diimplementasikan berdasarkan eBPF. Bahkan, dalam banyak contoh dokumentasi modern, BPF dipahami sebagai eBPF, dan BPF klasik sekarang dikenal sebagai cBPF.

eBPF memperluas mesin virtual BPF klasik dalam beberapa cara:

  • Berdasarkan arsitektur 64-bit modern. eBPF menggunakan register 64-bit dan meningkatkan jumlah register yang tersedia dari 2 (baterai dan X) menjadi 10. eBPF juga menyediakan kode operasi tambahan (BPF_MOV, BPF_JNE, BPF_CALL ...).
  • . BPF . , , . , eBPF . , eBPF tracepoint kprobe. eBPF, . eBPF : kernel/bpf.
  • . – «-», . eBPF .
  • . , , . . , eBPF .
  • . eBPF 4096 . eBPF eBPF- ( 32 ).

eBPF: Sebuah Contoh

Ada beberapa contoh untuk eBPF di sumber kernel Linux. Mereka tersedia di sampel / bpf /. Untuk mengkompilasi contoh-contoh ini, cukup masukkan:

$ sudo make samples/bpf/

Saya sendiri tidak akan menulis contoh baru untuk eBPF, tetapi akan menggunakan salah satu sampel yang tersedia dalam sampel / bpf /. Saya akan melihat beberapa bagian dari kode dan menjelaskan cara kerjanya. Sebagai contoh, saya memilih program tracex4.

Secara umum, masing-masing contoh dalam sampel / bpf / terdiri dari dua file. Pada kasus ini:

  • tracex4_kern.c, berisi kode sumber yang harus dijalankan di kernel sebagai bytecode eBPF.
  • tracex4_user.c, berisi program dari ruang pengguna.

Dalam hal ini, kita perlu mengkompilasi tracex4_kern.ceBPF ke dalam bytecode. Saat ini gcctidak ada bagian server untuk eBPF. Untungnya, ia clangbisa mengeluarkan bytecode eBPF. Makefilegunakan clanguntuk mengkompilasi tracex4_kern.cfile objek.

Saya sebutkan di atas bahwa salah satu fitur paling menarik dari eBPF adalah kartu. tracex4_kern mendefinisikan satu peta:

struct pair {
    u64 val;
    u64 ip;
};  

struct bpf_map_def SEC("maps") my_map = {
    .type = BPF_MAP_TYPE_HASH,
    .key_size = sizeof(long),
    .value_size = sizeof(struct pair),
    .max_entries = 1000000,
};

BPF_MAP_TYPE_HASH- Salah satu dari banyak jenis kartu yang ditawarkan oleh eBPF. Dalam hal ini, itu hanya hash. Anda mungkin juga memperhatikan iklan SEC("maps"). SEC adalah makro yang digunakan untuk membuat bagian baru dari file biner. Sebenarnya, contoh tracex4_kernmendefinisikan dua bagian lagi:

SEC("kprobe/kmem_cache_free")
int bpf_prog1(struct pt_regs *ctx)
{   
    long ptr = PT_REGS_PARM2(ctx);

    bpf_map_delete_elem(&my_map, &ptr); 
    return 0;
}
    
SEC("kretprobe/kmem_cache_alloc_node") 
int bpf_prog2(struct pt_regs *ctx)
{
    long ptr = PT_REGS_RC(ctx);
    long ip = 0;

    //  ip-   kmem_cache_alloc_node() 
    BPF_KRETPROBE_READ_RET_IP(ip, ctx);

    struct pair v = {
        .val = bpf_ktime_get_ns(),
        .ip = ip,
    };
    
    bpf_map_update_elem(&my_map, &ptr, &v, BPF_ANY);
    return 0;
}   

Dua fungsi ini memungkinkan Anda untuk menghapus entri dari kartu ( kprobe/kmem_cache_free) dan menambahkan catatan baru ( kretprobe/kmem_cache_alloc_node) ke kartu . Semua nama fungsi yang ditulis dengan huruf besar sesuai dengan makro yang didefinisikan dalam bpf_helpers.h.

Jika saya menampilkan dump bagian dari file objek, saya akan melihat bahwa bagian-bagian baru ini sudah didefinisikan: Masih ada , program utama. Pada dasarnya, program ini mendengarkan acara . Ketika peristiwa semacam itu terjadi, kode eBPF yang sesuai dijalankan. Kode menyimpan atribut IP objek di peta, dan kemudian objek ini ditampilkan secara siklis di program utama. Contoh: Bagaimana program ruang pengguna dan program eBPF terkait? Pada inisialisasi, ini memuat file objek menggunakan fungsi .

$ objdump -h tracex4_kern.o

tracex4_kern.o: file format elf64-little

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000000 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 kprobe/kmem_cache_free 00000048 0000000000000000 0000000000000000 00000040 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
2 kretprobe/kmem_cache_alloc_node 000000c0 0000000000000000 0000000000000000 00000088 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
3 maps 0000001c 0000000000000000 0000000000000000 00000148 2**2
CONTENTS, ALLOC, LOAD, DATA
4 license 00000004 0000000000000000 0000000000000000 00000164 2**0
CONTENTS, ALLOC, LOAD, DATA
5 version 00000004 0000000000000000 0000000000000000 00000168 2**2
CONTENTS, ALLOC, LOAD, DATA
6 .eh_frame 00000050 0000000000000000 0000000000000000 00000170 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA


tracex4_user.ckmem_cache_alloc_node

$ sudo ./tracex4
obj 0xffff8d6430f60a00 is 2sec old was allocated at ip ffffffff9891ad90
obj 0xffff8d6062ca5e00 is 23sec old was allocated at ip ffffffff98090e8f
obj 0xffff8d5f80161780 is 6sec old was allocated at ip ffffffff98090e8f


tracex4_user.ctracex4_kern.oload_bpf_file

int main(int ac, char **argv)
{
    struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY};
    char filename[256];
    int i;

    snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);

    if (setrlimit(RLIMIT_MEMLOCK, &r)) {
        perror("setrlimit(RLIMIT_MEMLOCK, RLIM_INFINITY)");
        return 1;
    }

    if (load_bpf_file(filename)) {
        printf("%s", bpf_log_buf);
        return 1;
    }

    for (i = 0; ; i++) {
        print_old_objects(map_fd[1]);
        sleep(1);
    }

    return 0;
}

Ketika dieksekusi, load_bpf_fileprobe yang ditentukan dalam file eBPF ditambahkan ke /sys/kernel/debug/tracing/kprobe_events. Sekarang kami mendengarkan acara ini, dan program kami dapat melakukan sesuatu ketika itu terjadi. Semua program lain dalam sampel / bpf / disusun dengan cara yang sama. Mereka selalu memiliki dua file:

$ sudo cat /sys/kernel/debug/tracing/kprobe_events
p:kprobes/kmem_cache_free kmem_cache_free
r:kprobes/kmem_cache_alloc_node kmem_cache_alloc_node




  • XXX_kern.c: program eBPF.
  • XXX_user.c: program utama.

Program eBPF mendefinisikan kartu dan fungsi yang terkait dengan bagian tersebut. Ketika kernel melempar suatu kejadian dari tipe tertentu (misalnya, tracepoint), fungsi terikat dieksekusi. Peta menyediakan pertukaran data antara program kernel dan program ruang pengguna.

Kesimpulan

Artikel ini menguraikan BPF dan eBPF. Saya tahu bahwa hari ini ada banyak informasi dan sumber daya tentang eBPF, jadi saya merekomendasikan beberapa bahan untuk studi lebih lanjut. Saya

sarankan membaca:


All Articles