Համառոտ ներածություն BPF-ին և eBPF-ին

Հե՜յ Հաբր։ Տեղեկացնում ենք, որ պատրաստվում ենք գիրք թողարկել»Linux դիտարկելիություն BPF-ով".

Համառոտ ներածություն BPF-ին և eBPF-ին
Քանի որ BPF վիրտուալ մեքենան շարունակում է զարգանալ և ակտիվորեն օգտագործվում է գործնականում, մենք ձեզ համար թարգմանել ենք հոդված, որտեղ նկարագրված է դրա հիմնական առանձնահատկությունները և ներկա վիճակը:

Վերջին տարիներին ծրագրավորման գործիքներն ու տեխնիկան ձեռք են բերել ժողովրդականություն՝ փոխհատուցելու Linux միջուկի սահմանափակումները այն դեպքերում, երբ պահանջվում է բարձր արդյունավետությամբ փաթեթների մշակում: Այս տեսակի ամենատարածված մեթոդներից մեկը կոչվում է առանցքային շրջանցում (միջուկի շրջանցում) և թույլ է տալիս, բաց թողնելով միջուկի ցանցային շերտը, կատարել փաթեթների ամբողջ մշակումը օգտագործողի տարածությունից: Միջուկը շրջանցելը նաև ներառում է ցանցային քարտի կառավարում օգտագործողի տարածք. Այսինքն՝ ցանցային քարտով աշխատելիս մենք ապավինում ենք վարորդին օգտագործողի տարածք.

Ցանցային քարտի ամբողջական կառավարումը օգտատեր-տարածության ծրագրին փոխանցելով՝ մենք նվազեցնում ենք միջուկի առաջացրած գերծանրաբեռնվածությունը (համատեքստային անջատիչներ, ցանցի շերտի մշակում, ընդհատումներ և այլն), ինչը բավականին կարևոր է 10 Գբ/վ և ավելի արագությամբ աշխատելիս: Շրջանցելով միջուկը գումարած այլ հատկանիշների համակցություն (խմբաքանակի վերամշակում) և կատարողականի զգույշ կարգավորում (NUMA հաշվապահություն, CPU-ի մեկուսացումև այլն) համապատասխանում են օգտատեր-տարածության բարձր արդյունավետության ցանցի հիմունքներին: Փաթեթների մշակման այս նոր մոտեցման օրինակելի օրինակ է DPDK Intel-ից (Տվյալների ինքնաթիռի մշակման հավաքածու), չնայած կան այլ հայտնի գործիքներ և տեխնիկա, ներառյալ VPP Cisco-ից (Vector Packet Processing), Netmap-ը և, իհարկե, Սնաբբ.

Օգտագործողի տարածքում ցանցային փոխազդեցությունների կազմակերպումն ունի մի շարք թերություններ.

  • ՕՀ միջուկը աբստրակցիոն շերտ է ապարատային ռեսուրսների համար: Քանի որ օգտագործող-տիեզերական ծրագրերը պետք է ուղղակիորեն կառավարեն իրենց ռեսուրսները, նրանք նույնպես պետք է կառավարեն իրենց ապարատը: Սա հաճախ նշանակում է ծրագրավորել ձեր սեփական դրայվերները:
  • Քանի որ մենք ամբողջությամբ հրաժարվում ենք միջուկի տարածությունից, մենք նաև հրաժարվում ենք միջուկի կողմից տրամադրվող ցանցային բոլոր գործառույթներից: Օգտատերերի տարածության ծրագրերը պետք է նորից կիրառեն գործառույթներ, որոնք կարող են արդեն տրամադրվել միջուկի կամ օպերացիոն համակարգի կողմից:
  • Ծրագրերն աշխատում են sandbox ռեժիմով, ինչը լրջորեն սահմանափակում է դրանց փոխազդեցությունը և թույլ չի տալիս դրանց ինտեգրվել օպերացիոն համակարգի այլ մասերի հետ:

Ըստ էության, օգտատերերի տարածքում ցանցային կապի ժամանակ, կատարողականի ձեռքբերումները ձեռք են բերվում փաթեթների մշակումը միջուկից օգտվողի տարածք տեղափոխելու միջոցով: XDP-ն անում է ճիշտ հակառակը՝ այն տեղափոխում է ցանցային ծրագրերը օգտագործողի տարածքից (ֆիլտրեր, փոխարկիչներ, երթուղիներ և այլն) միջուկի տարածք։ XDP-ն թույլ է տալիս մեզ կատարել ցանցի գործառույթը հենց որ փաթեթը դիպչի ցանցի միջերեսին և մինչև այն սկսի շարժվել դեպի միջուկի ցանցային ենթահամակարգ: Արդյունքում, փաթեթների մշակման արագությունը զգալիորեն ավելացել է: Այնուամենայնիվ, ինչպե՞ս է միջուկը թույլ տալիս օգտվողին գործարկել իրենց ծրագրերը միջուկի տարածքում: Նախքան այս հարցին պատասխանելը, եկեք տեսնենք, թե ինչ է BPF-ն:

