От Вирадж Ариявангша — младшего инженера-электронщика-стажера

Linux — одна из самых используемых операционных систем в мире. Linux является популярным выбором среди разработчиков и системных администраторов по всему миру, поскольку на нем работает более 95% из миллиона лучших серверов в мире. Основная причина, по которой Linux удалось добиться такой известности, как сегодня, связана с его легко адаптируемым ядром и наличием широкого спектра инструментов с открытым исходным кодом, в то время как Linux сам по себе является проектом с открытым исходным кодом. И eBPF, также называемый расширенным фильтром пакетов Berkeley, является одним из таких мощных инструментов, которые можно использовать для отслеживания ядра Linux.

eBPF изначально был выпущен и использовался как трассировщик пакетов под названием BPF. Затем он был улучшен и выпущен как расширенная версия, которая была интегрирована в ядро ​​​​Linux в 2014 году после реализации своих сверхспособностей внутри ядра. Хотя он используется для трассировки в большинстве современных приложений, на этом его возможности не заканчиваются. Что он на самом деле делает, так это позволяет приложениям пользовательского пространства запускать программы в пространстве ядра с существенно низкими накладными расходами. В этом вся разница и корона eBPF как одного из самых мощных инструментов в своем роде. Чтобы понять, почему это важно, давайте сначала кратко рассмотрим разницу между пространством пользователя и пространством ядра.

В любом современном компьютере память разделена на две части, называемые пространством пользователя и пространством ядра. Пространство пользователя — это место, где работают все программные приложения и пользовательские программы, а пространство ядра — это раздел, в котором работают ядро ​​и многие драйверы. Ядро реализует связь между оборудованием и операционной системой. Когда приложение выполняется, оно выполняется в пользовательском пространстве.

Всякий раз, когда ему требуются функции пространства ядра, такие как запись в/из ввода-вывода, запись на диск или запуск процесса, он запускает соответствующий системный вызов, аналогичный сигналу, который передается ядру для запуска требуемого процесса. подпрограмма для реализации чтения/записи ввода-вывода, записи на диск или планировщика. Системные вызовы — это единственное средство, с помощью которого программы пользовательского пространства могут достичь функциональности ядра. В ядре могут выполняться только привилегированные программы. Такой подход предотвращает сбой ядра из-за любых уязвимостей или небезопасных программ, обеспечивая при этом безопасность.

Здесь в игру вступает eBPF. eBPF реализует песочницу внутри ядра, где программа пользовательского пространства может безопасно запускать код внутри нее. А также выполняет проверку программы на наличие уязвимостей, чтобы убедиться, что программа безопасна для запуска. Таким образом, это помогает программам пользовательского пространства обходить барьер на пути к ядру.

Как программы пользовательского уровня реализуют программу внутри ядра

Чтобы использовать этот инструмент для трассировки ядра Linux, необходимо выполнить два важных требования.

  • К программе должна быть прикреплена точка трассировки или зонд из ядра
  • Соответствующая точка трассировки/зонд должна быть соблюдена

Точка трассировки/kprobe — это ловушка или метка в ядре, которую можно прикрепить к программе, чтобы обнаружить определенное поведение и проверить ядро, как определено программой. Сценарий, написанный с помощью eBPF, должен обрабатывать обе эти ситуации. Есть два основных инструмента, которые обычно используются для программирования eBPF. Первый — это фреймворк с именем bpftrace, который можно использовать для реализации однострочников, отслеживающих функции ядра. Другой — это API-интерфейс Python под названием bcc (набор компиляторов BPF), который широко используется для более сложных программ.

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

Программа пространства пользователя (USP) объявляет программу пространства ядра и прикрепляет ее к соответствующей точке трассировки/зонду.

Программа в пространстве ядра (KSP) — это то, что запускается и запускается внутри ядра после достижения точки трассировки/проверки.

