Pengenalan Ringkas kepada BPF dan eBPF

Hello, Habr! Kami ingin memaklumkan kepada anda bahawa kami sedang menyediakan buku untuk dikeluarkan."Kebolehcerapan Linux dengan BPF".

Pengenalan Ringkas kepada BPF dan eBPF
Memandangkan mesin maya BPF terus berkembang dan digunakan secara aktif dalam amalan, kami telah menterjemahkan untuk anda artikel yang menerangkan keupayaan utama dan keadaan semasanya.

Dalam beberapa tahun kebelakangan ini, alat dan teknik pengaturcaraan telah menjadi semakin popular untuk mengimbangi batasan kernel Linux dalam kes di mana pemprosesan paket berprestasi tinggi diperlukan. Salah satu teknik yang paling popular seperti ini dipanggil pintasan kernel (pintasan kernel) dan membenarkan, memintas lapisan rangkaian kernel, untuk melaksanakan semua pemprosesan paket dari ruang pengguna. Memintas kernel juga melibatkan mengawal kad rangkaian daripada ruang pengguna. Dalam erti kata lain, apabila bekerja dengan kad rangkaian, kami bergantung pada pemandu ruang pengguna.

Dengan memindahkan kawalan penuh kad rangkaian kepada program ruang pengguna, kami mengurangkan overhed kernel (penukaran konteks, pemprosesan lapisan rangkaian, gangguan, dll.), yang agak penting apabila berjalan pada kelajuan 10Gb/s atau lebih tinggi. Pintasan kernel ditambah gabungan ciri lain (pemprosesan kelompok) dan penalaan prestasi yang teliti (perakaunan NUMA, Pengasingan CPU, dsb.) sesuai dengan asas pemprosesan rangkaian berprestasi tinggi dalam ruang pengguna. Mungkin contoh teladan pendekatan baharu untuk pemprosesan paket ini ialah DPDK daripada Intel (Kit Pembangunan Pesawat Data), walaupun terdapat alat dan teknik terkenal lain, termasuk Cisco's VPP (Vector Packet Processing), Netmap dan, sudah tentu, Berdendam.

Mengatur interaksi rangkaian dalam ruang pengguna mempunyai beberapa kelemahan:

  • Kernel OS ialah lapisan abstraksi untuk sumber perkakasan. Oleh kerana program ruang pengguna perlu mengurus sumber mereka secara langsung, mereka juga perlu mengurus perkakasan mereka sendiri. Ini selalunya bermakna perlu memprogramkan pemandu anda sendiri.
  • Kerana kami melepaskan ruang kernel sepenuhnya, kami juga menyerahkan semua fungsi rangkaian yang disediakan oleh kernel. Program ruang pengguna mesti mengimplementasikan semula ciri yang mungkin telah disediakan oleh kernel atau sistem pengendalian.
  • Program beroperasi dalam mod kotak pasir, yang secara serius mengehadkan interaksi mereka dan menghalangnya daripada menyepadukan dengan bahagian lain sistem pengendalian.

Pada dasarnya, apabila rangkaian dalam ruang pengguna, keuntungan prestasi dicapai dengan memindahkan pemprosesan paket dari kernel ke ruang pengguna. XDP melakukan sebaliknya: ia memindahkan program rangkaian dari ruang pengguna (penapis, penyelesai, penghalaan, dll.) ke ruang kernel. XDP membolehkan kami melaksanakan fungsi rangkaian sebaik sahaja paket mencecah antara muka rangkaian dan sebelum ia mula bergerak ke atas ke dalam subsistem rangkaian kernel. Akibatnya, kelajuan pemprosesan paket meningkat dengan ketara. Walau bagaimanapun, bagaimanakah kernel membenarkan pengguna untuk melaksanakan program mereka dalam ruang kernel? Sebelum menjawab soalan ini, mari kita lihat apa itu BPF.

BPF dan eBPF

Walaupun nama yang mengelirukan, BPF (Penapisan Paket Berkeley) sebenarnya, model mesin maya. Mesin maya ini pada asalnya direka untuk mengendalikan penapisan paket, oleh itu namanya.

Salah satu alat yang paling terkenal menggunakan BPF ialah tcpdump. Apabila menangkap paket menggunakan tcpdump pengguna boleh menentukan ungkapan untuk menapis paket. Hanya paket yang sepadan dengan ungkapan ini akan ditangkap. Contohnya, ungkapan β€œtcp dst port 80” merujuk kepada semua paket TCP yang tiba pada port 80. Pengkompil boleh memendekkan ungkapan ini dengan menukarkannya kepada kod bait BPF.

