Tere, Habr! Teatame, et meil on valmimas raamat "".

Kuna BPF-i virtuaalne masin jätkab evolutsiooni ja seda rakendatakse aktiivselt praktikas, tõlkisime teie jaoks artikli, mis kirjeldab selle põhivõimalusi ja praegust seisukorda.
Viimastel aastatel on populaarsust kogunud programmeerimistööriistad ja tehnikad, mis aitavad kompenseerida Linuxi tuuma piiranguid olukordades, kus on vajalik kõrgperformaansiline pakettide töötlemine. Üks sellistest populaarsetest tehnikatest on tuuma möödasõit (kernel bypass), mis võimaldab, jättes välja tuuma võrgu taseme, teostada kogu pakettide töötlemise kasutajaruumi. Tuuma möödasõit hõlmab ka võrkaartide haldamist kasutajaruumis. Teisisõnu, võrkaartidega töötamisel toetume draiverile kasutajaruumis.
Kandes, et programm, mis annab täieliku kontrolli võrgukaardi üle kasutajapinnalt, vähendab kerni tööga seotud kulusid (konteksti vahetamine, võrgu taseme töötlemine, katked jne), mis on ülioluline, kui töötate kiirusel 10 Gb/s või rohkem. Kerni häirimine ja teiste funktsioonide kombinatsioon (pakettide töötlemine) ja täpsustatud jõudluse seaded (NUMA arvestamine, CPU isolatsioon, jne) on kõrge jõudlusega võrgu töötlemise aluseks kasutajapinnal. Tüüpiline näide sellisest uuest lähenemisest pakettide töötlemisel on Inteli (Data Plane Development Kit), kuigi on olemas ka teisi laialdaselt tuntud tööriistu ja tehnikaid, sealhulgas Cisco VPP (Vector Packet Processing), Netmap ja muidugi .
Kasutajapinnas olevate võrguühingute korraldamisel on mitmeid puudusi:
- OS-i tuum on hardware’i ressursside abstraktsioonitase. Kuna kasutajaruumis toimivad programmid peavad oma ressursse otse haldama, tuleb neil ka oma riistvara halduskohustusi täita. See tähendab sageli, et nad peavad programmeerima oma draivereid.
- Kuna me loobume täielikult tuumikuruumist, loobume me ka kõigist tuuma pakutavatest võrgufunktsioonidest. Kasutajaruumis toimivad programmid peavad uuesti rakendama neid funktsioone, mis võivad juba olla tuuma või operatsioonisüsteemi poolt pakutud.
- Programmid toimivad liivakastirežiimis, mis piirab oluliselt nende suhtlemisvõimet ja takistab neil teiste operatsioonisüsteemi osadega integreerumist.
Sisuliselt saavutatakse kasutajaruumi võrguside korraldamisel jõudluse suurenemine, viies pakettide töötlemise tuumast kasutajaruumi. XDP teeb just vastupidist: viib võrguprogrammid (filtrid, muundurid, marsruudid jne) kasutajaruumist tuumaruumi. XDP võimaldab meil teostada võrgufunktsiooni kohe, kui pakett jõuab võrguinterfeissi, ja enne, kui see hakkab liikuma tuuma võrgualasse. Selle tulemusena suureneb pakettide töötlemise kiirus oluliselt. Kuidas aga tuum võimaldab kasutajatel oma programme tuumaruumis käitada? Enne sellele küsimusele vastamist, vaatame, mis asi on BPF.
BPF ja eBPF
Kuigi BPF (Berkeley Packet Filtering) nimi on mõnevõrra segane, on see tegelikult virtuaalse masina mudel. See virtuaalne masin kavandati algselt pakettide filtreerimise töötlemiseks, sealt ka nimi.
Üks BPF-i kasutavatest tuntumatest tööriistadest on tcpdump. Pakettide salvestamine koos tcpdump kasutaja saab määrata pakettide filtreerimise väljendi. Ahnustatakse ainult neid pakette, mis vastavad sellele väljendile. Näiteks väljend “tcp dst port 80” katab kõik TCP-paketid, mis saavad porti 80. Koodigeneraator võib seda väljendit lühendada, muutes selle BPF-i bytecode'iks.
$ sudo tcpdump -d "tcp siht sad 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
Näiteks, seda teeb ülaltoodud programm:
- Juhis (000): laadib paketi nihkega 12, kujul 16-bitine sõna akumulaatorisse. Nihke 12 vastab paketihüübise tüübile.
- Juhis (001): võrdleb akumulaatoris olevat väärtust 0x86dd-ga, st IPv6 hüübitüübiga. Kui tulemus on tõene, siis programm läheb juhisele (002), muidu juhisele (006).
- Juhis (006): võrdleb väärtust 0x800-ga (IPv4 hüübitüüp). Kui vastus on tõene, siis programm läheb juhisele (007); kui ei, siis (015).
Ja nii edasi, kuni pakettide filtreerimise programm annab tulemuse. Tavaline tagastus on boolean. Mitte Nulli tagamine (juhis (014)) tähendab, et pakett sobib, ja Nulli tagamine (juhis (015)) tähendab, et pakett ei sobi.
BPF virtuaalmasina ja selle byte-kood pakkusid esmakordselt Steve McCann ja Van Jacobson 1992. aasta lõpus, kui avaldati nende artikkel , mis tutvustati esmakordselt Usenixi konverentsil 1993. aasta talvel.
Kuna BPF on virtuaalmasin, määratleb see keskkonna, kus programmid töötavad. Lisaks byte-koodile määratleb see ka mälumudeli (laadimisjuhised kehtivad vaikimisi paketile), registrid (A ja X; akumulaator ja indeks registreerimine), skripti-mälu ning vaikimisi programmiloenduri. Huvitav on, et BPF byte-kood mudeldi Motorola 6502 ISA järgi. Nagu meenutas Steve McCann oma Sharkfest '11-l, oli ta 6502 koostamisega tuttav juba keskkooli päevadest, kui ta programmeeris Apple II-l, ja need teadmised mõjutasid tema tööd BPF byte-koodi kavandamisel.
BPF-tugi on rakendatud Linuxi tuumas versioonis v2.5 ja uuemates, peamiselt Jay Schulisti jõupingutuste kaudu. BPF kood jäi tõsiste muudatusteta kuni aastani 2011, mil Eric Dumazet ümber kujundas BPF tõlgendi JIT-režiimi tööks (Allikas: ). Pärast seda suutis tuum mitte ainult tõlgendada BPF-baitkoodi, vaid ka otse BPF programme sihtarhitektuuri jaoks: x86, ARM, MIPS jne.
Hiljem, 2014. aastal, pakkus Alexey Starovoitov välja uue JIT-mekhanismi BPF jaoks. Tegelikult sai see uus JIT uue BPF-aluse arhitektuuri ja nimeks eBPF. Usun, et mõnda aega eksisteerisid mõlemad virtuaalsed masinad kõrvuti, kuid praegu põhineb paketifiltreerimine eBPF-il. Tegelikult mõistetakse paljudes kaasaegsetes dokumentatsioonides BPF-i all eBPF-i, samas kui klassikaline BPF on täna tuntud kui cBPF.
eBPF laiendab klassikalist BPF virtuaalmasinat mitmel viisil:
- Toetab kaasaegseid 64-bitiseid arhitektuure. eBPF kasutab 64-bitiseid registreid ja suurendab vabade registrite arvu 2 (aku ja X) kuni 10. eBPF-s on saadaval ka täiendavad käsukoodid (BPF_MOV, BPF_JNE, BPF_CALL...).
- Eemaldatud võrgu tasandi alamsüsteemist. BPF oli seotud pakettide andmemudelitega. Kuna seda kasutati pakettide filterdamiseks, asus selle kood alamsüsteemis, mis tagas võrgu suhtluse. Kuid eBPF virtuaalne masin ei ole enam andmemudelitega seotud ja seda saab kasutada mis tahes otstarbeks. Nüüd saab eBPF programmi manustada tracepoint'ile või kprobe'ile. See avab tee eBPF instrumenteerimisele, jõudluse analüüsile ja paljudele teistele kasutusvõimalustele teiste tuuma alamsüsteemide kontekstis. Nüüd asub eBPF kood oma teel: kernel/bpf.
- Globaalsed andmehoidlad, mida nimetatakse Kaartideks. Kaardid on 'võti-väärtus' tüüpi hoidlates, mis võimaldavad andmete vahetust kasutaja ruumi ja tuuma ruumi vahel. eBPF-s on saadaval erinevate tüüpidega kaarte.
- Abifunktsioonid. Eelkõige paketi ümberkirjutamiseks, kontrollsummade arvutamiseks või paketi kloonimiseks. Need funktsioonid toimuvad tuumas ja ei kuulu kasutajaruumi programmide alla. Lisaks saab eBPF-programmidelt teha süsteemikõnesid.
- Lõppkõned. eBPF-programmi suurus on piiratud 4096 baitiga. Lõppkõne võimalus võimaldab eBPF programmil edastada juhtimise uuele eBPF programmile ja seeläbi ületada see piirang (sel viisil saab siduda kuni 32 programmi).
eBPF: näide
Linuxi tuuma lähtekoodis on mitu eBPF näidet. Need on saadaval aadressil samples/bpf/. Nende näidiste kompileerimiseks kirjutage lihtsalt:
$ sudo make samples/bpf/
Ma ei hakka uut eBPF näidet ise kirjutama, vaid kasutan ühte näidist, mis on saadaval samples/bpf/. Vaatan mõningaid koodilõike ja selgitan, kuidas see töötab. Näitena valisin programmi tracex4.
Üldiselt koosneb iga näide aadressilt samples/bpf/ kahest failist. Antud juhul:
tracex4_kern.c, sisaldab lähtekoodi, mis peab tuumas toimuma eBPF baitkoodina.tracex4_user.c, sisaldab kasutajaruumi programmi.
Sellisel juhul peame kompileerima tracex4_kern.c eBPF-i baitkoodiks. Hetkel puudub gcc eBPF-i serveripool. Õnneks clang suudab genereerida eBPF-i baitkoodi. kasutab clang kompileerimiseks tracex4_kern.c objektifailiks.
Mainisin varem, et üks eBPF-i huvitavamaid omadusi on kaardid. tracex4_kern määratleb ühe kaardi:
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 – üks paljusid kaarditüüpe, mida eBPF pakub. Antud juhul on see lihtsalt hash. Samuti võisite märgata kuulutust SEC("maps"). SEC on makro, mida kasutatakse binaarfaili uue sektsiooni loomiseks. Tegelikult määratleb näites tracex4_kern veel kaks sektsiooni:
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;
// saame ip-aadress, mis kutsus üles 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;
} Need kaks funktsiooni võimaldavad kaardist välja kustutada kirje (kprobe/kmem_cache_free) ja lisada kaardile uus sissekanne (kretprobe/kmem_cache_alloc_node). Kõik suurtähtedega kirjutatud funktsioonide nimed vastavad makrodele, mis on määratletud .
Kui ma teen objekti faili sektsioonide väljavõtte, peaksin nägema, et need uued sektsioonid on juba määratletud:
$ objdump -h tracex4_kern.o
tracex4_kern.o: faili formaat elf64-little
Sektsioonid:
Idx Nimi Suurus VMA LMA Faili off Algn
0 .text 00000000 0000000000000000 0000000000000000 00000040 2**2
SISU, ALLOC, LOAD, READONLY, KOOD
1 kprobe/kmem_cache_free 00000048 0000000000000000 0000000000000000 00000040 2**3
SISU, ALLOC, LOAD, RELOC, READONLY, KOOD
2 kretprobe/kmem_cache_alloc_node 000000c0 0000000000000000 0000000000000000 00000088 2**3
SISU, ALLOC, LOAD, RELOC, READONLY, KOOD
3 maps 0000001c 0000000000000000 0000000000000000 00000148 2**2
SISU, ALLOC, LOAD, ANDMED
4 license 00000004 0000000000000000 0000000000000000 00000164 2**0
SISU, ALLOC, LOAD, ANDMED
5 version 00000004 0000000000000000 0000000000000000 00000168 2**2
SISU, ALLOC, LOAD, ANDMED
6 .eh_frame 00000050 0000000000000000 0000000000000000 00000170 2**3
SISU, ALLOC, LOAD, RELOC, READONLY, ANDMED
On veel , peamine programm. Põhimõtteliselt kuulab see programm sündmusi kmem_cache_alloc_node. Kui selline sündmus aset leiab, täidetakse vastav eBPF kood. Kood salvestab objekti IP-atribuudi kaardile, ja seejärel väljendatakse see objekt tsükliliselt põhiprogrammis. Näide:
$ sudo ./tracex4
obj 0xffff8d6430f60a00 on 2 sekundi vana, eraldatud ip-l ffffffff9891ad90
obj 0xffff8d6062ca5e00 on 23 sekundi vana, eraldatud ip-l ffffffff98090e8f
obj 0xffff8d5f80161780 on 6 sekundi vana, eraldatud ip-l ffffffff98090e8f
Kuidas on seotud kasutajaruumi programm ja eBPF programm? Algusfaasis tracex4_user.c laeb objekti faili tracex4_kern.o kasutades funktsiooni laadi_bpf_fail.
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;
} Täitmisel eBPF failis määratletud sondid lisatakse /sys/kernel/debug/tracing/kprobe_events. Nüüd kuulame neid sündmusi, ja meie programm võib midagi teha, kui need juhtuvad.
$ 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
Kõik muud programmid kaustas sample/bpf on struktureeritud sarnaselt. Neis on alati kaks faili:
XXX_kern.c: eBPF programm.XXX_user.c: vajalik programm.
eBPF programm määratleb kaardid ja funktsioonid, mis on seotud sektsiooniga. Kui tuuma genereerib teatud tüüpi sündmuse (näiteks, tracepoint), käivitatakse seotud funktsioonid. Kaardid tagavad andmevahetuse tuuma ja kasutaja programmi vahel.
Kokkuvõte
Selles artiklis on üldiselt käsitletud BPF ja eBPF. Tean, et tänapäeval on palju teavet ja ressursse eBPF kohta, seetõttu soovitaksin veel paar materjali edasisteks teadmisteks.
Soovitan lugeda:
- Jonathan Corbett. BPF-i tutvustamine ja selle areng eBPF-iks.
- Brendan Gregg. Artikkel LWN.neti veebisaidilt. Brendan kirjutab sageli eBPF-ist Twitteris ja hoiab selle teemaga seotud ressursside loendit oma .
- Julia Evans. Kommentaarid Sushakra Sharma ettekandele "BSD Packet Filter: Uus arhitektuur kasutaja tasemel paketipüüdmiseks". Kommentaarid on väärtuslikud ja aitavad tõeliselt slaidides orienteeruda.
- Ferris Ellise. Pikk artikkel , kuid tasub lugeda. Üks parimaid eBPF-i artikleid, mida olen kohanud.
Allikas: habr.com
