LINUX.ORG.RU

вопрос по BPF

 ,


2

2

Привветствую,

Вот есть два типа BPF - classic BPF (cBPF) и extended BPF (eBPF). Как я понимаю, второй 64-битный, а cBPF - 32-битный. То есть получается что cBPF код внутри ядра будет транслироваться в 64-битный перед выполнением? И еще - eBPF всегда требует встроенной в ядро виртуальной машины (echo 1 > /proc/sys/net/core/bpf_jit_enable)? Как eBPF будет выполняться без включенной VM ?

★★

BPF - специфиакция виртуальной машины. jit (и интерпретация) - два способа реализации машины.

Проще и быстрее всего использовать родные операции того же размера, что и разрядность виртуальной машины, но не обязательно. Лучше всего смотреть на реализацию для конкретной архитектуры.

Когда jit отключен используется интерпретатор: http://elixir.free-electrons.com/linux/v4.1/source/kernel/bpf/core.c#L186

Когда jit включен __bpf_prog_run будет сгенерен jit компилятором для родной архитектуры

void bpf_prog_select_runtime(struct bpf_prog *fp)
{
	fp->bpf_func = (void *) __bpf_prog_run;

	/* Probe if internal BPF can be JITed */
	bpf_int_jit_compile(fp);
	/* Lock whole bpf_prog as read-only */
	bpf_prog_lock_ro(fp);
}

Например, x86_64: http://elixir.free-electrons.com/linux/v3.18/source/arch/x86/net/bpf_jit_comp...

sf ★★★
()
Ответ на: комментарий от sf

Спасибо. Поясните, чем отличается jit от интерпретатора? JIT, как я понимаю, интерпретирует в native code «на лету» ?

cruz7 ★★
() автор топика
Ответ на: комментарий от cruz7

В режиме интерпретатора на каждый IP пакет вызыывется функция __bpf_prog_run, которая побайтно декодирует байткод BPF:

static unsigned int __bpf_prog_run(void *ctx, const struct bpf_insn *insn) {
    select_insn:
    goto *jumptable[insn->code]; // [1]
    ...
    LD_IMM_DW:
        DST = (u64) (u32) insn[0].imm | ((u64) (u32) insn[1].imm) << 32; // [2]
        insn++;
        CONT; // [3]

Здесь происходит следующее:

  • 1. считывается байт
  • 2. выполняется собственно инструкция
  • 3. переход на [1]

Смысл jit в том, чтобы выбросить шаги [1] и [3] сгенерировав код эквивалентный этому интерпретатору. Ядро linux это делает в bpf_int_jit_compile генерируя функцию в момент регистрации BPF фильтра:

void bpf_int_jit_compile(struct bpf_prog *prog) {
    ....
    u8 *image = NULL;
    do_jit(prog, addrs, image, oldproglen, &ctx);
    prog->bpf_func = (void *)image;

Функция do_jit() записывает эквивалентный код в image, который бы выполнил интерпретатор:

static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image, int oldproglen, struct jit_context *ctx) {
    ...
    case BPF_LD | BPF_IMM | BPF_DW:
        /* movabsq %rax, imm64 */
        EMIT2(add_1mod(0x48, dst_reg), add_1reg(0xB8, dst_reg));
        EMIT(insn[0].imm, 4);
        EMIT(insn[1].imm, 4);
        insn++;
        i++;
        break;

В итоге при обработке каждого пакета будет вызываться код в image: одна сгенеренная инструкция movabs (не учитывая сгенеренного пролога/эпилога).

Итого:

  • без JIT BPF-фильтры исполняются интерпретатором декодируя каждую исполняемую команду байткода фильтра (для каждого пакета)
  • с JIT BPF-фильтры исполняют уже скомпилированный native код минуя стадию декодирования
  • c JIT BPF-фильтры используют реальные регистры процессора для виртуальных регистров, а не память (как в интерпретаторе)
sf ★★★
()
Ответ на: комментарий от sf

Спасибо, намного понятнее. Для bpf-кода прикрепленного к сокету через setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, ...) логика такая же? То есть либо интерпретатор, либо JIT? Я стал прослеживать код SYSCALL_DEFINE5(setsockopt) в net/socket.c но дальше следы затерялись и не понял, как будет вызываться jit машина.

cruz7 ★★
() автор топика
Ответ на: комментарий от cruz7

Кодогенерация/регистрация jit:

Вызов jit (или не-jit) кода по обработке пакета. Проще посмотреть в обратном порядке вызовов. Так как вызывающих много, можно посмотреть на ipv4/tcp:

sf ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.