Программа BPF, которая объединяет эти две программы, работает следующим образом:

  1. Объявите программу, которая будет выполняться при достижении точки трассировки/зонда (KSP).
  2. Прикрепите kprobe/tracepoint к программе
  3. Дождитесь точки трассировки/kprobe
  4. Точка трассировки/зонд достигнута и зацеплена
  5. KSP запускается, и информация извлекается из ядра
  6. Передача данных обратно в пространство пользователя через буферы
  7. Прием, анализ и обработка передаваемых данных в УТП

Точки отслеживания

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

В ядре Linux доступно большое количество точек трассировки и зондов (kprobes, uprobes, kretprobes и т. д.), и каждый из этих ловушек представляет собой различное поведение. Хотя зонды нестабильны в разных версиях ядра, точки трассировки — нет. Поэтому рекомендуется использовать точки трассировки, которые представляют собой набор стабильных ловушек, когда это возможно, вместо kprobes/upprobes.

Все доступные в системе точки трассировки можно просмотреть в следующем каталоге: /sys/kernel/debug/tracing/events.

Доступ к указанному выше каталогу возможен только как пользователь root. Каждый из его подкаталогов содержит точки трассировки, которые соответствуют событиям многих различных типов, таких как tcp, sched, системные вызовы, сеть и т. д. Например, подкаталог sched/ содержит все точки трассировки, соответствующие событиям планировщика ядра, таким как переключение процессоров, выполнение новые задачи, переключение потоков, очередь потоков и т. д. И каждая из этих точек трассировки имеет файл с именем format, который определяет формат аргументов, содержащих информацию о событии, вызвавшем срабатывание точки трассировки.

Эти аргументы передаются функции KSP, к которой привязана точка трассировки. Информация, содержащаяся в этих аргументах, хранится и отправляется обратно в пользовательское пространство через буферы. Это механизм, который позволяет извлекать данные из ядра с помощью обработчиков, прикрепленных к функциям проверки eBPF.

Написание программы с помощью eBPF

Как уже говорилось ранее, в программе eBPF есть два сегмента. Программа USP заботится об объявлении программы KSP, подключении точки трассировки/зонда, а также о получении и обработке данных, отправляемых через буфер. Компонент KSP программы гарантирует, что данные, передаваемые из аргумента точки трассировки в функцию, сохраняются и передаются обратно в пространство пользователя. Программа KSP написана на Си-подобном языке. Хотя он очень похож на c, в его механизмах есть определенные отличия, предотвращающие сбой ядра программы. Неудивительно, что для кодирования KSP использовался C-подобный язык, поскольку ядро ​​Linux также написано на C.

Однако когда дело доходит до USP, он закодирован на python для программ, использующих API python под названием bcc, а при использовании bpftrace это синтаксис bpf, который прикрепляет трассировочная точка/проба к KSP.

Пример трассировки системного вызова execve можно написать с помощью bpftrace в виде одной строки следующим образом:

sudo bpftrace -e 'tracepoint:syscall:sys_enter_execve { printf("Execve Triggered\n"); }’

Таким образом, шаблон bpftrace можно определить как:

Использование скрытой копии API Python для написания программы в пользовательском пространстве

При написании УТП с API Python необходимо выполнить 6 основных шагов.

  1. Импорт модуля BPF из bcc
  2. Объявление KSP как строки в тройных кавычках
  3. Объявление объекта BPF с помощью строки KSP
  4. Присоединение kprobe к триггерной функции KSP
  5. Определение функции обработчика для данных, полученных из пространства ядра
  6. Получение данных через буфер в цикле

Сначала создайте скрипт Python как file_name.py. Чтобы использовать этот API, сначала его необходимо установить и импортировать в код. Класс BPF API bcc — это то, что мы используем ниже:

Затем необходимо создать объект BPF. Это обрабатывает все вышеупомянутые задачи USP. Чтобы объявить объект BPF, KSP следует записать в виде строковой переменной и передать в качестве аргумента. Таким образом, перед определением объекта BPF напишите KSP (подробнее об этом будет сказано позже).

