Кратак увод у БПФ и еБПФ

Здраво, Хабр! Обавештавамо вас да припремамо књигу за издавање“.Линук Обсервабилити са БПФ-ом".

Кратак увод у БПФ и еБПФ
Пошто БПФ виртуелна машина наставља да се развија и активно се користи у пракси, за вас смо превели чланак који описује њене главне могућности и тренутно стање.

Последњих година, програмски алати и технике постају све популарнији за компензацију ограничења Линук кернела у случајевима када је потребна обрада пакета високих перформанси. Једна од најпопуларнијих техника ове врсте тзв заобићи кернел (заобилажење кернела) и омогућава, заобилазећи слој мреже кернела, да изврши сву обраду пакета из корисничког простора. Заобилажење кернела такође укључује контролу мрежне картице из кориснички простор. Другим речима, када радимо са мрежном картицом, ослањамо се на драјвер кориснички простор.

Преносећи пуну контролу над мрежном картицом на програм у корисничком простору, смањујемо трошкове кернела (промена контекста, обрада мрежног слоја, прекиди, итд.), што је веома важно када се ради при брзинама од 10 Гб/с или већим. Заобилажење кернела плус комбинација других функција (серијска обрада) и пажљиво подешавање перформанси (НУМА рачуноводство, ЦПУ изолација, итд.) одговарају основама мрежне обраде високих перформанси у корисничком простору. Можда је пример овог новог приступа обради пакета ДПДК од Интела (Комплет за развој равни података), иако постоје и други добро познати алати и технике, укључујући Цисцо ВПП (Векторска обрада пакета), Нетмап и, наравно, снаб.

Организовање мрежних интеракција у корисничком простору има бројне недостатке:

  • ОС кернел је слој апстракције за хардверске ресурсе. Пошто програми корисничког простора морају директно да управљају својим ресурсима, они такође морају да управљају сопственим хардвером. То често значи да морате да програмирате сопствене драјвере.
  • Пошто се у потпуности одричемо простора кернела, такође се одричемо свих мрежних функционалности које нуди кернел. Програми корисничког простора морају поново да имплементирају функције које можда већ нуди кернел или оперативни систем.
  • Програми раде у сандбок режиму, што озбиљно ограничава њихову интеракцију и спречава их да се интегришу са другим деловима оперативног система.

У суштини, када се умрежавање дешава у корисничком простору, добитак у перформансама се постиже премештањем обраде пакета из језгра у кориснички простор. КСДП ради управо супротно: премешта мрежне програме из корисничког простора (филтери, разрешивачи, рутирање, итд.) у простор кернела. КСДП нам омогућава да извршимо мрежну функцију чим пакет удари у мрежни интерфејс и пре него што почне да се креће у подсистем мреже кернела. Као резултат тога, брзина обраде пакета се значајно повећава. Међутим, како кернел дозвољава кориснику да извршава своје програме у простору кернела? Пре него што одговоримо на ово питање, хајде да погледамо шта је БПФ.

БПФ и еБПФ

Упркос збуњујућем имену, БПФ (Беркелеи Пацкет Филтеринг) је, у ствари, модел виртуелне машине. Ова виртуелна машина је првобитно дизајнирана да рукује филтрирањем пакета, отуда и назив.

Један од најпознатијих алата који користе БПФ је tcpdump. Приликом хватања пакета користећи tcpdump корисник може одредити израз за филтрирање пакета. Биће ухваћени само пакети који одговарају овом изразу. На пример, израз „tcp dst port 80” се односи на све ТЦП пакете који стижу на порт 80. Компајлер може скратити овај израз тако што ће га конвертовати у БПФ бајт код.

$ 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): упоређује вредност у акумулатору са 0к86дд, односно са вредношћу етертипа за ИПв6. Ако је резултат тачан, програмски бројач иде на инструкцију (002), а ако није, онда на (006).
  • Инструкција (006): упоређује вредност са 0к800 (вредност типа етера за ИПв4). Ако је одговор тачан, онда програм иде на (007), ако није, онда на (015).

И тако све док програм за филтрирање пакета не врати резултат. Ово је обично Боолеан. Враћање вредности која није нула (инструкција (014)) значи да је пакет прихваћен, а враћање нулте вредности (инструкција (015)) значи да пакет није прихваћен.

БПФ виртуелну машину и њен бајт код предложили су Стив МекКан и Ван Џејкобсон крајем 1992. године када је њихов рад објављен. БСД филтер пакета: Нова архитектура за хватање пакета на нивоу корисника, ова технологија је први пут представљена на Усеник конференцији у зиму 1993. године.

Пошто је БПФ виртуелна машина, он дефинише окружење у коме се програми покрећу. Поред бајткода, он такође дефинише модел батцх меморије (инструкције за учитавање се имплицитно примењују на серију), регистре (А и Кс; акумулаторски и индексни регистри), складиштење меморије и имплицитни програмски бројач. Занимљиво је да је БПФ бајт код направљен по узору на Моторола 6502 ИСА. Како се Стив Мекан присећао у својој пленарни извештај на Схаркфесту '11, био је упознат са буилд 6502 из својих средњошколских дана програмирања на Аппле ИИ, и ово знање је утицало на његов рад на дизајнирању БПФ бајткода.