$ 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

Inilah yang pada dasarnya dilakukan oleh program di atas:

  • Arahan (000): Memuatkan paket pada offset 12, sebagai perkataan 16-bit, ke dalam penumpuk. Offset 12 sepadan dengan jenis eter paket.
  • Arahan (001): membandingkan nilai dalam penumpuk dengan 0x86dd, iaitu, dengan nilai ethertype untuk IPv6. Jika hasilnya benar, maka kaunter program pergi ke arahan (002), dan jika tidak, maka ke (006).
  • Arahan (006): membandingkan nilai dengan 0x800 (nilai ethertype untuk IPv4). Jika jawapannya benar, maka program pergi ke (007), jika tidak, maka ke (015).

Dan seterusnya sehingga program penapisan paket mengembalikan hasil. Ini biasanya Boolean. Mengembalikan nilai bukan sifar (arahan (014)) bermakna paket itu diterima, dan mengembalikan nilai sifar (arahan (015)) bermakna paket itu tidak diterima.

Mesin maya BPF dan kod baitnya telah dicadangkan oleh Steve McCann dan Van Jacobson pada akhir 1992 apabila kertas kerja mereka diterbitkan Penapis Paket BSD: Seni Bina Baharu untuk Tangkapan Paket Peringkat Pengguna, teknologi ini pertama kali dibentangkan pada persidangan Usenix pada musim sejuk 1993.

Oleh kerana BPF ialah mesin maya, ia mentakrifkan persekitaran di mana program dijalankan. Selain kod bait, ia juga mentakrifkan model memori kelompok (arahan beban digunakan secara tersirat pada kelompok), daftar (A dan X; daftar penumpuk dan indeks), storan memori gores dan pembilang program tersirat. Menariknya, kod bait BPF dimodelkan selepas Motorola 6502 ISA. Seperti yang diingati oleh Steve McCann dalam bukunya laporan pleno di Sharkfest '11, dia sudah biasa dengan build 6502 daripada pengaturcaraan zaman sekolah menengahnya pada Apple II, dan pengetahuan ini mempengaruhi kerjanya mereka bentuk bytecode BPF.

Sokongan BPF dilaksanakan dalam kernel Linux dalam versi v2.5 dan lebih tinggi, ditambah terutamanya oleh usaha Jay Schullist. Kod BPF kekal tidak berubah sehingga 2011, apabila Eric Dumaset mereka bentuk semula penterjemah BPF untuk dijalankan dalam mod JIT (Sumber: JIT untuk penapis paket). Selepas ini, kernel, bukannya mentafsir kod bait BPF, boleh secara langsung menukar program BPF kepada seni bina sasaran: x86, ARM, MIPS, dsb.

Kemudian, pada tahun 2014, Alexey Starovoitov mencadangkan mekanisme JIT baharu untuk BPF. Malah, JIT baharu ini menjadi seni bina berasaskan BPF baharu dan dipanggil eBPF. Saya rasa kedua-dua VM wujud bersama untuk beberapa waktu, tetapi pada masa ini penapisan paket dilaksanakan berdasarkan eBPF. Malah, dalam banyak contoh dokumentasi moden, BPF difahamkan sebagai eBPF, dan BPF klasik hari ini dikenali sebagai cBPF.

eBPF memanjangkan mesin maya BPF klasik dalam beberapa cara:

  • Berdasarkan seni bina 64-bit moden. eBPF menggunakan daftar 64-bit dan meningkatkan bilangan daftar yang tersedia daripada 2 (akumulator dan X) kepada 10. eBPF juga menyediakan opcode tambahan (BPF_MOV, BPF_JNE, BPF_CALL...).
  • Terpisah daripada subsistem lapisan rangkaian. BPF terikat pada model data kelompok. Memandangkan ia digunakan untuk penapisan paket, kodnya terletak dalam subsistem yang menyediakan komunikasi rangkaian. Walau bagaimanapun, mesin maya eBPF tidak lagi terikat dengan model data dan boleh digunakan untuk sebarang tujuan. Jadi, kini program eBPF boleh disambungkan ke tracepoint atau kprobe. Ini membuka jalan kepada instrumentasi eBPF, analisis prestasi dan banyak kes penggunaan lain dalam konteks subsistem kernel lain. Kini kod eBPF terletak di laluannya sendiri: kernel/bpf.
  • Simpanan data global yang dipanggil Peta. Peta ialah stor nilai kunci yang membolehkan pertukaran data antara ruang pengguna dan ruang kernel. eBPF menyediakan beberapa jenis peta.
  • Fungsi sekunder. Khususnya, untuk menulis semula pakej, mengira jumlah semak atau mengklon pakej. Fungsi ini dijalankan di dalam kernel dan bukan program ruang pengguna. Anda juga boleh membuat panggilan sistem daripada program eBPF.
  • Tamatkan panggilan. Saiz program dalam eBPF adalah terhad kepada 4096 bait. Ciri panggilan ekor membenarkan program eBPF memindahkan kawalan kepada program eBPF baharu dan dengan itu memintas had ini (sehingga 32 program boleh dipautkan dengan cara ini).