BPF և eBPF

Չնայած ոչ ամբողջությամբ հստակ անվանմանը, BPF-ը (Փաթեթների զտում, Բերքլի) իրականում վիրտուալ մեքենայի մոդել է: Այս վիրտուալ մեքենան ի սկզբանե նախագծված էր փաթեթների զտման համար, այստեղից էլ կոչվում է:

BPF օգտագործող առավել հայտնի գործիքներից մեկն է tcpdump. Փաթեթներ գրավելիս tcpdump Օգտագործողը կարող է նշել փաթեթների զտման արտահայտություն: Միայն այս արտահայտությանը համապատասխանող փաթեթները կգրանցվեն: Օրինակ, արտահայտությունը.tcp dst port 80” վերաբերում է բոլոր TCP փաթեթներին, որոնք ժամանում են 80 նավահանգիստ: Կոմպիլյատորը կարող է կրճատել այս արտահայտությունը՝ այն փոխակերպելով 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

Սա հիմնականում այն ​​է, ինչ անում է վերը նշված ծրագիրը.

  • Հրահանգ (000). Բեռնում է փաթեթը օֆսեթ 12-ով, որպես 16-բիթանոց բառ, կուտակիչի մեջ: Օֆսեթ 12-ը համապատասխանում է փաթեթի եթերի տիպին:
  • Հրահանգ (001): համեմատում է կուտակիչի արժեքը 0x86dd-ի հետ, այսինքն՝ IPv6-ի եթերատիպի արժեքի հետ: Եթե ​​արդյունքը ճշմարիտ է, ապա ծրագրի հաշվիչը գնում է հրահանգին (002), իսկ եթե ոչ, ապա (006):
  • Հրահանգ (006). համեմատում է արժեքը 0x800-ի հետ (եթերտիպային արժեք IPv4-ի համար): Եթե ​​պատասխանը ճիշտ է, ապա ծրագիրը գնում է (007), եթե ոչ, ապա (015):

Եվ այսպես շարունակ, մինչև փաթեթների զտման ծրագիրը արդյունք չտա: Սովորաբար դա բուլյան է: Ոչ զրոյական արժեքի վերադարձը (հրահանգ (014)) նշանակում է, որ փաթեթը համընկնում է, իսկ զրոյի վերադարձը (հրահանգ (015)) նշանակում է, որ փաթեթը չի համընկնում:

BPF վիրտուալ մեքենան և դրա բայթկոդը առաջարկվել են Սթիվ ՄաքՔենի և Վան Ջեյքոբսոնի կողմից 1992-ի վերջին, երբ հրապարակվեց նրանց փաստաթուղթը: BSD փաթեթի զտիչ. նոր ճարտարապետություն օգտագործողի մակարդակով փաթեթների գրավման համար, առաջին անգամ այս տեխնոլոգիան ներկայացվել է 1993 թվականի ձմռանը Usenix կոնֆերանսում։

Քանի որ BPF-ն վիրտուալ մեքենա է, այն սահմանում է այն միջավայրը, որտեղ ծրագրերն աշխատում են: Բացի բայթկոդից, այն նաև սահմանում է փաթեթային հիշողության մոդել (բեռնման հրահանգները անուղղակիորեն կիրառվում են փաթեթի վրա), ռեգիստրներ (A և X; կուտակիչ և ինդեքսային ռեգիստրներ), քերծվածքային հիշողության պահեստավորում և անուղղակի ծրագրի հաշվիչ։ Հետաքրքիր է, որ BPF բայթկոդը մոդելավորվել է Motorola 6502 ISA-ի հիման վրա: Ինչպես հիշեց Սթիվ Մաքքենն իր լիագումար զեկույց Sharkfest '11-ին նա ծանոթ էր 6502 build-ին ավագ դպրոցից, երբ ծրագրավորում էր Apple II-ի վրա, և այս գիտելիքն ազդեց նրա աշխատանքի վրա՝ նախագծելով BPF բայթկոդը:

