ÄŖss ievads par BPF un eBPF

Sveiks, Habr! Vēlamies jÅ«s informēt, ka gatavojam grāmatu izdoÅ”anai."Linux novērojamÄ«ba ar BPF".

ÄŖss ievads par BPF un eBPF
Tā kā BPF virtuālā maŔīna turpina attÄ«stÄ«ties un tiek aktÄ«vi izmantota praksē, mēs esam iztulkojuÅ”i jums rakstu, kurā aprakstÄ«tas tās galvenās iespējas un paÅ”reizējais stāvoklis.

Pēdējos gados programmÄ“Å”anas rÄ«ki un metodes ir kļuvuÅ”as arvien populārākas, lai kompensētu Linux kodola ierobežojumus gadÄ«jumos, kad nepiecieÅ”ama augstas veiktspējas pakeÅ”u apstrāde. Viens no populārākajiem Ŕāda veida paņēmieniem tiek saukts kodola apieÅ”ana (kodola apieÅ”ana) un ļauj, apejot kodola tÄ«kla slāni, veikt visu pakeÅ”u apstrādi no lietotāja vietas. Kodola apieÅ”ana ietver arÄ« tÄ«kla kartes kontroli no lietotāja telpa. Citiem vārdiem sakot, strādājot ar tÄ«kla karti, mēs paļaujamies uz draiveri lietotāja telpa.

Nododot pilnu tÄ«kla kartes vadÄ«bu uz lietotāja telpas programmu, mēs samazinām kodola papildu izmaksas (konteksta pārslēgÅ”ana, tÄ«kla slāņa apstrāde, pārtraukumi utt.), kas ir diezgan svarÄ«gi, ja darbojas ar ātrumu 10Gb/s vai lielāku. Kodola apieÅ”ana un citu funkciju kombinācija (partijas apstrāde) un rÅ«pÄ«ga veiktspējas regulÄ“Å”ana (NUMA grāmatvedÄ«ba, CPU izolācijauc) atbilst augstas veiktspējas tÄ«kla apstrādes pamatiem lietotāju telpā. VarbÅ«t Ŕīs jaunās pieejas pakeÅ”u apstrādei piemērs ir DPDK no Intel (Datu plaknes izstrādes komplekts), lai gan ir arÄ« citi labi zināmi rÄ«ki un paņēmieni, tostarp Cisco VPP (Vector Packet Processing), Netmap un, protams, Snabb.

TÄ«kla mijiedarbÄ«bas organizÄ“Å”anai lietotāja telpā ir vairāki trÅ«kumi:

  • OS kodols ir aparatÅ«ras resursu abstrakcijas slānis. Tā kā lietotāju kosmosa programmām ir tieÅ”i jāpārvalda savi resursi, tām ir arÄ« jāpārvalda sava aparatÅ«ra. Tas bieži nozÄ«mē, ka paÅ”iem ir jāprogrammē draiveri.
  • Tā kā mēs pilnÄ«bā atsakāmies no kodola vietas, mēs atsakāmies arÄ« no visas kodola nodroÅ”inātās tÄ«kla funkcionalitātes. Lietotāja vietas programmām atkārtoti jāievieÅ” funkcijas, kuras jau var nodroÅ”ināt kodols vai operētājsistēma.
  • Programmas darbojas smilÅ”kastes režīmā, kas nopietni ierobežo to mijiedarbÄ«bu un neļauj tām integrēties ar citām operētājsistēmas daļām.

BÅ«tÄ«bā, veidojot tÄ«klu lietotāju telpā, veiktspējas pieaugums tiek sasniegts, pārvietojot pakeÅ”u apstrādi no kodola uz lietotāja telpu. XDP rÄ«kojas tieÅ”i pretēji: tas pārvieto tÄ«kla programmas no lietotāja vietas (filtri, atrisinātāji, marÅ”rutÄ“Å”ana utt.) kodola telpā. XDP ļauj mums veikt tÄ«kla funkciju, tiklÄ«dz pakete nonāk tÄ«kla saskarnē un pirms tā sāk pārvietoties uz kodola tÄ«kla apakÅ”sistēmu. Rezultātā pakeÅ”u apstrādes ātrums ievērojami palielinās. Tomēr kā kodols ļauj lietotājam izpildÄ«t savas programmas kodola telpā? Pirms atbildes uz Å”o jautājumu, apskatÄ«sim, kas ir BPF.

