Përshëndetje, Habr! Po ju bëjmë me dije që po publikohet libri "".

Duke pasur parasysh se makineria virtuale BPF vazhdon të evoluojë dhe aplikohet aktivisht në praktikë, kemi përkthyer për ju një artikull që përshkruan aftësitë e saj kryesore dhe gjendjen aktuale.
Në vitet e fundit, janë bërë gjithnjë e më të njohura mjetet e programimit dhe teknikat që synojnë të kompensojnë kufizimet e bërthamës Linux në raste kur kërkohet përpunim i lartë i paketave. Një nga teknikat më të njohura të këtij lloji quhet anashkalimi i bërthamës (kernel bypass) dhe lejon që, duke anashkaluar nivelin rrjetit të bërthamës, të bëhet e gjithë përpunimi i pakove nga hapësira e përdoruesit. Anashkalimi i bërthamës gjithashtu nënkupton menaxhimin e kartës rrjetit nga hapësira e përdoruesit. Me fjalë të tjera, kur punojmë me kartën rrjetit, ne mbështetemi në pilotin hapësira e përdoruesit.
Duke kontrollin e plotë mbi kartën e rrjetit në programin nga hapësira e përdoruesit, ne reduktojmë kostot që lidhen me funksionimin e bërthamës (ndryshimi i kontekstit, përpunimi i nivelit të rrjetit, ndërprerjet, etj.), e cila është shumë e rëndësishme kur punojmë në shpejtësi prej 10Gb/s ose më të larta. Shmangia e bërthamës plus kombinimi i mundësive të tjera (procesimi i grupit) dhe rregullimi i kujdesshëm i performancës (shqyrtimi i NUMA, izolimi i CPU, etj.) i përputhen bazeve të përpunimit të rrjetit me performancë të lartë në hapësirën e përdoruesit. Një shembull i shkëlqyer i një qasje të tillë për përpunimin e paketave është nga Intel (Data Plane Development Kit), megjithatë, ekzistojnë edhe instrumente e metoda të tjera të njohura gjerësisht, midis të cilave VPP nga Cisco (Vector Packet Processing), Netmap dhe, natyrisht, .
Organizimi i ndërveprimeve të rrjetit në hapësirën e përdoruesit ka një mori disavantazhesh:
- Baza e OS është niveli i abstraksionit për burimet harduerike. Të dhënat e hapësirës së përdoruesit duhet të menaxhojnë burimet e tyre drejtpërdrejt, kështu që ata gjithashtu duhet të menaxhojnë pajisjet e tyre harduerike. Shpesh, kjo do të thotë nevoja për të programuar drejtuesit e tyre.
- Duke hequr dorë plotësisht nga hapësira e bërthamës, ne gjithashtu heqim dorë nga të gjitha funksionalitetet rrjetit të ofruara nga bërthama. Programet në hapësirën e përdoruesit duhet të zbatojnë sërish ato funksione që, ndoshta, ofrohen tashmë nga bërthama ose sistemi operativ.
- Programet punojnë në mënyrë të izoluar, e cila kufizon ndjeshëm mundësitë e bashkëpunimit dhe e pengon ato të integrohen me pjesë të tjera të sistemit operativ.
Në thelb, kur organizoni ndërveprimet rrjet në hapësirën e përdoruesit, përmirësimi i performancës arrihet duke zhvendosur përpunimin e paketave nga bërthama në hapësirën e përdoruesit. XDP bën pikërisht të kundërtën: zhvendos programet rrjet në hapësirën e bërthamës (filtra, konvertues, ruter etj.). XDP na lejon të realizojmë funksionin rrjetor sapo paketa arrin në ndërfaqen rrjet dhe para se të fillojë lëvizjen up në nën-sistemin rrjetor të bërthamës. Si rezultat, shpejtësia e përpunimit të paketave rritet ndjeshëm. Por, si e lejon bërthama përdoruesin të ekzekutojë programet e tij në hapësirën e bërthamës? Para se të përgjigjemi në këtë pyetje, le të shqyrtojmë se çfarë është BPF.
BPF dhe eBPF
PavarĂ«sisht emrit tĂ« paqartĂ« BPF (Filtrimi i Paketave, Berkley) â kjo, nĂ« fakt, Ă«shtĂ« njĂ« model makinerie virtuale. Kjo makineri virtuale Ă«shtĂ« ndĂ«rtuar fillimisht pĂ«r tĂ« trajtuar filtrimin e paketave, ndaj dhe emri.
NjĂ« prej mjeteve mĂ« tĂ« njohura qĂ« pĂ«rdor BPF Ă«shtĂ« tcpdump. NdĂ«rsa kapet paketat me anĂ« tĂ« tcpdump PĂ«rdoruesi mund tĂ« pĂ«rcaktojĂ« njĂ« shprehje pĂ«r filtrimin e paketeve. VetĂ«m pakete qĂ« pĂ«rputhen me kĂ«tĂ« shprehje do tĂ« kapen. PĂ«r shembull, shprehja âtcp dst port 80â ka tĂ« bĂ«jĂ« me tĂ« gjitha pakot TCP qĂ« vijnĂ« nĂ« portin 80. Kompilatori mund ta shkurtĂ«zojĂ« kĂ«tĂ« shprehje duke e transformuar nĂ« kodin bajt 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
Kjo është, në parim, ajo që bën programi i mësipërm:
- Instruksioni (000): ngarkon paketën nga ofseti 12, në formën e një fjale 16-bit në akumulator. Ofseti 12 përputhet me ethertype të paketës.
- Instruksioni (001): krahasohet vlera nĂ« akumulator me 0x86dd, domethĂ«nĂ«, me vlerĂ«n ethertype pĂ«r IPv6. NĂ«se rezultati Ă«shtĂ« true, numĂ«ruesi i programit kalon nĂ« instruksionin (002), nĂ«se jo â nĂ« (006).
- Instruksioni (006): krahasohet vlera me 0x800 (vlera ethertype pĂ«r IPv4). NĂ«se pĂ«rgjigjja Ă«shtĂ« true, programi kalon nĂ« (007), nĂ«se jo â nĂ« (015).
Dhe kështu me radhë, derisa programi i filtrimit të paketeve të kthejë rezultat. Zakonisht, kjo është një boolean. Kthimi i një vlere jo zero (instruksioni (014)) do të thotë se paketa kalon, ndërsa kthimi i zeros (instruksioni (015)) do të thotë se paketa nuk kalon.
Makinë virtuale BPF dhe kodi i saj byte u propozua nga Steve McCanne dhe Van Jacobson në fund të vitit 1992, kur doli artikulli i tyre. , teknologjia fillimisht u prezantua në konferencën Usenix në dimrin e vitit 1993.
Duke qenĂ« se BPF Ă«shtĂ« njĂ« makinĂ« virtuale, ajo pĂ«rcakton mjedisin nĂ« tĂ« cilin ekzekutohen programet. PĂ«rveç kodit byte, ajo gjithashtu pĂ«rcakton modelin e memories tĂ« paketĂ«s (instruksionet e ngarkesĂ«s aplikohen nĂ« mĂ«nyrĂ« tĂ« heshtur nĂ« paketĂ«), regjistrat (A dhe X; regjistrat e akumulatorit dhe indeksit), hapĂ«sirĂ«n e memories sĂ« pĂ«rkohshme dhe numĂ«ruesin e heshtur tĂ« programeve. ĂshtĂ« interesante se kodi byte i BPF Ă«shtĂ« modeluar sipas Motorola 6502 ISA. Siç e kujton Steve McCanne nĂ« fjalimin e tij nĂ« Sharkfest â11, ai ishte njohur me pĂ«rbĂ«rjen 6502 qĂ« nga klasa e lartĂ«, kur programonte nĂ« Apple II, dhe kĂ«to njohuri ndihmuan nĂ« punĂ«n e tij pĂ«r projektimin e kodit byte tĂ« BPF.
Mbështetja e BPF është realizuar në bërthamën Linux në versionin v2.5 dhe më lart, e shtuar kryesisht falë përpjekjeve të Jay Shullist. Kodi i BPF mbeti pa ndryshime të mëdha deri në vitin 2011, kur Eric Dumazet ripunoi interpretorin BPF për të punuar në modalitetin JIT (Burimi: ). Pas kësaj, bërthama përveç interpretimit të kodit byte të BPF mund të konvertonte drejtpërdrejt programet BPF për arkitekturën e caktuar: x86, ARM, MIPS, etj.
Më vonë, në vitin 2014, Alexey Starovoytov propozuar një mekanizëm të ri JIT për BPF. Në fakt, ky JIT i ri u bë një arkitekturë e re e bazuar në BPF dhe mori emrin eBPF. Mendoj se për një kohë të caktuar, të dy makineritë virtuale bashkëjetonin, por aktualisht filtrimi i paketave realizohet mbi bazën e eBPF. Në fakt, në shumë mostra të dokumentacionit modern, BPF kuptohet si eBPF, ndërsa BPF klasike sot njihet si cBPF.
eBPF në disa mënyra zgjerohet mbi makinerinë klasike virtuale BPF:
- Bazohet nĂ« arkitektura moderne 64-bit. eBPF pĂ«rdor regjistrat 64-bit dhe rrit numrin e regjistrave tĂ« disponueshĂ«m nga 2 (akumulatori dhe X) nĂ« 10. NĂ« eBPF gjithashtu ofrohen operacione tĂ« tjera (BPF_MOV, BPF_JNE, BPF_CALLâŠ).
- E shkëputur nga nën-sistemi i nivelit rrjet. BPF ishte e lidhur me modelin e të dhënave me paketë. Duke qenë se ajo përdorej për filtrimin e paketimeve, kodi i saj ndodhej në nën-sistemin që siguroi ndërveprimin rrjet. Megjithatë, makina virtuale eBPF nuk është më e lidhur me modelin e të dhënave dhe mund të përdoret për qëllime të ndryshme. Tani, programi eBPF mund të lidhet me tracepoint ose kprobe. Kjo hap rrugën për instrumentimin e eBPF, analizimin e performancës dhe shumë mundësi të tjera në kontekstin e nën-sistemeve të tjera të kernelit. Tani kodi i eBPF ndodhet në rrugën e tij të vet: kernel/bpf.
- Depot globale të dhënash të quajtura Kartat. Kartat janë depo të tipit "çelës-vlerë", duke mundësuar shkëmbimin e të dhënave midis hapësirës së përdoruesit dhe hapësirës së kernelit. Në eBPF ofrohen karta disa llojesh.
- Funksionet ndihmëse. Në veçanti, për sovrimin e paketave, llogaritjen e checksum-it ose klonimin e paketave. Këto funksione kryhen brenda kernelit dhe nuk i përkasin programeve të hapësirës së përdoruesit. Për më tepër, nga programet e eBPF mund të kryhen thirrje sistemore.
- Thirrjet finale. Madhësia e programit në eBPF është e kufizuar në 4096 byte. Mundësia e thirrjes finale lejon programit eBPF të transferojë kontrollin në një program të ri eBPF dhe kështu të anashkalojë këtë kufizim (në këtë mënyrë mund të lidhen deri në 32 programe).
eBPF: shembull
Në burimet e kernelit Linux ka disa shembuj për eBPF. Ato janë të disponueshme në samples/bpf/. Për t'i kompaktuar këto shembuj, thjesht shkruani:
$ sudo make samples/bpf/
Nuk do të shkruaj vetë një shembull të ri për eBPF, por do të përdor një nga mostrën e disponueshme në samples/bpf/. Do të shqyrtoj disa pjesë të kodit dhe do shpjegoj se si funksionon. Si shembull, zgjodha programin tracex4.
Në të vërtetë, secili nga shembujt në samples/bpf/ përbëhet nga dy skedarë. Në këtë rast:
tracex4_kern.c, përmban kodin burimor që duhet të ekzekutohet në kernel si bytecode eBPF.tracex4_user.c, përmban programin nga hapësira e përdoruesit.
Në këtë rast, na nevojitet të kompilojmë tracex4_kern.c në kodin e bajtave eBPF. Aktualisht, gcc mungon një server për eBPF. Fatmirësisht, clang mund të gjenerojë kod bajt eBPF. përdor clang për kompilimin tracex4_kern.c në një skedar objekti.
Më sipër përmenda se një nga veçoritë më interesante të eBPF janë hartat. tracex4_kern definon një hartë:
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 â njĂ« nga shumĂ« lloje hartash qĂ« ofron eBPF. NĂ« kĂ«tĂ« rast, Ă«shtĂ« thjesht njĂ« hash. Gjithashtu mund tĂ« keni vĂ«nĂ« re shpalljen SEC("maps"). SEC Ă«shtĂ« njĂ« makros qĂ« pĂ«rdoret pĂ«r tĂ« krijuar njĂ« seksion tĂ« ri tĂ« skedarit binar. NĂ« fakt, nĂ« shembullin tracex4_kern definohen edhe dy seksione tĂ« tjera:
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;
// marrim adresën ip të thirrësit 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;
} Këto dy funksione lejojnë të fshihen shënimet nga harta (kprobe/kmem_cache_free) dhe shtoni një regjistrim të ri në hartë (kretprobe/kern_cache_alloc_node). Të gjitha emrat e funksioneve, të shkruara me shkronja të mëdha, korrespondon me makrosat e definuara në .
Nëse unë nxjerr një dump të seksioneve të skedarit objekt, duhet të shoh se këto seksione të reja janë tashmë të definuara:
$ objdump -h tracex4_kern.o
tracex4_kern.o: formati i skedarit elf64-little
Seksionet:
Idx Emri Madhësia VMA LMA Off skedari Algn
0 .text 00000000 0000000000000000 0000000000000000 00000040 2**2
PĂRBĂRJA, ALLOKIMI, NGLOJ, READONLY, KOD
1 kprobe/kmem_cache_free 00000048 0000000000000000 0000000000000000 00000040 2**3
PĂRBĂRJA, ALLOKIMI, NGLOJ, RIKTHIM, READONLY, KOD
2 kretprobe/kern_cache_alloc_node 000000c0 0000000000000000 0000000000000000 00000088 2**3
PĂRBĂRJA, ALLOKIMI, NGLOJ, RIKTHIM, READONLY, KOD
3 hartat 0000001c 0000000000000000 0000000000000000 00000148 2**2
PĂRBĂRJA, ALLOKIMI, NGLOJ, TĂ DHĂNAT
4 licenca 00000004 0000000000000000 0000000000000000 00000164 2**0
PĂRBĂRJA, ALLOKIMI, NGLOJ, TĂ DHĂNAT
5 version 00000004 0000000000000000 0000000000000000 00000168 2**2
PĂRBĂRJA, ALLOKIMI, NGLOJ, TĂ DHĂNAT
6 .eh_frame 00000050 0000000000000000 0000000000000000 00000170 2**3
PĂRBĂRJA, ALLOKIMI, NGLOJ, RIKTHIM, READONLY, TĂ DHĂNAT
Ka gjithashtu , programi kryesor. Në parim, ky program dëgjon ngjarje kmem_cache_alloc_node. Kur ndodh një ngjarje e tillë, ekzekutohet kodi përkatës eBPF. Kodi ruan atributin IP të objektit në hartë, dhe më pas ky objekt printohet ciklikisht në programin kryesor. Shembull:
$ sudo ./tracex4
obj 0xffff8d6430f60a00 është 2 sekonda e vjetër, është alokuar në ip ffffffff9891ad90
obj 0xffff8d6062ca5e00 është 23 sekonda e vjetër, është alokuar në ip ffffffff98090e8f
obj 0xffff8d5f80161780 është 6 sekonda e vjetër, është alokuar në ip ffffffff98090e8f
Si janĂ« tĂ« lidhura programi i hapĂ«sirĂ«s sĂ« pĂ«rdoruesit dhe programi eBPF? NĂ« inicializim tracex4_user.c ngarkon skedarin e objektit tracex4_kern.o ĐżŃĐž ĐżĐŸĐŒĐŸŃĐž ŃŃĐœĐșŃОО ngarko_skedarin_bpf.
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 kërkesë sondat, të përcaktuara në skedarin e eBPF, shtohen në /sys/kernel/debug/tracing/kprobe_events. Tani ne po dëgjojmë këto ngjarje dhe programi ynë mund të bëjë diçka kur ato ndodhin.
$ 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
Të gjitha programet në sample/bpf/ janë struktuar në një mënyrë të ngjashme. Ato gjithmonë kanë dy skeda:
XXX_kern.c: programi eBPF.XXX_user.c: programi kryesor.
Programi eBPF përcakton karta dhe funksione të lidhura me seksionin. Kur bërthama lëshon një ngjarje të caktuar (p.sh., tracepoint), funksionet e lidhura ekzekutohen. Kartat sigurojnë shkëmbimin e të dhënave midis programit të bërthamës dhe programit të hapësirës së përdoruesit.
Përfundimi
Ky artikull përmbledh në një farë mënyre BPF dhe eBPF. E di që sot ka shumë informacion dhe burime mbi eBPF, prandaj rekomandoj disa materiale për studim të mëtejmë.
Rekomandoj të lexoni:
- Jonathan Corbet. Një hyrje në BPF dhe një tregim mbi evolucionin e saj në eBPF.
- Brendan Gregg. Një artikull nga faqja LWN.net. Brendan shpesh shkruan tweete mbi eBPF dhe mban një listë burimesh në këtë temë në .
- Julia Evans. Komente mbi prezantimin e Su Chakraborty 'Filtri i Paketave BSD: Një Arkitekturë e Re për Kapjen e Paketave në Nivelin e Përdoruesit'. Komentet janë të mira dhe vërtet ndihmojnë për të kuptuar slidet.
- Ferris Ellis. Një artikull i gjatë me , por ia vlen të lexohet. Një nga artikujt më të mirë mbi eBPF që kam hasur.
Burimi: habr.com
