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

В этой статье разберем три аллокатора памяти ядра: SLAB, SLOB, а также SLUB.

Зачем вообще нужны slab-менеджеры?

Ядро Linux работает с множеством объектов, которые занимают память, но их размер может сильно варьироваться. Выделение памяти через обычный механизм работы с кучей (как malloc в юзер пространстве) было бы слишком затратным и приводило бы к значительной фрагментации. Slab‑менеджеры были разработаны для лучшего управления памятью, минимизации фрагментации и ускорения работы с мелкими объектами, которые часто создаются и уничтожаются ядром.

Основная идея: slab как контейнер

SLAB основан на концепции кэшей, в которых хранятся объекты определенного размера. Эти объекты размещаются в блоках памяти, которые называются slabами. Каждый slab содержит несколько объектов одного типа и может находиться в одном из трёх состояний:

  1. Пустой (empty) — все объекты свободны.

  2. Частично заполненный (partial) — некоторые объекты заняты.

  3. Полностью заполненный (full) — все объекты заняты.

Когда ядру требуется объект, оно сначала пытается найти свободный объект в частично заполненных slab'ах. Если таких объектов нет, выделяется новый slab.

Структура slab'а

Каждый slab состоит из нескольких страниц памяти (по дефолту 4 КБ), которые делятся на маленькие кусочки (объекты). Когда объект освобождается, он не удаляется физически, а возвращается в slab, чтобы его можно было использовать повторно. Это снижает накладные расходы на выделение и освобождение памяти.

struct kmem_cache {
    unsigned int object_size;    // Размер объекта
    unsigned int num;            // Количество объектов в slab'е
    struct list_head slabs_full, slabs_partial, slabs_empty;  // Списки slab'ов
};

SLAB использует три списка:

  • slabs_full — все объекты в этих slab'ах заняты.

  • slabs_partial — slab'ы, в которых есть свободные объекты.

  • slabs_empty — пустые slab'ы, которые можно использовать для новых объектов.

Допустим, ядру нужен объект размером 32 байта. Если в частично заполненных slab'ах есть свободное место, оно выделяется. Если же места нет, создаётся новый slab. Вот как это выглядит:

struct kmem_cache *my_cache;
my_cache = kmem_cache_create("my_cache", sizeof(struct my_struct), 0, SLAB_HWCACHE_ALIGN, NULL);
void *obj = kmem_cache_alloc(my_cache, GFP_KERNEL);
// используем объект
kmem_cache_free(my_cache, obj); // возвращаем его в кэш

SLAB хорошо подходит для серверных систем с большим количеством параллельных запросов.

Как работает SLOB?

SLOB — это супер минималистичный аллокатор, который, в отличие от SLAB, ориентирован на минимальные накладные расходы и максимальную экономию памяти. Он напоминает работу стандартной кучи, как malloc в пользовательском пространстве. Внутри SLOB работает с небольшими блоками памяти, что делает его очень простым и эффективным для устройств с ограниченными ресурсами.

Как это работает?

  1. Свободные блоки (Free List). В SLOB нет кэшей, как в SLAB, и объекты выделяются динамически из блоков памяти. Аллокатор хранит список свободных блоков, из которых выделяются объекты. Когда объект освобождается, он возвращается в свободный список, что делает алгоритм простым, но приводит к фрагментации.

  2. Аллокация памяти. SLOB работает как простая куча, выделяя ровно столько памяти, сколько нужно. Если фрагментация слишком высока и не удаётся найти подходящий блок, выделяется новый.

  3. Экономия памяти. Поскольку SLOB выделяет ровно столько памяти, сколько необходимо, без использования больших slab'ов, он оптимален для систем, где важно экономить каждый байт.

Пример простого выделения памяти с использованием SLOB:

void *data = slob_alloc(128);
if (data) {
    // Работаем с выделенной памятью
    slob_free(data);  // Освобождаем память
}

SLOB выделяет блок, минимально необходимый для объекта

В чем разница между SLAB и SLOB?

Характеристика

SLAB