BPF un eBPF

Neskatoties uz mulsinoÅ”o nosaukumu, BPF (Berkeley Packet Filtering) patiesÄ«bā ir virtuālās maŔīnas modelis. Å Ä« virtuālā maŔīna sākotnēji tika izstrādāta, lai apstrādātu pakeÅ”u filtrÄ“Å”anu, tāpēc arÄ« nosaukums.

Viens no slavenākajiem rÄ«kiem, kas izmanto BPF, ir tcpdump. Tverot paketes, izmantojot tcpdump lietotājs var norādÄ«t izteiksmi pakeÅ”u filtrÄ“Å”anai. Tiks tvertas tikai paketes, kas atbilst Å”ai izteiksmei. Piemēram, izteiciens "tcp dst port 80ā€ attiecas uz visām TCP paketēm, kas nonāk portā 80. Kompilators var saÄ«sināt Å”o izteiksmi, pārvērÅ”ot to BPF baitkodā.

$ 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

IepriekÅ” minētā programma pamatā dara to:

  • Instrukcija (000): ielādē paketi 12. nobÄ«dē kā 16 bitu vārdu akumulatorā. NobÄ«de 12 atbilst paketes ētertipam.
  • Instrukcija (001): salÄ«dzina vērtÄ«bu akumulatorā ar 0x86dd, tas ir, ar ētera tipa vērtÄ«bu IPv6. Ja rezultāts ir patiess, programmas skaitÄ«tājs pāriet uz instrukciju (002), un, ja nē, tad uz (006).
  • Instrukcija (006): salÄ«dzina vērtÄ«bu ar 0x800 (ethertype vērtÄ«ba IPv4). Ja atbilde ir patiesa, programma pāriet uz (007), ja nē, tad uz (015).

Un tā tālāk, lÄ«dz pakeÅ”u filtrÄ“Å”anas programma atgriež rezultātu. Tas parasti ir BÅ«la vērtÄ«ba. VērtÄ«bas, kas nav nulle, atgrieÅ”ana (instrukcija (014)) nozÄ«mē, ka pakete tika pieņemta, un nulles vērtÄ«bas atgrieÅ”ana (instrukcija (015)) nozÄ«mē, ka pakete netika pieņemta.

BPF virtuālo maŔīnu un tās baitu kodu ierosināja StÄ«vs Makkans un Van Džeikobsons 1992. gada beigās, kad tika publicēts viņu raksts. BSD pakeÅ”u filtrs: jauna arhitektÅ«ra lietotāja lÄ«meņa pakeÅ”u tverÅ”anai, Ŕī tehnoloÄ£ija pirmo reizi tika prezentēta Usenix konferencē 1993. gada ziemā.

Tā kā BPF ir virtuāla maŔīna, tā nosaka vidi, kurā darbojas programmas. Papildus baitkodam tas definē arÄ« pakeÅ”atmiņas modeli (slodzes instrukcijas tiek netieÅ”i lietotas partijai), reÄ£istrus (A un X; akumulatoru un indeksu reÄ£istri), scratch atmiņas krātuvi un netieÅ”o programmu skaitÄ«tāju. Interesanti, ka BPF baitu kods tika veidots pēc Motorola 6502 ISA. Kā StÄ«vs Makkans atgādināja savā plenārsēdes ziņojums Sharkfest '11 viņŔ bija iepazinies ar build 6502 no vidusskolas laika programmÄ“Å”anas uz Apple II, un Ŕīs zināŔanas ietekmēja viņa darbu, izstrādājot BPF baitu kodu.