Затем объявляется объект BPF, и точка трассировки присоединяется к указанной выше функции KSP следующим образом:

Это прикрепляет функцию trigger_func, определенную в KSP, с указанной точкой трассировки/kprobe. Он выполняет функцию fn_name() всякий раз, когда встречается подключенная точка трассировки/kprobe.

Далее, USP должен иметь функцию обработчика для обработки полученных данных от KSP. Это должно облегчить преобразование данных, отправленных из KSP, в удобочитаемую структуру данных Python (recv_data).

Функция обработчика должна быть назначена буферу, как показано ниже. Эта функция будет выполняться каждый раз при поступлении новых данных от KSP. Эти входящие данные через буфер перехватываются циклом while ниже. Для этого используется вспомогательная функция perf_buffer_poll().

Структура KSP при использовании bcc API

KSP, написанный на языке скрытой копии, должен выполнять следующие основные задачи:

1. Включение необходимых заголовочных файлов

2. Определение таблицы BPF для передачи собранных данных о событиях в пространство пользователя.

  • BPF_PERF_OUTPUT(BPF_table_name);

3. Объявление структуры для хранения данных для передачи

  • структура data_t

4. Объявление структуры для хранения данных о точках трассировки для разбора

  • структура tp_args_t

5. Определение функции, которая будет выполняться после достижения точки трассировки

  • Разобрать данные из точки трассировки
  • Сохраните их в структуре data_t
  • Отправка собранных данных в буфер
  • BPF_table_name.perf_submit(args, &data, sizeof(data));

При анализе данных точки трассировки необходимо учитывать формат точки трассировки. В файле формата, доступном для каждой точки трассировки.

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

Передаваемый аргумент — это одна переменная, в основном 64-битная, с разными полями, хранящимися в разных позициях. В этом отдельном блоке данных РАЗМЕР – это количество битов, занимаемых соответствующим полем, а СМЕЩЕНИЕ – это количество битов, отсчитываемых от одного конца до начала соответствующего поля данных. .

В API bcc определены вспомогательные функции как для KSP, так и для USP. Синтаксис и примеры этих вспомогательных функций, а также многие другие полезные атрибуты можно найти в следующей документации репозитория BPF Compiler Collection (BCC) — bcc/reference_guide.

Копать глубже

Документация ядра Linux для eBPF — отличное место, чтобы узнать об использовании eBPF в Linux. Он содержит необходимую информацию и рекомендации с точки зрения пользователя Linux. Помимо документации по Linux, в репозитории BPF Compiler Collection (BCC) также есть отличная документация и учебник. Кроме того, он содержит набор инструментов, разработанных с помощью eBPF для трассировки. Примеры и исходный код для всех этих инструментов также доступны в том же репозитории.

Чтобы узнать больше о bpftrace, посетите GitHub — iovisor/bpftrace. Существует список однострочников, которые вы можете скопировать-вставить и запустить из терминала. Взгляните на набор инструментов, доступных там.

Все эти инструменты имеют открытый исходный код, и вполне возможно установить все эти инструменты или клонировать репозитории на свой локальный компьютер. Или, поскольку eBPF легко доступен в вашей системе Linux, скопируйте исходный код, внесите изменения и начните экспериментировать. Вы также можете разрабатывать собственные инструменты. И это будет лучший способ учиться, как всегда.

Еще одним одним из лучших справочников по eBPF и мониторингу производительности является книга Брендана Грегга под названием Инструменты производительности BPF. Кроме того, другие его книги, блоги и лекции, посвященные той же теме, могут оказать большую помощь в исследовании.

Хотя ажиотаж вокруг eBPF начался совсем недавно, есть признаки того, что в будущем он будет использоваться для решения важных и важных задач. Он открывает дверь для любого, кто может погрузиться в ядро ​​​​Linux и делать всевозможные замечательные вещи, используя «сверхспособности ядра» под рукой.

Удачного кодирования!

Свяжитесь с нашими экспертами в Zone24x7 для получения дополнительной информации.