eBPF: contoh

Terdapat beberapa contoh untuk eBPF dalam sumber kernel Linux. Mereka boleh didapati di sampel/bpf/. Untuk menyusun contoh ini, hanya masukkan:

$ sudo make samples/bpf/

Saya tidak akan menulis contoh baharu untuk eBPF sendiri, tetapi akan menggunakan salah satu sampel yang tersedia dalam sampel/bpf/. Saya akan melihat beberapa bahagian kod dan menerangkan cara ia berfungsi. Sebagai contoh, saya memilih program ini tracex4.

Secara umum, setiap contoh dalam sampel/bpf/ terdiri daripada dua fail. Dalam kes ini:

  • tracex4_kern.c, mengandungi kod sumber yang akan dilaksanakan dalam kernel sebagai kod bait eBPF.
  • tracex4_user.c, mengandungi atur cara dari ruang pengguna.

Dalam kes ini, kita perlu menyusun tracex4_kern.c kepada kod bait eBPF. Pada masa ini dalam gcc tiada hujung belakang untuk eBPF. Nasib baik, clang boleh mengeluarkan kod bait eBPF. Makefile kegunaan clang untuk kompilasi tracex4_kern.c kepada fail objek.

Saya nyatakan di atas bahawa salah satu ciri eBPF yang paling menarik ialah peta. tracex4_kern mentakrifkan 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 adalah salah satu daripada banyak jenis kad yang ditawarkan oleh eBPF. Dalam kes ini, ia hanyalah cincangan. Anda juga mungkin perasan iklan SEC("maps"). SEC ialah makro yang digunakan untuk mencipta bahagian baharu fail binari. Sebenarnya, dalam contoh tracex4_kern dua bahagian lagi ditakrifkan:

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

Kedua-dua fungsi ini membolehkan anda memadamkan entri daripada peta (kprobe/kmem_cache_free) dan tambahkan entri baharu pada peta (kretprobe/kmem_cache_alloc_node). Semua nama fungsi yang ditulis dengan huruf besar sepadan dengan makro yang ditakrifkan dalam bpf_helpers.h.

Jika saya membuang bahagian fail objek, saya harus melihat bahawa bahagian baharu ini telah ditakrifkan:

$ 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

Terdapat juga tracex4_user.c, program utama. Pada asasnya, program ini mendengar acara kmem_cache_alloc_node. Apabila peristiwa sedemikian berlaku, kod eBPF yang sepadan akan dilaksanakan. Kod tersebut menyimpan atribut IP objek ke dalam peta, dan objek itu kemudiannya digelung melalui program utama. Contoh:

$ 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

Bagaimanakah program angkasa pengguna dan program eBPF berkaitan? Pada permulaan tracex4_user.c memuatkan fail objek tracex4_kern.o menggunakan fungsi tersebut load_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;
}

Dengan melakukan load_bpf_file probe yang ditakrifkan dalam fail eBPF ditambahkan pada /sys/kernel/debug/tracing/kprobe_events. Sekarang kami mendengar acara ini dan program kami boleh melakukan sesuatu apabila ia berlaku.

$ 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

Semua program lain dalam sampel/bpf/ distrukturkan sama. Mereka sentiasa mengandungi dua fail:

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

Program eBPF mengenal pasti peta dan fungsi yang dikaitkan dengan bahagian. Apabila kernel mengeluarkan acara jenis tertentu (contohnya, tracepoint), fungsi terikat dilaksanakan. Kad menyediakan komunikasi antara program kernel dan program ruang pengguna.

Kesimpulan

Artikel ini membincangkan BPF dan eBPF secara umum. Saya tahu terdapat banyak maklumat dan sumber tentang eBPF hari ini, jadi saya akan mengesyorkan beberapa lagi sumber untuk kajian lanjut

Saya mengesyorkan membaca:

Sumber: www.habr.com

Tambah komen