Разработка ядра Linux
Шрифт:
В случае успешного выполнения функция
Эта функция ликвидирует указанный кэш. Она обычно вызывается при выгрузке модуля, который создает свой кэш. Из контекста прерывания эту функцию вызывать нельзя, так как она может переводить вызывающий процесс в состояние ожидания. Перед вызовом этой функции необходимо, чтобы были выполнены следующие два условия.
• Все слябы кэша являются пустыми. Действительно,
• Никто не будет обращаться к кэшу во время и особенно после вызова функции
В случае успешного выполнения функция возвращает нуль, в других случаях возвращается ненулевое значение.
После того как кэш создан, из него можно получить объект путем вызова следующей функции.
Эта функция возвращает указатель на объект из кэша, на который указывает параметр
Далее для удаления объекта и возвращения его в сляб, из которого он был выделен, необходимо использовать следующую функцию.
Данная функция помечает объект, на который указывает параметр
Пример использования слябового распределителя памяти
Давайте рассмотрим пример из реальной жизни, связанный с работой со структурами
В ядре определена глобальная переменная, в которой хранится указатель на кэш объектов
Во время инициализации ядра, в функции
Данный вызов создает кэш с именем
Каждый раз, когда процесс вызывает функцию
Когда процесс завершается, если нет порожденных процессов, которые ожидают на завершение родительского процесса, то дескриптор освобождается и возвращается обратно в кэш
Так как дескрипторы процессов принадлежат к основным компонентам ядра и всегда необходимы, то кэш
Достаточно просто, не так ли? Уровень слябового распределения памяти скрывает все низкоуровневые операции, связанные с выравниванием, "раскрашиванием", выделением и освобождением памяти, "сборкой мусора" в случае нехватки памяти. Коли часто необходимо создавать много объектов одного типа, то следует подумать об использовании слябового кэша. И уж точно не нужно писать свою реализацию списка свободных ресурсов!
Статическое выделение памяти в стеке
В пространстве пользователя многие операции выделения памяти, в частности некоторые рассмотренные ранее примеры, могут быть выполнены с использованием стека, потому что априори известен размер выделяемой области памяти. В пространстве пользователя доступна такая роскошь, как очень большой и динамически увеличивающийся стек задачи, однако в режиме ядра такой роскоши нет — стек ядра маленький и фиксирован по размеру. Когда процессу выделяется небольшой и фиксированный по размеру стек, то затраты памяти уменьшаются и ядру нет необходимости выполнять дополнительные функции по управлению памятью.
Размер стека зависит как от аппаратной платформы, так и от конфигурационных параметров, которые были указаны на этапе компиляции. Исторически размер стека ядра был равен двум страницам памяти для каждого процесса. Это соответствует 8 Кбайт для 32-разрядных аппаратных платформ и 16 Кбайт для 64-разрядных аппаратных платформ.
В первых версиях ядер серии 2.6 была введена возможность конфигурации, для которой размер стека ядра равен одной странице памяти. Когда устанавливается такая конфигурация, то процесс получает стек, по размеру равный всего одной странице памяти: 4 Кбайт на 32-разрядных аппаратных платформах и 8 Кбайт — на 64-разрядных. Это сделано по двум причинам. Во-первых это уменьшает затраты памяти на одну страницу для каждого процесса. Во-вторых, что наиболее важно, при увеличении времени работы системы (uptime) становится все тяжелее искать две физически смежные страницы памяти. Физическая память становится все более фрагментированной, и нагрузка на систему управления виртуальной памятью при создании новых процессов становится все более существенной.
Существует еще одна сложность (оставайтесь с нами, и Вы узнаете все о стеках ядра). Вся последовательность вложенных вызовов функций в режиме ядра должна поместиться в стеке. Исторически обработчики прерываний используют стек того процесса, выполнение которого они прервали. Это означает, что в худшем случае 8 Кбайт стека должно использоваться совместно всеми вложенными вызовами функций и еще парой обработчиков прерываний. Все это эффективно и просто, но это накладывает еще больше ограничений на использование стека ядра. Когда размер стека сократился до одной страницы памяти, обработчики прерываний туда перестали помещаться.