Почему kretprobe на sys_futex вызывается реже, чем соответствующий kprobe?

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

Одна вещь, которую я заметил, заключается в том, что иногда, даже в моем простом тестовом приложении, которое запускает несколько потоков, играющих с некоторыми мьютексами, я не получаю никаких вызовов kretprobe__sys_futex, но получаю много вызовов kprobe__sys_futex.

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

Затем я предположил, что проблема заключалась в том, как я фильтровал вызовы kprobe__sys_futex, поэтому я сделал минимальный пример, используя BCC/eBPF, чтобы проверить это:

#! /usr/bin/env python

from bcc import BPF

b = BPF(text="""
BPF_HASH(call_count, int, int);

int kprobe__sys_futex() {
  int zero = 0;
  call_count.lookup_or_init(&zero, &zero);
  bpf_trace_printk("futex start\\n");
  call_count.increment(zero);
  return 0;
}

int kretprobe__sys_futex() {
  int zero = 0;
  int *val_p = call_count.lookup(&zero);
  if (val_p != NULL) {
      int val = *val_p;
      val--;
      call_count.update(&zero, &val);
      bpf_trace_printk("futex calls with no return: %d\\n", val);
  } else { bpf_trace_printk("unexpected futex return\\n"); }
  return 0;
}
""")

b.trace_print()

Я замечаю, что во всевозможных приложениях (хорошим примером является mysql-server, который выполняет обычные операции с фьютексами даже в состоянии простоя — по крайней мере, на моей машине) многие (часто 10+) futex start печатаются перед сообщением от return probe.

Вот пример трассировки вышеуказанной программы, которую я оставил запущенной на пару минут, пока писал этот пост:

... hundreds of lines of much the same as below
           gdbus-612   [001] .... 211229.997665: 0x00000001: futex start
  NetworkManager-541   [001] .... 211229.997667: 0x00000001: futex start
           gdbus-612   [001] .... 211229.997670: 0x00000001: futex start
          mysqld-697   [001] .... 211230.789205: 0x00000001: futex start
          mysqld-697   [001] .... 211230.789227: 0x00000001: futex start
          mysqld-703   [001] .... 211230.789251: 0x00000001: futex start
          mysqld-703   [001] .... 211230.789253: 0x00000001: futex start
          mysqld-704   [001] d... 211230.789258: 0x00000001: futex calls with no return: 3994
          mysqld-704   [001] .... 211230.789259: 0x00000001: futex start
          mysqld-704   [001] d... 211230.789260: 0x00000001: futex calls with no return: 3994
          mysqld-704   [001] .... 211230.789272: 0x00000001: futex start
          mysqld-713   [000] .... 211231.037016: 0x00000001: futex start
          mysqld-713   [000] .... 211231.037036: 0x00000001: futex start
         vmstats-895   [000] .... 211231.464867: 0x00000001: futex start
          mysqld-697   [001] .... 211231.790738: 0x00000001: futex start
          mysqld-697   [001] .... 211231.790784: 0x00000001: futex start
          mysqld-703   [001] .... 211231.790796: 0x00000001: futex start
          mysqld-703   [001] .... 211231.790799: 0x00000001: futex start
          mysqld-704   [001] d... 211231.790809: 0x00000001: futex calls with no return: 4001
          mysqld-704   [001] .... 211231.790812: 0x00000001: futex start
          mysqld-704   [001] d... 211231.790814: 0x00000001: futex calls with no return: 4001

Как видите, pid 697, например, четыре раза вызывал sys_futex, не возвращаясь только в этой небольшой трассировке.

Я не думаю, что это состояние гонки в коде eBPF, потому что, если вы отключите операторы печати и печатаете только периодически, счетчик обычно ограничен несколькими около нуля для sys_write, что происходит на порядок чаще, чем sys_futex (по крайней мере, на моем нагрузка на систему), поэтому я ожидаю, что любые условия гонки будут усугубляться, а не решаться.

Я использую Kernel 4.15.0-43-generic на Ubuntu 18.04 LTS, который находится в VirtualBox.

Рад предоставить любой другой контекст, который может быть полезен!

В списке рассылки IOVisor есть соответствующая ветка: https://lists.iovisor.org/g/iovisor-dev/topic/29702757


person Ashley Davies    schedule 07.02.2019    source источник
comment
Если у вас есть эквивалентный код модуля ядра, который не имеет такой же проблемы, это поможет отладить!   -  person pchaigno    schedule 10.02.2019
comment
Извиняюсь! - прямо сейчас пишу ответ на ваше сообщение в рассылку; Я ожидал отправить его некоторое время назад, но обнаружил, что модуль ядра тоже иногда сбрасывал вызовы.   -  person Ashley Davies    schedule 10.02.2019


Ответы (1)


Это известное ограничение скрытой копии (см. iovisor/bcc#1072). По сути, максимальное количество активных тестов слишком низкое для вашего контекста трассировки, и поэтому вы пропускаете некоторые возвратные тесты.

В bcc значение maxactive (максимальное количество активных зондов, см. отрывок из документации ниже) остается равным значению по умолчанию. После патча Alban Crequy для ядра Linux (см. iovisor/bcc#1072), можно изменить значение maxactive при подключении зондов через debugfs. Однако этот новый API еще не был представлен через скрытую копию. На этой неделе постараюсь выслать соответствующий патч.

Пока выполняется зондируемая функция, ее адрес возврата сохраняется в объекте типа kretprobe_instance. Перед вызовом register_kretprobe() пользователь задает поле maxactive структуры kretprobe, чтобы указать, сколько экземпляров указанной функции можно проверять одновременно. register_kretprobe() предварительно выделяет указанное количество объектов kretprobe_instance.

Например, если функция нерекурсивная и вызывается с удерживаемой спин-блокировкой, maxactive = 1 должно быть достаточно. Если функция нерекурсивная и никогда не может освободить ЦП (например, через семафор или вытеснение), NR_CPUS должно быть достаточно. Если maxactive ‹= 0, устанавливается значение по умолчанию. Если CONFIG_PREEMPT включен, значение по умолчанию равно max(10, 2*NR_CPUS). В противном случае по умолчанию используется NR_CPUS.

person pchaigno    schedule 17.02.2019
comment
Я не был полностью уверен, что это полностью объясняет, почему у меня регулярно бывают прогоны, в которых, похоже, почти не срабатывают ретзонды, в то время как некоторые надежно их отслеживают, но я просто понял, что это, вероятно, связано с тем, что несколько потоков в системе спят на фьютексе. в течение нескольких секунд повторяется без других действий (смотря на вас, mysql), каждый из которых постоянно потребляет один из зондов. Это объясняет, почему я мог наблюдать эту проблему только с sys_futex (sys_write возвращался слишком быстро в моей системе, чтобы это стало проблемой). Еще раз спасибо за ваше время и усилия, помогающие с этим! - person Ashley Davies; 17.02.2019