BPF atbalsts ir ieviests Linux kodolā versijās v2.5 un jaunākās versijās, ko galvenokārt pievienoja Džeja Å ullista pÅ«les. BPF kods palika nemainÄ«gs lÄ«dz 2011. gadam, kad Ēriks Dumasets pārveidoja BPF tulku, lai tas darbotos JIT režīmā (avots: JIT pakeÅ”u filtriem). Pēc tam kodols tā vietā, lai interpretētu BPF baitu kodu, varētu tieÅ”i konvertēt BPF programmas uz mērÄ·a arhitektÅ«ru: x86, ARM, MIPS utt.

Vēlāk, 2014. gadā, Aleksejs Starovoitovs ierosināja jaunu BPF JIT mehānismu. Faktiski Ŕī jaunā JIT kļuva par jaunu uz BPF balstÄ«tu arhitektÅ«ru, un to sauca par eBPF. Es domāju, ka abas virtuālās maŔīnas kādu laiku pastāvēja lÄ«dzās, bet Å”obrÄ«d pakeÅ”u filtrÄ“Å”ana tiek ieviesta, pamatojoties uz eBPF. Faktiski daudzos mÅ«sdienu dokumentācijas piemēros BPF tiek saprasts kā eBPF, un klasiskais BPF mÅ«sdienās ir pazÄ«stams kā cBPF.

eBPF paplaŔina klasisko BPF virtuālo maŔīnu vairākos veidos:

  • BalstÄ«ts uz modernām 64 bitu arhitektÅ«rām. eBPF izmanto 64 bitu reÄ£istrus un palielina pieejamo reÄ£istru skaitu no 2 (akumulators un X) lÄ«dz 10. eBPF nodroÅ”ina arÄ« papildu operācijas kodus (BPF_MOV, BPF_JNE, BPF_CALL...).
  • AtdalÄ«ts no tÄ«kla slāņa apakÅ”sistēmas. BPF bija saistÄ«ts ar partijas datu modeli. Tā kā tas tika izmantots pakeÅ”u filtrÄ“Å”anai, tā kods atradās apakÅ”sistēmā, kas nodroÅ”ina tÄ«kla sakarus. Tomēr eBPF virtuālā maŔīna vairs nav saistÄ«ta ar datu modeli un to var izmantot jebkuram mērÄ·im. Tātad, tagad eBPF programmu var savienot ar tracepoint vai kprobe. Tas paver ceļu uz eBPF instrumentiem, veiktspējas analÄ«zi un daudziem citiem lietoÅ”anas gadÄ«jumiem citu kodola apakÅ”sistēmu kontekstā. Tagad eBPF kods atrodas savā ceļā: kodols/bpf.
  • Globālie datu veikali, ko sauc par Maps. Kartes ir atslēgu vērtÄ«bu krātuves, kas nodroÅ”ina datu apmaiņu starp lietotāja vietu un kodola vietu. eBPF nodroÅ”ina vairāku veidu kartes.
  • Sekundārās funkcijas. Jo Ä«paÅ”i, lai pārrakstÄ«tu paketi, aprēķinātu kontrolsummu vai klonētu paketi. Å Ä«s funkcijas darbojas kodolā un nav lietotāja telpas programmas. Varat arÄ« veikt sistēmas zvanus no eBPF programmām.
  • Beigt zvanus. Programmas lielums eBPF ir ierobežots lÄ«dz 4096 baitiem. Astes izsaukuma funkcija ļauj eBPF programmai pārsÅ«tÄ«t vadÄ«bu uz jaunu eBPF programmu un tādējādi apiet Å”o ierobežojumu (Ŕādā veidā var saistÄ«t lÄ«dz 32 programmām).

eBPF: piemērs

Linux kodola avotos ir vairāki eBPF piemēri. Tie ir pieejami paraugi/bpf/. Lai apkopotu Å”os piemērus, vienkārÅ”i ievadiet:

