BPF болон eBPF-ийн товч танилцуулга

Сайн уу, Хабр! Бид номоо худалдаанд гаргахаар бэлтгэж байгааг дуулгая" гэлээ.BPF-тэй Линуксийн ажиглалт".

BPF болон eBPF-ийн товч танилцуулга
BPF виртуал машин нь хөгжиж, практикт идэвхтэй ашиглагдаж байгаа тул бид танд түүний үндсэн чадвар, өнөөгийн байдлыг тодорхойлсон нийтлэлийг орчуулан хүргэж байна.

Сүүлийн жилүүдэд өндөр хүчин чадалтай пакет боловсруулах шаардлагатай тохиолдолд Линуксийн цөмийн хязгаарлалтыг нөхөх програмчлалын хэрэгсэл, техникүүд улам бүр түгээмэл болж байна. Энэ төрлийн хамгийн алдартай техникүүдийн нэгийг нэрлэдэг цөмийн тойрч гарах (цөмийг тойрч гарах) бөгөөд цөмийн сүлжээний давхаргыг алгасаж, хэрэглэгчийн орон зайгаас бүх пакет боловсруулалтыг гүйцэтгэх боломжийг олгодог. Цөмийг тойрч гарах нь сүлжээний картыг удирдахад мөн хамаарна хэрэглэгчийн орон зай. Өөрөөр хэлбэл, сүлжээний карттай ажиллахдаа бид драйвер дээр тулгуурладаг хэрэглэгчийн орон зай.

Сүлжээний картын бүрэн хяналтыг хэрэглэгчийн орон зайн програм руу шилжүүлснээр бид цөмийн ачааллыг (контекст шилжих, сүлжээний давхаргын боловсруулалт, тасалдал гэх мэт) багасгадаг бөгөөд энэ нь 10 Гб/с ба түүнээс дээш хурдтай ажиллахад маш чухал юм. Цөмийг тойрон гарах ба бусад функцуудын хослол (багц боловсруулах) болон гүйцэтгэлийг сайтар тааруулах (NUMA нягтлан бодох бүртгэл, CPU-ийн тусгаарлалт, гэх мэт) нь хэрэглэгчийн орон зайд өндөр гүйцэтгэлтэй сүлжээний боловсруулалтын үндэс суурьтай нийцдэг. Пакет боловсруулах энэхүү шинэ аргын үлгэр жишээ жишээ байж болох юм DPDK Intel-ээс (Data Plane Development Kit), Cisco-ийн VPP (Vector Packet Processing), Nemap болон мэдээжийн хэрэг, бусад алдартай хэрэгсэл, техникүүд байдаг. Снабб.

Хэрэглэгчийн орон зайд сүлжээний харилцан үйлчлэлийг зохион байгуулах нь хэд хэдэн сул талуудтай:

  • OS цөм нь техник хангамжийн нөөцөд зориулсан хийсвэр давхарга юм. Хэрэглэгчийн орон зайн программууд нь нөөцөө шууд удирдах ёстой тул өөрийн техник хангамжийг удирдах ёстой. Энэ нь ихэвчлэн өөрийн драйверуудыг програмчлах шаардлагатай гэсэн үг юм.
  • Бид цөмийн орон зайг бүхэлд нь орхиж байгаа тул цөмөөр хангагдсан сүлжээний бүх функцээс татгалзаж байна. Хэрэглэгчийн орон зайн програмууд нь цөм эсвэл үйлдлийн системээс аль хэдийн хангагдсан байж болох функцуудыг дахин хэрэгжүүлэх ёстой.
  • Хөтөлбөрүүд хамгаалагдсан хязгаарлагдмал орчинд ажилладаг бөгөөд энэ нь тэдний харилцан үйлчлэлийг ноцтойгоор хязгаарлаж, үйлдлийн системийн бусад хэсгүүдтэй нэгтгэхээс сэргийлдэг.

Үндсэндээ хэрэглэгчийн орон зайд сүлжээ байгуулах үед пакет боловсруулалтыг цөмөөс хэрэглэгчийн орон зай руу шилжүүлснээр гүйцэтгэлийн өсөлтөд хүрдэг. XDP нь яг эсрэгээр ажилладаг: сүлжээний программуудыг хэрэглэгчийн орон зайнаас (шүүлтүүр, шийдүүлэгч, чиглүүлэлт гэх мэт) цөмийн орон зай руу шилжүүлдэг. XDP нь пакет сүлжээний интерфэйстэй таарч, цөмийн сүлжээний дэд систем рүү шилжиж эхлэхээс өмнө сүлжээний функцийг гүйцэтгэх боломжийг бидэнд олгодог. Үүний үр дүнд пакет боловсруулах хурд мэдэгдэхүйц нэмэгддэг. Гэсэн хэдий ч цөм нь хэрэглэгчдэд цөмийн орон зайд програмаа ажиллуулах боломжийг хэрхэн олгодог вэ? Энэ асуултад хариулахын өмнө BPF гэж юу болохыг харцгаая.

BPF ба eBPF

Хэдийгээр ойлгомжгүй нэртэй ч BPF (Berkeley Packet Filtering) нь үнэн хэрэгтээ виртуал машины загвар юм. Энэхүү виртуал машин нь пакет шүүлтүүрийг зохицуулах зориулалттай байсан тул ийм нэрээр нэрлэсэн.