SLOB

Подходит для

Высоконагруженные системы

Встраиваемые системы

Управление памятью

Использует кэши для объектов одинакового размера

Прямое управление блоками памяти

Фрагментация

Минимальная за счёт использования slab'ов

Высокая из-за простого управления блоками

Производительность

Высокая за счёт кэширования объектов и минимальной фрагментации

Низкая при большом количестве мелких объектов

Накладные расходы

Высокие, из-за сложного механизма кэширования и поддержки SMP

Минимальные, за счёт простоты реализации

SLUB

SLUB использует упрощённую структуру по сравнению со SLAB. Он не поддерживает такие понятия, как кэш объектов, и не хранит метаданные о каждом объекте в отдельной структуре. Вместо этого SLUB хранит свободные блоки памяти прямо внутри slab'ов.

Каждый процессор имеет свой собственный активный slab, что оптимизирует работу многопроцессорных систем и минимизирует задержки при выделении памяти.

Пример структуры в SLUB:

struct kmem_cache_cpu {
    void **freelist;     // Список свободных блоков памяти
    struct page *page;   // Текущая страница slab'а
};

В этом примере freelist — это указатель на свободные блоки памяти, которые могут быть быстро выделены для новых объектов. Вместо создания сложных кэшей, как в SLAB, SLUB просто хранит указатели на свободные объекты прямо в slab'ах.


Что выбрать

Динамическое управление памятью в Linux реализовано через несколько аллокаторов, каждый из которых решает свои задачи. SLAB дает высокую производительность за счёт кэширования объектов и минимизации фрагментации, что идеально подходит для серверных систем с высокой нагрузкой. SLOB фокусируется на экономии памяти и минимальных накладных расходах, что делает его лучшим выбором для встраиваемых устройств с ограниченными ресурсами. SLUB является упрощённой и улучшенной версией SLAB, дает более быструю работу, особенно в многопроцессорных системах.

Научиться профессиональному подбору конфигураций, управлению процессами, обеспечению безопасности, развертыванию, настройке и обслуживанию сетей можно на онлайн‑курсе «Administrator Linux. Professional» под руководством экспертов-практиков.

Комментарии (6)


  1. INSTE
    30.10.2024 19:06

    На самом деле kmem_cache_alloc и kmalloc - это универсальные фронтенды, а реально SLAB, SLOB и SLUB сидят в глубине и через API не видны.


  1. Apoheliy
    30.10.2024 19:06

    Не могли бы вы больше раскрыть тему про механизмы SLAB?

    Возникают обоснованные сомнения, что на куске в 4 кБайт выделение 32 байт (это ваш пример из статьи) из условной середины блока (например, начало оказалось занятым) и потом ещё отслеживание "а где же ещё свободное место? а есть ли непрерывный кусок в 93 байта?", может как-то упростить управление памятью. Скорее наоборот. Даже хранение структур с описаниями кусков занятой памяти уже будет затратным. Плюс ещё эти структуры блокировать при выделении/освобождении памяти (например, освобождая память вы же не говорите размер, сколько освобождать - менеджеру памяти это нужно где-то узнать).

    В общем, странно это и хотелось бы более развёрнутого описания. Если нет желания писать ещё одну статью, может поделитесь ссылками?


  1. gudvinr
    30.10.2024 19:06

    Вы вычитку текста вообще не проводите перед публикацией?

    в юзер пространстве

    для более лучшего

    slab»ами

    И это только первые 2 абзаца


  1. IkaR49
    30.10.2024 19:06

    Вот только в новых ядрах удаляют поддержку SLAB и SLOB: https://lwn.net/Articles/932201/


    1. dartraiden
      30.10.2024 19:06

      Уже удалили: SLOB в 6.4, SLAB в 6.8


  1. CitizenOfDreams
    30.10.2024 19:06

    Прямо три поросенка - Слэб-Слэб, Слоб-Слоб и Слаб-Слаб.

    Вот только в новых ядрах удаляют поддержку SLAB и SLOB

    И у двух домики уже сдуло.