BPF жана eBPFге кыскача киришүү

Салам, Хабр! Китепти басып чыгарууга даярдап жатканыбызды билдиребиз».BPF менен Linux байкоо мүмкүнчүлүгү".

BPF жана eBPFге кыскача киришүү
BPF виртуалдык машинасы өнүгүп келе жаткандыктан жана практикада жигердүү колдонулуп жаткандыктан, биз анын негизги өзгөчөлүктөрүн жана учурдагы абалын сүрөттөгөн макаланы сиздер үчүн котордук.

Акыркы жылдары, программалоо куралдары жана ыкмалары Linux ядросунун чектөөлөрүнүн ордун толтуруу үчүн барган сайын популярдуу болуп, пакеттерди жогорку өндүрүмдүүлүктө иштетүү талап кылынат. Бул түрдөгү эң популярдуу техникалардын бири деп аталат негизги айланып өтүү (ядрону айланып өтүү) жана ядронун тармактык катмарын өткөрүп жиберүү менен колдонуучу мейкиндигинен бардык пакеттерди иштетүүнү аткарууга мүмкүндүк берет. Ядрону айланып өтүү тармактык картаны башкарууну да камтыйт колдонуучу мейкиндиги. Башкача айтканда, тармактык карта менен иштөөдө биз драйверге таянабыз колдонуучу мейкиндиги.

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

Колдонуучу мейкиндигинде тармактык өз ара аракеттенүүнү уюштуруу бир катар кемчиликтерге ээ:

  • OS ядросу аппараттык ресурстар үчүн абстракция катмары болуп саналат. Колдонуучу мейкиндик программалары өз ресурстарын түздөн-түз башкарууга туура келгендиктен, алар өздөрүнүн аппараттык каражаттарын да башкаруусу керек. Бул көбүнчө өз драйверлериңизди программалоону билдирет.
  • Биз ядро ​​мейкиндигинен толугу менен баш тарткандыктан, ядро ​​тарабынан камсыз кылынган бардык тармактык функциялардан да баш тартабыз. Колдонуучу мейкиндик программалары ядро ​​же операциялык тутум тарабынан берилген функцияларды кайра ишке ашырышы керек.
  • Программалар кумдук режимде иштешет, бул алардын өз ара аракеттенүүсүн олуттуу чектейт жана операциялык системанын башка бөлүктөрүнө интеграциялануусуна жол бербейт.

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

BPF жана eBPF

Толук так эмес аталышына карабастан, BPF (Пакет чыпкалоо, Беркли) чындыгында виртуалдык машина модели. Бул виртуалдык машина алгач пакеттик чыпкалоону иштетүү үчүн иштелип чыккан, ошондуктан аты.

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 биттик сөз катары аккумуляторго жүктөйт. Offset 12 пакеттин этертипине туура келет.
  • Көрсөтмө (001): аккумулятордогу маанини 0x86dd менен, башкача айтканда, IPv6 үчүн ethertype мааниси менен салыштырат. Эгерде натыйжа чын болсо, анда программалык эсептегич инструкцияга (002), ал эми жок болсо, анда (006) барат.
  • Көрсөтмө (006): маанини 0x800 (IPv4 үчүн ethertype мааниси) менен салыштырат. Эгерде жооп чын болсо, анда программа (007), эгер жок болсо, анда (015) болот.

Пакетти чыпкалоо программасы натыйжа бергенге чейин. Адатта бул логикалык. Нөл эмес маанини кайтаруу (инструкция (014)) пакеттин кабыл алынганын билдирет, ал эми нөл маанисин кайтаруу (инструкция (015)) пакеттин кабыл алынбагандыгын билдирет.

BPF виртуалдык машинасы жана анын байткоду 1992-жылдын аягында алардын кагазы чыкканда Стив МакКэнн жана Ван Джейкобсон тарабынан сунушталган. BSD пакет чыпкасы: Колдонуучу деңгээлиндеги пакеттерди басып алуу үчүн жаңы архитектура, биринчи жолу бул технология 1993-жылдын кышында Usenix конференциясында сунушталган.

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

BPF колдоосу Linux ядросунда v2.5 жана андан кийинки версияларда ишке ашырылат, аны негизинен Джей Шуллист кошкон. BPF коду 2011-жылга чейин өзгөрүүсүз калган, Эрик Дюмасет BPF котормочусун JIT режиминде иштөө үчүн кайра иштеп чыккан (Булак: пакет чыпкалары үчүн JIT). Андан кийин, ядро ​​BPF байт кодун чечмелөөнүн ордуна, BPF программаларын түздөн-түз максаттуу архитектурага айландыра алат: x86, ARM, MIPS ж.б.

Кийинчерээк, 2014-жылы, Алексей Starovoitov BPF үчүн жаңы JIT механизмин сунуш кылган. Чынында, бул жаңы JIT BPF негизинде жаңы архитектура болуп калды жана eBPF деп аталды. Менин оюмча, эки VM тең бир нече убакыт бою бирге жашаган, бирок пакеттик чыпкалоо учурда eBPFтин үстүндө ишке ашырылууда. Чынында, көптөгөн заманбап документтердин мисалдарында, BPF eBPF деп аталат, ал эми классикалык BPF бүгүн cBPF катары белгилүү.

eBPF классикалык BPF виртуалдык машинасын бир нече жол менен кеңейтет:

  • Заманбап 64-бит архитектурасына таянат. eBPF 64 биттик регистрлерди колдонот жана жеткиликтүү регистрлердин санын 2ден (аккумулятор жана X) 10го чейин көбөйтөт. eBPF ошондой эле кошумча операция коддорун (BPF_MOV, BPF_JNE, BPF_CALL...) камсыз кылат.
  • Тармак катмарынын подсистемасынан ажыратылган. BPF сериялык маалымат моделине байланган. Ал пакеттерди чыпкалоо үчүн колдонулгандыктан, анын коду тармактык өз ара аракеттенүүнү камсыз кылган подсистемада болгон. Бирок, eBPF виртуалдык машинасы мындан ары маалымат моделине байланыштуу эмес жана аны каалаган максатта колдонсо болот. Ошентип, азыр eBPF программасы tracepoint же kprobe менен туташа алат. Бул eBPF инструменттерине, өндүрүмдүүлүктү талдоого жана башка ядролук подсистемалардын контекстинде көптөгөн башка колдонуу учурларына эшик ачат. Азыр eBPF коду өз жолунда жайгашкан: kernel/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.c: eBPF программасы.
  • XXX_user.c: негизги программа.

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

жыйынтыктоо

Бул макалада жалпысынан BPF жана eBPF талкууланды. Мен бүгүн eBPF жөнүндө көп маалымат жана ресурстар бар экенин билем, ошондуктан мен андан ары изилдөө үчүн дагы бир нече ресурстарды сунуштайм

Мен окууну сунуштайм:

Source: www.habr.com

Комментарий кошуу