Кэширование в Linux: принципы работы и ограничения
Cache, если kswapd не смог выполнить сбор мусора или выполнял его медленно, то происходит переход к direct_reclaim для более глубокого сбора мусора до тех пор, пока оставшаяся память системы не будет соответствовать требованиям. Если это не помогает, то может произойти OOM. Достаточно ли такого механизма?
В некоторых сценариях с атомарными операциями, например, в контексте прерывания, невозможно заснуть, поэтому при выделении памяти в таких сценариях не происходит перехода к direct_reclaim и даже не пробуждается процесс kswapd. Например, в функции обработки мягкого прерывания сетевого адаптера может возникнуть ситуация, когда page cache занимает слишком много места, и системе не хватает оставшейся памяти для выделения данных пакета. В этом случае пакет просто отбрасывается. Это как раз та проблема, которую решает ограничение page cache.
Введение в page cache
Page cache — это дисковый кэш файлов, реализованный в ядре Linux. Его основная цель — уменьшить количество операций ввода-вывода на диск, делая доступ к данным более быстрым за счёт их кэширования в физической памяти. Page cache используется для повышения производительности доступа к файлам. Когда именно создаётся page cache и где он хранится?
На схеме ниже показано примерное расположение page cache в системе:
Когда приложение читает данные с диска, оно сначала вызывает функцию find_get_page(), чтобы проверить, есть ли соответствующий page cache в системе и является ли он актуальным. Если такой кэш существует и он актуален, данные будут возвращены без необходимости обращения к диску.
Если в системе нет соответствующего page cache, вызывается соответствующая файловая система, которая инициирует операцию чтения с диска. После того как данные прочитаны и возвращены, вызывается функция add_to_page_cache_lru() для добавления только что прочитанных страниц (pages) в список lru. Список lru управляет page cache, разделяя его на активные и неактивные списки в зависимости от активности доступа. Более подробную информацию о механизме управления lru можно найти в статьях в интернете.
Анализ реализации ограничения page cache
Ограничение page cache вступает в действие, когда приложение добавляет page cache (вызывая функцию add_to_page_cache_lru()). Оно проверяет, не превышает ли размер page cache установленный предел (/proc/sys/vm/pagecache_limit_ratio).
Если обнаружено превышение лимита, вызывается функция shrink_page_cache() для освобождения page cache. Эта функция является основной для освобождения page cache.
Функция shrink_page_cache() освобождает page cache до достижения порогового значения. Процесс освобождения происходит следующим образом:
Важно отметить, что этот процесс не обязательно должен завершиться полностью. Как только объём page cache становится меньше порогового значения, процесс освобождения завершается. Анонимные и отображённые страницы не освобождаются, поскольку они требуют swap, что занимает больше времени, а отображённые страницы представляют собой только отображение в таблице страниц процесса.
Описание и использование функций ограничения page cache
Мы предоставляем три интерфейса /proc:
Заключение
После тестирования было подтверждено, что можно ограничить использование page cache согласно настройкам пользователя. При скорости чтения и записи данных 1 Гбит/с дополнительная нагрузка на процессор, вызванная ограничением page cache, составляет менее 3%. Однако уменьшение объёма page cache может увеличить вероятность промахов кэша, хотя обычно мы не устанавливаем слишком низкие ограничения на page cache, и эти параметры могут быть адаптированы под конкретные требования бизнес-сценариев. Поддержка kpatch для arm64
Включает удаление регистрации ftrace_ops, а также функции, связанные с переадресацией данных при загрузке модуля.
Переадресация данных: краткое описание кода
Рисунок 1.
Инструменты пользовательского режима: модификация
Из предыдущего описания работы в пользовательском режиме мы можем видеть, что при создании diff.o мы не можем использовать возможности компилятора и должны анализировать вручную.
ELF-файлы
Процесс связывания В объектном файле есть важный section — relocation section. Связывание в основном решает следующие проблемы: * Объединение различных section, таких как текстовые сегменты всех obj-файлов. * Разрешение символов. После объединения можно определить адрес каждого символа, на который ссылается объектный файл, не находящийся в текущем файле. * Определение адреса каждого символа и выполнение переадресации согласно каждой записи в relocation section.
Функция переадресации в архитектуре arm64 имеет несколько типов:
1. R_AARCH64_ABS64: абсолютный адрес символа во время выполнения.
*addr = symbol.addr
2. R_AARCH64_PREL32: вычисление адреса символа относительно текущего адреса.
*addr = symbol.addr - addr
3. R_AARCH64_CALL26: инструкция br для переадресации.
*addr = symbol.addr - addr
4. R_AARCH64_ADR_PREL_PG_HI21: переадресация страницы.
*addr = Page(symbol.addr) - Page(addr)
5. R_AARCH64_ADD_ABS_LO12_NC: добавление абсолютного адреса младших 12 бит.
*addr = symbol.addr[11:0]
Kernel module Понимая принцип работы процесса связывания, можно легко понять, как работает kernel module. Фактически, ядро переносит работу компоновщика в ядро, позволяя загружать объектные файлы. Файл ko по сути является объектным файлом, но с добавлением других section для описания вашего kernel module. Когда вы используете команду insmod, ядро анализирует relocation section файла ko, разрешает символы и успешно запускает ваш код.
Следует отметить, что хотя kernel module переносит процесс связывания в ядро, из-за ограничений открытых исходных кодов он помогает разрешать только символы, экспортированные через EXPORT_SYMBOL_XXX.
— Поиск изменений в функциях Можно сравнить соответствующие section, чтобы найти изменения. Однако сложно определить, какая именно функция изменилась. Компилятор предоставляет специальные опции для создания отдельного section для каждой функции и глобальной переменной, что упрощает поиск изменённых функций.
— Создание diff.o для архитектуры arm64 После сравнения мы можем найти изменённые section и соответствующие функции. Для создания diff.o необходимо учесть несколько моментов: * Поскольку diff.o не создаётся кодом C, нам нужно использовать инструменты для ручного создания нового obj-файла. Это можно сделать с помощью библиотеки libelf. * Изменённые функции могут вводить новые глобальные символы, которые могут привести к созданию новых section. Эти section также должны быть включены в diff.o. * Изменённые функции могут вызывать уже существующие системные символы. Мы можем использовать relocation section, чтобы ядро помогло нам разрешить эти символы. Однако нам нужно убедиться, что оно разрешает все системные символы.
Анализ основан на использовании спецификаций ELF для архитектуры arm64, поэтому реализация должна соответствовать этим спецификациям.
Сначала необходимо загрузить модуль kpatch, затем новый функциональный модуль, созданный инструментами пользователя. Используйте команду lsmod для проверки успешной загрузки модулей. Kpatch также предоставляет интерфейс sysfs для просмотра информации о загруженных новых функциональных модулях, включая адреса старых и новых функций. Вы можете использовать команду /sys/kernel/kpatch/xxx/enabled
для удаления модуля и восстановления исходной функции.
Процесс проверки включает следующие шаги:
Эта функция предотвращает доступ даже root-пользователя к процессу после установки защиты. Она также предотвращает получение памяти процесса, загрузку динамических библиотек и другие действия. Gdb использует системный вызов ptrace для реализации этих функций, и мы модифицируем этот вызов, добавляя условия для предотвращения доступа к процессу. Модификации включают:
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -1118,6 +1118,9 @@ static struct task_struct *ptrace_get_task_struct(pid_t pid)
#define arch_ptrace_attach(child) do { } while (0)
#endif
+int (*ptrace_pre_hook)(long request, long pid, struct task_struct *task, long addr, long data);
+EXPORT_SYMBOL(ptrace_pre_hook);
+
SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,
unsigned long, data)
{
@@ -1136,6 +1139,12 @@ SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,
ret = PTR_ERR(child);
goto out;
}
+
+ if (ptrace_pre_hook) {
+ ret = ptrace_pre_hook(request, pid, child, addr, data);
+ if (ret)
+ goto out_put_task_struct;
+ }
Здесь ptrace_pre_hook — указатель на функцию обратного вызова, которая устанавливается в модуле ttools. Модуль ttools создаёт символьное устройство /dev/ttools и предоставляет два ioctl для включения и отключения защиты процесса от gdb.
#define TTOOLS_PTRACE_PROTECT _IO(TTOOLS_IO, 0x00)
#define TTOOLS_PTRACE_UNPROTECT _IO(TTOOLS_IO, 0x01)
Пользователь должен сначала выполнить команду modprobe ttools для загрузки модуля ttools, а затем использовать код для включения или отключения защиты процесса. Реализация модуля ttools находится в каталоге kernel/tkernel/ttools. Троттлинг с использованием cgroup для ограничения IOPS и BPS. Одновременно на уровне диспетчеризации ввода-вывода ядро изолирует процессы, используя различные планировщики, в соответствии с пропорциями их весов.
Поскольку устройства MQ обычно не проходят через уровень диспетчеризации, их невозможно изолировать в соответствии с весами. Этот патч реализует динамическую регулировку предела BPS на основе троттлинга ввода-вывода, что позволяет достичь изоляции процессов на общем уровне в соответствии с их весами.
Ранее в рамках троттлинга мы могли использовать cgroup, чтобы ограничить значение BPS для процессов, находящихся в одной группе, для определённого устройства. Если мы сможем своевременно получать текущую пропускную способность блочного устройства, то сможем динамически регулировать это значение в соответствии с весом, реализуя эту функцию.
Чтобы избежать потери пропускной способности, мы временно лишаем группы, которые не производят ввод-вывод, части их веса, предоставляя его другим группам, тем самым максимизируя коэффициент использования.
Для каждой группы мы вводим два веса, представляющих сумму их весов:
Для некоторой группы c записываем:
c.sum = c.leaf_weight + Σc.child[i].weight, где c.child[i].weight — значение веса всех прямых дочерних групп группы c.
Доля пропускной способности корневого узла составляет 100%.
Доля пропускной способности некорневой группы c составляет:
c.weight / c.parent.sum.
В каталоге cgroup blkio есть два файла:
blkio.throttle.leaf_weight_device
blkio.throttle.weight_device
.
Конфигурирование этих файлов выполняется следующим образом:
echo major:min $weight > $file,
где:
major и min соответствуют блочному устройству (не может быть раздел);
вес находится в диапазоне от 10 до 1000;
file — это файлы конфигурации двух интерфейсов группы c.
echo "259:2 800 > /sys/fs/cgroup/blkio/blkio.throttling.leaf_weight_device"
echo "259:2 200 > /sys/fs/cgroup/blkio/offline_tasks/blkio.throttling.weight_device".
Это означает, что мы хотим, чтобы вес корневого узла для устройства 259:2 был в четыре раза больше, чем у offline_tasks. Когда корневой узел в течение некоторого времени не выполняет операции ввода-вывода для этого устройства, весь его вес будет распределён между узлами offline_tasks.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )