Stutt kynning á BPF og eBPF

Hæ Habr! Við tilkynnum ykkur að við erum að undirbúa útgáfu bók "Linux Observability með BPF".

Stutt kynning á BPF og eBPF
Þar sem BPF sýndarvélin heldur áfram að þróast og er virkan notuð í reynd, höfum við þýtt grein fyrir þig sem lýsir helstu eiginleikum hennar og núverandi ástandi.

Undanfarin ár hafa forritunartól og -tækni notið vinsælda til að bæta upp fyrir takmarkanir Linux kjarnans í þeim tilvikum þar sem þörf er á afkastamikilli pakkavinnslu. Ein vinsælasta aðferðin af þessu tagi er kölluð kjarna framhjá (kernel bypass) og gerir, sleppa netlagi kjarnans, að framkvæma alla pakkavinnslu úr notendarými. Að komast framhjá kjarnanum felur einnig í sér að stjórna netkortinu frá notendarými. Með öðrum orðum, þegar unnið er með netkort treystum við á ökumanninn notendarými.

Með því að flytja fulla stjórn á netkortinu yfir í notendarýmisforrit minnkum við kostnaði sem stafar af kjarnanum (samhengisrofar, netlagsvinnsla, truflanir osfrv.), sem er mjög mikilvægt þegar keyrt er á 10Gb/s eða hærri hraða. Framhjá kjarnanum ásamt samsetningu annarra eiginleika (lotuvinnsla) og vandlega stilla frammistöðu (NUMA bókhald, CPU einangrun, osfrv.) passa undirstöðuatriðin í afkastamiklu netkerfi notendarýmis. Kannski er fyrirmyndardæmi um þessa nýju nálgun við pakkavinnslu DPDK frá Intel (Data Plane Development Kit), þó að það séu önnur vel þekkt verkfæri og tækni, þar á meðal VPP frá Cisco (Vector Packet Processing), Netmap og auðvitað, hnífur.

Skipulag netsamskipta í notendarými hefur ýmsa ókosti:

  • OS kjarni er útdráttarlag fyrir vélbúnaðarauðlindir. Vegna þess að notendarýmisforrit verða að stjórna auðlindum sínum beint, verða þau einnig að stjórna eigin vélbúnaði. Þetta þýðir oft að forrita eigin rekla.
  • Þar sem við erum algjörlega að gefa upp kjarnapláss, erum við líka að gefa upp alla netvirkni sem kjarnann býður upp á. Notendarýmisforrit verða að endurútfæra eiginleika sem kunna að vera þegar útvegaðir af kjarnanum eða stýrikerfinu.
  • Forrit virka í sandkassaham, sem takmarkar verulega samskipti þeirra og kemur í veg fyrir að þau samþættist öðrum hlutum stýrikerfisins.

Í meginatriðum, þegar netkerfi í notendarými er náð, næst frammistöðuaukning með því að færa pakkavinnslu frá kjarnanum yfir í notendarýmið. XDP gerir nákvæmlega hið gagnstæða: það færir netforrit úr notendarými (síur, breytir, leið osfrv.) yfir á kjarnasvæðið. XDP gerir okkur kleift að framkvæma netaðgerðina um leið og pakkinn lendir á netviðmótinu og áður en hann byrjar að ferðast upp að netundirkerfi kjarnans. Fyrir vikið er pakkavinnsluhraði aukinn verulega. Hins vegar, hvernig gerir kjarninn notandanum kleift að keyra forritin sín í kjarnarými? Áður en þessari spurningu er svarað skulum við skoða hvað BPF er.

BPF og eBPF

Þrátt fyrir ekki alveg skýra nafnið er BPF (Packet Filtering, Berkeley) í raun sýndarvélamódel. Þessi sýndarvél var upphaflega hönnuð til að takast á við pakkasíun, þess vegna nafnið.

Eitt af þekktari verkfærunum sem nota BPF er tcpdump. Þegar þú tekur pakka með tcpdump notandinn getur tilgreint tjáningu fyrir pakkasíun. Aðeins pakkar sem passa við þessa tjáningu verða teknir. Til dæmis, orðatiltækið "tcp dst port 80” vísar til allra TCP pakka sem berast á höfn 80. Þjálfarinn getur stytt þessa tjáningu með því að breyta henni í BPF bætikóða.

$ 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

Þetta er í grundvallaratriðum það sem ofangreint forrit gerir:

  • Leiðbeiningar (000): Hleður pakkanum við offset 12, sem 16 bita orð, inn í safnið. Offset 12 samsvarar etergerð pakkans.
  • Leiðbeiningar (001): ber saman gildið í rafgeyminum við 0x86dd, það er við ethertype gildi fyrir IPv6. Ef niðurstaðan er sönn fer forritateljarinn í leiðbeiningar (002), og ef ekki, þá í (006).
  • Leiðbeiningar (006): ber gildið saman við 0x800 (etergerð gildi fyrir IPv4). Ef svarið er satt, þá fer forritið í (007), ef ekki, þá í (015).

Og svo framvegis, þar til pakkasíunarforritið skilar niðurstöðu. Venjulega er það Boolean. Að skila gildi sem ekki er núll (fyrirmæli (014)) þýðir að pakkinn passaði og að skila núlli (fyrirmæli (015)) þýðir að pakkinn passaði ekki.

BPF sýndarvélin og bækikóði hennar voru settar fram af Steve McCann og Van Jacobson seint á árinu 1992 þegar blað þeirra kom út. BSD Packet Filter: Nýr arkitektúr fyrir pakkafanga á notendastigi, í fyrsta skipti sem þessi tækni var kynnt á Usenix ráðstefnunni veturinn 1993.

Þar sem BPF er sýndarvél skilgreinir hún umhverfið sem forrit keyra í. Til viðbótar við bætikóða, skilgreinir það einnig pakkaminnislíkan (hleðsluleiðbeiningar eru óbeint beittar á pakka), skrár (A og X; safn- og vísitöluskrár), geymsluminni fyrir klóra og óbeina forritateljara. Athyglisvert er að BPF bætikóði var gerður eftir Motorola 6502 ISA. Eins og Steve McCann minntist á í sínum skýrslu allsherjarþings á Sharkfest '11, var hann kunnugur smíði 6502 frá menntaskóla þegar hann forritaði á Apple II, og þessi þekking hafði áhrif á vinnu hans við að hanna BPF bækakóðann.

BPF stuðningur er útfærður í Linux kjarnanum í útgáfu v2.5 og síðar, aðallega bætt við af Jay Schullist. BPF kóðinn hélst óbreyttur þar til 2011, þegar Eric Dumaset endurhannaði BPF túlkinn til að vinna í JIT ham (Heimild: JIT fyrir pakkasíur). Eftir það, í stað þess að túlka BPF bætikóðann, gæti kjarninn umbreytt BPF forritum beint í markarkitektúrinn: x86, ARM, MIPS osfrv.

Seinna, árið 2014, lagði Alexei Starovoitov til nýjan JIT vélbúnað fyrir BPF. Reyndar varð þessi nýja JIT nýr arkitektúr byggður á BPF og var kallaður eBPF. Ég held að báðar VM hafi verið samhliða í nokkurn tíma, en pakkasíun er sem stendur innleidd ofan á eBPF. Reyndar, í mörgum nútíma skjaladæmum, er BPF vísað til sem eBPF og klassískt BPF er þekkt í dag sem cBPF.

eBPF útvíkkar klassísku BPF sýndarvélina á nokkra vegu:

  • Byggir á nútíma 64-bita arkitektúr. eBPF notar 64 bita skrár og eykur fjölda tiltækra skráa úr 2 (safn og X) í 10. eBPF veitir einnig fleiri opkóða (BPF_MOV, BPF_JNE, BPF_CALL…).
  • Aðskilið frá undirkerfi netlagsins. BPF var bundið við lotugagnalíkanið. Þar sem það var notað til að sía pakka var kóði þess í undirkerfinu sem veitti netsamskiptum. Hins vegar er eBPF sýndarvélin ekki lengur bundin við gagnalíkan og hægt að nota hana í hvaða tilgangi sem er. Svo, nú er hægt að tengja eBPF forritið við rekjapunkt eða kprobe. Þetta opnar dyrnar að eBPF tækjabúnaði, frammistöðugreiningu og mörgum öðrum notkunartilvikum í samhengi við önnur kjarna undirkerfi. Nú er eBPF kóðinn staðsettur á eigin slóð: kjarna/bpf.
  • Alþjóðlegar gagnaverslanir sem kallast Maps. Kort eru lykilgildageymslur sem veita gagnaskipti milli notendarýmis og kjarnarýmis. eBPF býður upp á nokkrar gerðir af kortum.
  • Aukaaðgerðir. Sérstaklega til að skrifa yfir pakka, reikna tékksummu eða klóna pakka. Þessar aðgerðir keyra inni í kjarnanum og tilheyra ekki notendarýmisforritum. Að auki er hægt að hringja kerfissímtöl úr eBPF forritum.
  • Slíta símtölum. Forritastærð í eBPF er takmörkuð við 4096 bæti. Loksímtalareiginleikinn gerir eBPF forriti kleift að flytja stjórn yfir í nýtt eBPF forrit og komast þannig framhjá þessari takmörkun (hægt er að hlekkja allt að 32 forrit á þennan hátt).

eBPF dæmi

Það eru nokkur dæmi fyrir eBPF í Linux kjarnaheimildum. Þeir eru fáanlegir á samples/bpf/. Til að setja saman þessi dæmi skaltu bara slá inn:

$ sudo make samples/bpf/

Ég mun ekki skrifa nýtt dæmi fyrir eBPF sjálfur, en mun nota eitt af sýnunum sem til eru í samples/bpf/. Ég mun skoða nokkra hluta kóðans og útskýra hvernig hann virkar. Sem dæmi valdi ég forritið tracex4.

Almennt séð samanstendur hvert af dæmunum í samples/bpf/ af tveimur skrám. Í þessu tilfelli:

  • tracex4_kern.c, inniheldur frumkóða sem á að keyra í kjarnanum sem eBPF bækikóði.
  • tracex4_user.c, inniheldur forrit frá notendarými.

Í þessu tilfelli þurfum við að setja saman tracex4_kern.c í eBPF bætikóða. Í augnablikinu í gcc það er enginn miðlarahluti fyrir eBPF. Sem betur fer, clang getur framleitt eBPF bætikóða. Makefile notar clang að setja saman tracex4_kern.c í hlutskrána.

Ég nefndi hér að ofan að einn af áhugaverðustu eiginleikum eBPF eru kort. tracex4_kern skilgreinir eitt kort:

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 er ein af mörgum kortategundum sem eBPF býður upp á. Í þessu tilfelli er þetta bara hass. Þú gætir líka hafa tekið eftir auglýsingunni SEC("maps"). SEC er fjölvi notað til að búa til nýjan hluta af tvíundarskrá. Reyndar í dæminu tracex4_kern tveir kaflar til viðbótar eru skilgreindir:

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

Þessar tvær aðgerðir gera þér kleift að fjarlægja færslu af kortinu (kprobe/kmem_cache_free) og bættu nýrri færslu við kortið (kretprobe/kmem_cache_alloc_node). Öll fallaheiti skrifuð með hástöfum samsvara fjölvunum sem skilgreind eru í bpf_helpers.h.

Ef ég henda hlutunum í hlutskránni ætti ég að sjá að þessir nýju hlutar eru þegar skilgreindir:

$ 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

Það er einnig tracex4_user.c, aðaldagskrá. Í grundvallaratriðum hlustar þetta forrit á viðburði kmem_cache_alloc_node. Þegar slíkur atburður á sér stað er samsvarandi eBPF kóða keyrður. Kóðinn vistar IP-eiginleika hlutarins á korti og síðan er hlutnum hlaðið í gegnum aðalforritið. Dæmi:

$ 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

Hvernig tengjast geimforritið notenda og eBPF forritið? Við frumstillingu tracex4_user.c hleður hlut skrá tracex4_kern.o með því að nota aðgerðina 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;
}

Með því að gera load_bpf_file rannsaka sem skilgreindar eru í eBPF skránni er bætt við /sys/kernel/debug/tracing/kprobe_events. Nú hlustum við eftir þessum viðburðum og dagskráin okkar getur gert eitthvað þegar þeir gerast.

$ 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

Öll önnur forrit í sample/bpf/ eru byggð upp á svipaðan hátt. Þau innihalda alltaf tvær skrár:

  • XXX_kern.c: eBPF forrit.
  • XXX_user.c: aðalforrit.

eBPF forritið skilgreinir kortin og aðgerðir sem tengjast hluta. Þegar kjarninn sendir frá sér atburði af ákveðinni gerð (td. tracepoint), eru bundnu föllin keyrð. Kort veita samskipti milli kjarnaforrits og notendarýmisforrits.

Ályktun

Í þessari grein var fjallað almennt um BPF og eBPF. Ég veit að það er mikið af upplýsingum og úrræðum um eBPF í dag, svo ég mun mæla með nokkrum fleiri efni til frekari rannsókna.

Ég mæli með að lesa:

Heimild: www.habr.com

Bæta við athugasemd