$ sudo make samples/bpf/

Pats nerakstÄ«Å”u jaunu piemēru priekÅ” eBPF, bet izmantoÅ”u kādu no samples/bpf/ pieejamajiem paraugiem. Es apskatÄ«Å”u dažas koda daļas un paskaidroÅ”u, kā tas darbojas. Kā piemēru es izvēlējos programmu tracex4.

Kopumā katrs no paraugiem/bpf/ esoÅ”ajiem piemēriem sastāv no diviem failiem. Å ajā gadÄ«jumā:

  • tracex4_kern.c, satur avota kodu, kas jāizpilda kodolā kā eBPF baitkods.
  • tracex4_user.c, satur programmu no lietotāja vietas.

Šajā gadījumā mums ir jāapkopo tracex4_kern.c uz eBPF baitkodu. PaŔlaik atrodas gcc eBPF nav aizmugures. Par laimi, clang var izvadīt eBPF baitkodu. Makefile izmanto clang apkopoŔanai tracex4_kern.c uz objekta failu.

IepriekÅ” minēju, ka viena no interesantākajām eBPF funkcijām ir kartes. tracex4_kern definē vienu karti:

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 ir viens no daudzajiem eBPF piedāvāto karÅ”u veidiem. Å ajā gadÄ«jumā tas ir tikai hash. JÅ«s, iespējams, pamanÄ«jāt arÄ« sludinājumu SEC("maps"). SEC ir makro, ko izmanto, lai izveidotu jaunu binārā faila sadaļu. PatiesÄ«bā piemērā tracex4_kern ir noteiktas vēl divas sadaļas:

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

Šīs divas funkcijas ļauj izdzēst ierakstu no kartes (kprobe/kmem_cache_free) un pievienojiet kartei jaunu ierakstu (kretprobe/kmem_cache_alloc_node). Visi funkciju nosaukumi, kas rakstīti ar lielajiem burtiem, atbilst makro definētajiem bpf_helpers.h.

Ja es izmetu objekta faila sadaļas, man vajadzētu redzēt, ka Ŕīs jaunās sadaļas jau ir definētas:

$ 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

Ir arÄ« tracex4_user.c, galvenā programma. BÅ«tÄ«bā Ŕī programma klausās notikumus kmem_cache_alloc_node. Kad notiek Ŕāds notikums, tiek izpildÄ«ts atbilstoÅ”ais eBPF kods. Kods saglabā objekta IP atribÅ«tu kartē, un pēc tam objekts tiek cilpas caur galveno programmu. Piemērs:

$ 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

Kā lietotāja kosmosa programma un eBPF programma ir saistītas? Par inicializāciju tracex4_user.c ielādē objekta failu tracex4_kern.o izmantojot funkciju 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;
}

Darot load_bpf_file eBPF failā definētās zondes tiek pievienotas /sys/kernel/debug/tracing/kprobe_events. Tagad mēs klausāmies Å”os notikumus, un mÅ«su programma var kaut ko darÄ«t, kad tie notiek.

$ 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

Visas pārējās programmas paraugā/bpf/ ir strukturētas līdzīgi. Tajos vienmēr ir divi faili:

  • XXX_kern.c: eBPF programma.
  • XXX_user.c: galvenā programma.

eBPF programma identificē kartes un funkcijas, kas saistÄ«tas ar sadaļu. Kad kodols izdod noteikta veida notikumu (piemēram, tracepoint), tiek izpildÄ«tas saistÄ«tās funkcijas. Kartes nodroÅ”ina saziņu starp kodola programmu un lietotāja telpas programmu.

Secinājums

Å ajā rakstā BPF un eBPF tika apspriesti vispārÄ«gi. Es zinu, ka Å”odien ir daudz informācijas un resursu par eBPF, tāpēc es ieteikÅ”u vēl dažus resursus turpmākai izpētei.

Iesaku izlasīt:

Avots: www.habr.com

Pievieno komentāru