BPF-ի աջակցությունն իրականացվում է Linux միջուկում v2.5 և ավելի ուշ տարբերակում, որն ավելացվել է հիմնականում Ջեյ Շուլիստի կողմից: BPF ծածկագիրը մնաց անփոփոխ մինչև 2011 թվականը, երբ Էրիկ Դյումազեթը վերանախագծեց BPF թարգմանիչը JIT ռեժիմում աշխատելու համար (Աղբյուր. JIT փաթեթային զտիչների համար) Դրանից հետո, BPF բայթկոդը մեկնաբանելու փոխարեն, միջուկը կարող էր ուղղակիորեն փոխարկել BPF ծրագրերը թիրախային ճարտարապետության՝ x86, ARM, MIPS և այլն:

Ավելի ուշ՝ 2014 թվականին, Ալեքսեյ Ստարովոյտովն առաջարկեց BPF-ի համար JIT նոր մեխանիզմ։ Փաստորեն, այս նոր JIT-ը դարձավ BPF-ի վրա հիմնված նոր ճարտարապետություն և կոչվեց eBPF: Կարծում եմ, որ երկու VM-ներն էլ գոյակցեցին որոշ ժամանակ, բայց փաթեթների զտումը ներկայումս իրականացվում է eBPF-ի վերևում: Փաստորեն, բազմաթիվ ժամանակակից փաստաթղթերի օրինակներում BPF-ն նշվում է որպես eBPF, իսկ դասական BPF-ն այսօր հայտնի է որպես cBPF:

eBPF-ն ընդլայնում է դասական BPF վիրտուալ մեքենան մի քանի ձևով.

  • Հենվում է ժամանակակից 64-բիթանոց ճարտարապետությունների վրա: eBPF-ն օգտագործում է 64-բիթանոց ռեգիստրներ և ավելացնում է հասանելի ռեգիստրների թիվը 2-ից (կուտակիչ և X) մինչև 10:
  • Անջատված է ցանցային շերտի ենթահամակարգից։ BPF-ը կապված էր խմբաքանակի տվյալների մոդելի հետ: Քանի որ այն օգտագործվում էր փաթեթները զտելու համար, դրա կոդը գտնվում էր ցանցային փոխազդեցություններ ապահովող ենթահամակարգում: Այնուամենայնիվ, eBPF վիրտուալ մեքենան այլևս կապված չէ տվյալների մոդելի հետ և կարող է օգտագործվել ցանկացած նպատակով: Այսպիսով, այժմ eBPF ծրագիրը կարող է միացվել հետագծային կետին կամ kprobe-ին: Սա դուռ է բացում eBPF գործիքավորման, կատարողականի վերլուծության և բազմաթիվ այլ օգտագործման դեպքերի համար՝ միջուկի այլ ենթահամակարգերի համատեքստում: Այժմ eBPF կոդը գտնվում է իր սեփական ճանապարհում՝ միջուկ/bpf:
  • Համաշխարհային տվյալների պահեստներ, որոնք կոչվում են Քարտեզներ: Քարտեզները առանցքային արժեքների պահեստներ են, որոնք ապահովում են տվյալների փոխանակում օգտագործողի տարածքի և միջուկի տարածքի միջև: eBPF-ն տրամադրում է մի քանի տեսակի քարտեր:
  • Երկրորդական գործառույթներ. Մասնավորապես, փաթեթը վերագրելու, ստուգման գումարը հաշվարկելու կամ փաթեթը կլոնավորելու համար: Այս գործառույթներն աշխատում են միջուկի ներսում և չեն պատկանում օգտագործողի տարածության ծրագրերին: Բացի այդ, համակարգային զանգեր կարելի է կատարել eBPF ծրագրերից:
  • Ավարտել զանգերը: Ծրագրի չափը eBPF-ում սահմանափակված է 4096 բայթով: Վերջնական զանգի հնարավորությունը eBPF ծրագրին թույլ է տալիս կառավարումը փոխանցել նոր eBPF ծրագրին և այդպիսով շրջանցել այս սահմանափակումը (մինչև 32 ծրագիր կարող է շղթայվել այս կերպ):

eBPF օրինակ

Linux միջուկի աղբյուրներում eBPF-ի մի քանի օրինակներ կան: Դրանք հասանելի են նմուշների/bpf/ հասցեով: Այս օրինակները կազմելու համար պարզապես մուտքագրեք.

$ sudo make samples/bpf/

Ես ինքս նոր օրինակ չեմ գրի eBPF-ի համար, այլ կօգտագործեմ նմուշներից/bpf/-ում առկա նմուշներից մեկը: Ես կնայեմ կոդի որոշ մասերի և կբացատրեմ, թե ինչպես է այն աշխատում: Որպես օրինակ՝ ես ընտրեցի ծրագիրը tracex4.

Ընդհանուր առմամբ, նմուշներից յուրաքանչյուրը/bpf/ բաղկացած է երկու ֆայլից։ Այս դեպքում:

  • tracex4_kern.c, պարունակում է աղբյուրի կոդը, որը պետք է կատարվի միջուկում որպես eBPF բայթկոդ:
  • tracex4_user.c, պարունակում է ծրագիր օգտագործողի տարածությունից:

Այս դեպքում մենք պետք է կազմենք tracex4_kern.c դեպի eBPF բայթկոդ: Այս պահին ներս gcc eBPF-ի համար սերվերի մաս չկա: Բարեբախտաբար, clang կարող է արտադրել eBPF բայթկոդ: Makefile օգտագործում clang կազմելու համար tracex4_kern.c օբյեկտի ֆայլին:

Վերևում նշեցի, որ eBPF-ի ամենահետաքրքիր առանձնահատկություններից մեկը քարտեզներն են։ tracex4_kern-ը սահմանում է մեկ քարտեզ.

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 eBPF-ի կողմից առաջարկվող բազմաթիվ քարտերի տեսակներից մեկն է: Այս դեպքում դա պարզապես հաշ է: Դուք նույնպես կարող եք նկատել գովազդը SEC("maps"). SEC-ը մակրո է, որն օգտագործվում է երկուական ֆայլի նոր բաժին ստեղծելու համար: Փաստորեն, օրինակում tracex4_kern սահմանվում է ևս երկու բաժին.

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

Այս երկու գործառույթները թույլ են տալիս հեռացնել գրառումը քարտեզից (kprobe/kmem_cache_free) և քարտեզի վրա ավելացրեք նոր մուտք (kretprobe/kmem_cache_alloc_node) Մեծատառերով գրված բոլոր ֆունկցիաների անունները համապատասխանում են նշված մակրոներին bpf_helpers.h.

Եթե ​​ես թափեմ օբյեկտի ֆայլի բաժինները, ես պետք է տեսնեմ, որ այս նոր բաժիններն արդեն սահմանված են.

$ 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

Կա նաեւ tracex4_user.c, հիմնական ծրագիր. Հիմնականում այս ծրագիրը լսում է իրադարձությունները kmem_cache_alloc_node. Երբ նման իրադարձություն է տեղի ունենում, համապատասխան eBPF կոդը կատարվում է: Կոդը պահպանում է օբյեկտի IP հատկանիշը քարտեզի վրա, այնուհետև օբյեկտը պտտվում է հիմնական ծրագրի միջոցով: Օրինակ:

$ 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

Ինչպե՞ս են կապված օգտվողների տիեզերական ծրագիրը և eBPF ծրագիրը: Նախաստորագրման ժամանակ tracex4_user.c բեռնում է օբյեկտի ֆայլը tracex4_kern.o օգտագործելով գործառույթը 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;
}

Անելով load_bpf_file eBPF ֆայլում սահմանված զոնդերը ավելացվում են /sys/kernel/debug/tracing/kprobe_events. Այժմ մենք լսում ենք այս իրադարձությունները, և մեր ծրագիրը կարող է ինչ-որ բան անել, երբ դրանք տեղի ունենան:

$ 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

Նմուշ/bpf/-ի մյուս բոլոր ծրագրերը կառուցված են նույն կերպ: Նրանք միշտ պարունակում են երկու ֆայլ.

  • XXX_kern.ceBPF ծրագիր:
  • XXX_user.c: Հիմնական ծրագիր.

eBPF ծրագիրը սահմանում է հատվածի հետ կապված քարտեզները և գործառույթները: Երբ միջուկը թողարկում է որոշակի տեսակի իրադարձություն (օրինակ. tracepoint), կապված ֆունկցիաները կատարվում են։ Քարտեզները ապահովում են միջուկային ծրագրի և օգտագործողի տարածության ծրագրի միջև հաղորդակցությունը:

Ամփոփում

Այս հոդվածում BPF-ն և eBPF-ն քննարկվեցին ընդհանուր առումներով: Ես գիտեմ, որ այսօր eBPF-ի մասին շատ տեղեկություններ և ռեսուրսներ կան, ուստի ես խորհուրդ կտամ ևս մի քանի նյութեր հետագա ուսումնասիրության համար:

Խորհուրդ եմ տալիս կարդալ.

Source: www.habr.com

Добавить комментарий