БПФ подршка је имплементирана у Линук кернел у верзијама в2.5 и новијим, додата углавном напорима Јаи Сцхуллист-а. БПФ код је остао непромењен до 2011. године, када је Ериц Думасет редизајнирао БПФ интерпретер да ради у ЈИТ режиму (Извор: ЈИТ за филтере пакета). После овога, кернел, уместо да тумачи БПФ бајткод, може директно да конвертује БПФ програме у циљну архитектуру: к86, АРМ, МИПС, итд.

Касније, 2014. године, Алексеј Старовоитов је предложио нови ЈИТ механизам за БПФ. У ствари, овај нови ЈИТ је постао нова архитектура заснована на БПФ-у и назван је еБПФ. Мислим да су оба ВМ-а коегзистирала неко време, али тренутно се филтрирање пакета имплементира на основу еБПФ-а. У ствари, у многим примерима модерне документације, БПФ се схвата као еБПФ, а класични БПФ је данас познат као цБПФ.

еБПФ проширује класичну БПФ виртуелну машину на неколико начина:

  • Заснован на модерној 64-битној архитектури. еБПФ користи 64-битне регистре и повећава број доступних регистара са 2 (акумулатор и Кс) на 10. еБПФ такође обезбеђује додатне кодове операција (БПФ_МОВ, БПФ_ЈНЕ, БПФ_ЦАЛЛ...).
  • Одвојен од подсистема мрежног слоја. БПФ је био везан за модел серије података. Пошто је коришћен за филтрирање пакета, његов код се налазио у подсистему који обезбеђује мрежну комуникацију. Међутим, еБПФ виртуелна машина више није везана за модел података и може се користити у било коју сврху. Дакле, сада се еБПФ програм може повезати на трацепоинт или кпробе. Ово отвара пут еБПФ инструментацији, анализи перформанси и многим другим случајевима коришћења у контексту других подсистема кернела. Сада се еБПФ код налази на сопственој путањи: кернел/бпф.
  • Глобална складишта података под називом Мапе. Мапе су складишта кључ/вредност која омогућавају размену података између корисничког простора и простора кернела. еБПФ нуди неколико типова мапа.
  • Секундарне функције. Конкретно, да бисте поново написали пакет, израчунајте контролни збир или клонирајте пакет. Ове функције се покрећу унутар кернела и нису програми корисничког простора. Такође можете да упућујете системске позиве из еБПФ програма.
  • Завршите позиве. Величина програма у еБПФ-у је ограничена на 4096 бајтова. Функција таил цалл дозвољава еБПФ програму да пренесе контролу на нови еБПФ програм и тако заобиђе ово ограничење (на овај начин се могу повезати до 32 програма).

еБПФ: пример

Постоји неколико примера за еБПФ у изворима Линук кернела. Доступни су на самплес/бпф/. Да бисте саставили ове примере, једноставно унесите:

$ sudo make samples/bpf/

Нећу сам писати нови пример за еБПФ, већ ћу користити један од узорака доступних у самплес/бпф/. Погледаћу неке делове кода и објаснити како функционише. Као пример, изабрао сам програм tracex4.

Генерално, сваки од примера у самплес/бпф/ се састоји од две датотеке. У овом случају:

  • tracex4_kern.c, садржи изворни код који треба да се изврши у кернелу као еБПФ бајт код.
  • tracex4_user.c, садржи програм из корисничког простора.

У овом случају, морамо компајлирати tracex4_kern.c у еБПФ бајт код. Тренутно у gcc не постоји бацкенд за еБПФ. Срећом, clang може да избаци еБПФ бајт код. Makefile користи clang за компилацију tracex4_kern.c у објектну датотеку.

Горе сам споменуо да су мапе једна од најзанимљивијих карактеристика еБПФ-а. трацек4_керн дефинише једну мапу:

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 је једна од многих врста картица које нуди еБПФ. У овом случају, то је само хеш. Можда сте приметили и оглас SEC("maps"). СЕЦ је макро који се користи за креирање новог одељка бинарне датотеке. Заправо, у примеру 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. Када дође до таквог догађаја, извршава се одговарајући еБПФ код. Код чува ИП атрибут објекта у мапи, а затим се објекат избацује у петљу у главном програму. Пример:

$ 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

Како су повезани кориснички просторни програм и еБПФ програм? Приликом иницијализације 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 сонде дефинисане у еБПФ датотеци се додају у /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

Сви остали програми у сампле/бпф/ су структурирани слично. Увек садрже две датотеке:

  • XXX_kern.c: еБПФ програм.
  • XXX_user.c: главни програм.

еБПФ програм идентификује мапе и функције повезане са секцијом. Када кернел изда догађај одређеног типа (нпр. tracepoint), везане функције се извршавају. Картице обезбеђују комуникацију између програма кернела и корисничког свемирског програма.

Закључак

У овом чланку се уопштено говори о БПФ-у и еБПФ-у. Знам да данас има много информација и ресурса о еБПФ-у, па ћу препоручити још неколико ресурса за даље проучавање

Препоручујем читање:

Извор: ввв.хабр.цом

Додај коментар