BPF ашигладаг хамгийн алдартай хэрэгслүүдийн нэг юм tcpdump. ашиглан пакетуудыг барьж авах үед tcpdump хэрэглэгч пакетуудыг шүүх илэрхийлэлийг зааж өгч болно. Зөвхөн энэ илэрхийлэлтэй таарах пакетуудыг авах болно. Жишээ нь, илэрхийлэл "tcp dst port 80” нь 80-р порт дээр ирж буй бүх TCP пакетуудыг хэлнэ. Хөрвүүлэгч энэ илэрхийллийг 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-д зориулсан ethertype утгатай харьцуулна. Хэрэв үр дүн үнэн бол програмын тоолуур заавар (002), үгүй ​​бол (006) руу очно.
  • Заавар (006): утгыг 0x800 (IPv4-ийн ethertype утга)-тай харьцуулна. Хэрэв хариулт үнэн бол програм (007), үгүй ​​бол (015) руу очно.

Пакет шүүлтүүрийн програм нь үр дүнг буцаах хүртэл үргэлжилнэ. Энэ нь ихэвчлэн Boolean юм. Тэг биш утгыг буцаах нь (заавар (014)) нь багцыг хүлээн зөвшөөрсөн гэсэн үг бөгөөд тэг утгыг буцаах (заавар (015)) нь пакетыг хүлээн аваагүй гэсэн үг юм.

BPF виртуал машин болон түүний байт кодыг 1992 оны сүүлээр Стив МакКэнн, Ван Жэйкобсон нар өөрсдийн нийтлэлийг хэвлэхэд санал болгосон. BSD пакет шүүлтүүр: Хэрэглэгчийн түвшний пакет барих шинэ архитектур, энэ технологийг анх 1993 оны өвөл Usenix чуулган дээр танилцуулсан.

BPF нь виртуал машин учраас программуудын ажиллах орчныг тодорхойлдог. Энэ нь байт кодоос гадна багц санах ойн загварыг (ачааллын зааврыг багцад далд байдлаар ашигладаг), регистрүүд (A ба X; аккумлятор ба индексийн бүртгэлүүд), зурлага санах ойн хадгалалт, далд програмын тоолуурыг тодорхойлдог. Сонирхолтой нь, BPF байт кодыг Motorola 6502 ISA-ийн дагуу загварчилсан. Стив МакКэнн дурссанчлан чуулганы тайлан Sharkfest '11 дээр тэрээр ахлах сургуульд байхдаа Apple II дээр програмчлалын 6502-ыг мэддэг байсан бөгөөд энэ мэдлэг нь BPF байт кодыг зохион бүтээх ажилд нь нөлөөлсөн.

BPF-ийн дэмжлэгийг Линуксийн цөмд 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 хүртэл нэмэгдүүлдэг. eBPF нь нэмэлт opcodes (BPF_MOV, BPF_JNE, BPF_CALL...) өгдөг.
  • Сүлжээний давхаргын дэд системээс салсан. BPF нь багц өгөгдлийн загвартай холбогдсон. Энэ нь пакет шүүлтүүрт ашиглагдаж байсан тул код нь сүлжээний холболтыг хангадаг дэд системд байрладаг. Гэсэн хэдий ч eBPF виртуал машин нь өгөгдлийн загвартай холбоогүй бөгөөд ямар ч зорилгоор ашиглах боломжтой. Тиймээс одоо eBPF програмыг tracepoint эсвэл kprobe-д холбож болно. Энэ нь бусад цөмийн дэд системүүдийн хүрээнд eBPF багаж хэрэгсэл, гүйцэтгэлийн шинжилгээ болон бусад олон хэрэглээний тохиолдлуудад хүрэх замыг нээж өгдөг. Одоо eBPF код нь өөрийн замд байрладаг: kernel/bpf.
  • Газрын зураг гэж нэрлэгддэг дэлхийн мэдээллийн сангууд. Газрын зураг нь хэрэглэгчийн орон зай болон цөмийн орон зай хооронд өгөгдөл солилцох боломжийг олгодог түлхүүр-үнэ цэнэгийн хадгалалт юм. eBPF нь хэд хэдэн төрлийн газрын зургийг өгдөг.
  • Хоёрдогч функцууд. Ялангуяа багцыг дахин бичих, шалгах нийлбэрийг тооцоолох, багцыг хуулбарлах. Эдгээр функцууд нь цөм дотор ажилладаг бөгөөд хэрэглэгчийн орон зайн програм биш юм. Та мөн eBPF програмуудаас системийн дуудлага хийх боломжтой.
  • Дуудлага дуусгах. eBPF дахь програмын хэмжээ 4096 байтаар хязгаарлагддаг. Сүүлд залгах функц нь eBPF програмд ​​хяналтыг шинэ eBPF програм руу шилжүүлэх боломжийг олгодог бөгөөд ингэснээр энэ хязгаарлалтыг давж гарах боломжтой (32 хүртэлх програмыг ингэж холбож болно).

eBPF: жишээ

Линуксийн цөмийн эх сурвалжид 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

Sample/bpf/ дээрх бусад бүх программууд ижил төстэй бүтэцтэй. Тэд үргэлж хоёр файл агуулдаг:

  • XXX_kern.c: eBPF хөтөлбөр.
  • XXX_user.c: үндсэн програм.

eBPF програм нь тухайн хэсэгтэй холбоотой газрын зураг болон функцуудыг тодорхойлдог. Цөм нь тодорхой төрлийн үйл явдлыг гаргах үед (жишээлбэл, tracepoint), холбогдсон функцууд гүйцэтгэгдэнэ. Картууд нь цөмийн программ болон хэрэглэгчийн орон зайн программ хоорондын холбоог хангадаг.

дүгнэлт

Энэ нийтлэлд BPF болон eBPF-ийг ерөнхийд нь авч үзсэн. Өнөөдөр eBPF-ийн талаар маш их мэдээлэл, нөөц байгааг би мэдэж байгаа тул цаашдын судалгаанд зориулж цөөн хэдэн эх сурвалж санал болгох болно.

Би уншихыг зөвлөж байна:

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх