Роберт Лав - Разработка ядра Linux
- Название:Разработка ядра Linux
- Автор:
- Жанр:
- Издательство:Издательский дом Вильямс
- Год:2006
- Город:Москва
- ISBN:5-8459-1085-4
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Роберт Лав - Разработка ядра Linux краткое содержание
В книге детально рассмотрены основные подсистемы и функции ядер Linux серии 2.6, включая особенности построения, реализации и соответствующие программны интерфейсы. Рассмотренные вопросы включают: планирование выполнения процессов, управление временем и таймеры ядра, интерфейс системных вызовов, особенности адресации и управления памятью, страничный кэш, подсистему VFS, механизмы синхронизации, проблемы переносимости и особенности отладки. Автор книги является разработчиком основных подсистем ядра Linux. Ядро рассматривается как с теоретической, так и с прикладной точек зрения, что может привлечь читателей различными интересами и потребностями.
Книга может быть рекомендована как начинающим, так и опытным разработчикам программного обеспечения, а также в качестве дополнительных учебных материалов.
Разработка ядра Linux - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
• SLAB_PANIC
— этот флаг указывает на необходимость перевода ядра в состояние паники, если выделение памяти было неудачным. Данный флаг полезен, если выделение памяти всегда должно завершаться успешно, как, например, в случае создания кэша структур VMA (областей виртуальной памяти, см. главу 14, "Адресное пространство процесса") при загрузке системы.
• SLAB_CACHE_DMA
— этот флаг указывает уровню слябового распределения, что все слябы должны выделяться в памяти, с которой возможны операции прямого доступа к памяти. Это необходимо, когда объекты используются в операциях ПДП и должны находиться в зоне ZONE_DMA
. В противном случае эта возможность не нужна и этот флаг не нужно устанавливать.
Два последних параметра ctor
и dtor
— это конструктор и деструктор кэша соответственно. Конструктор вызывается, когда в кэш добавляются новые страницы памяти. Деструктор вызывается, когда из кэша удаляются страницы памяти. Если указан деструктор, то должен быть указан и конструктор. На практике кэши ядра ОС Linux обычно не используют функции конструктора и деструктора. В качестве этих параметров можно указывать значение NULL
.
В случае успешного выполнения функция kmem_cache_create()
возвращает указатель на созданный кэш. В противном случае возвращается NULL
. Данная функция не может вызываться в контексте прерывания, так как она может переводить процесс в состояние ожидания. Для ликвидации кэша необходимо вызвать следующую функцию.
int kmem_cache_destroy(kmem_cache_t *cachep);
Эта функция ликвидирует указанный кэш. Она обычно вызывается при выгрузке модуля, который создает свой кэш. Из контекста прерывания эту функцию вызывать нельзя, так как она может переводить вызывающий процесс в состояние ожидания. Перед вызовом этой функции необходимо, чтобы были выполнены следующие два условия.
• Все слябы кэша являются пустыми. Действительно, если в каком-либо слябе существует объект, который все еще используется, то как можно ликвидировать кэш?
• Никто не будет обращаться к кэшу во время и особенно после вызова функции kmem_cache_destroy()
. Эту синхронизацию должен обеспечить вызывающий код.
В случае успешного выполнения функция возвращает нуль, в других случаях возвращается ненулевое значение.
После того как кэш создан, из него можно получить объект путем вызова следующей функции.
void* kmem_cache_alloc(kmem_cache_t *cachep, int flags);
Эта функция возвращает указатель на объект из кэша, на который указывает параметр cachep
. Если ни в одном из слябов нет свободных объектов, то уровень слябового распределения должен получить новые страницы памяти с помощью функции kmem_getpages()
, значение параметра flags
передается в функцию __get_free_pages()
. Это те самые флаги, которые были рассмотрены ранее. Скорее всего, необходимо указывать GFP_KERNEL
или GFP_ATOMIC
.
Далее для удаления объекта и возвращения его в сляб, из которого он был выделен, необходимо использовать следующую функцию.
void kmem_cache_free(kmem_cache_t *cachep, void *objp);
Данная функция помечает объект, на который указывает параметр objp
, как свободный.
Пример использования слябового распределителя памяти
Давайте рассмотрим пример из реальной жизни, связанный с работой со структурами task_struct
(дескрипторы процессов). Показанный ниже код в несколько более сложной форме приведен в файле kernel/fork.c.
В ядре определена глобальная переменная, в которой хранится указатель на кэш объектов task_struct
:
kmem_cache_t *task_struct_cachep;
Во время инициализации ядра, в функции fork_init()
, этот кэш создается следующим образом.
task_struct_cachep = kmem_cache_create("task_struct",
sizeof(struct task_struct), ARCH_MIN_TASKALIGN,
SLAB_PANIC, NULL, NULL);
Данный вызов создает кэш с именем "task_struct"
, который предназначен для хранения объектов тина struct task_struct
. Объекты создаются с начальным смещением в слябе, равным ARCH_MIN_TASKALIGN
байт, и положение всех объектов выравнивается по границам строк системного кэша, значение этого выравнивания зависит от аппаратной платформы. Обычно значение выравнивания задается для каждой аппаратной платформы с помощью определения препроцессора L1_CACHE_BYTES
, которое равно размеру процессорного кэша первого уровня в байтах. Конструктор и деструктор отсутствуют. Следует обратить внимание, что возвращаемое значение не проверяется на равенство NULL
, поскольку указан флаг SLAB_PANIC
. В случае, когда при выделении памяти произошла ошибка, слябовый распределитель памяти вызовет функцию panic()
. Если этот флаг не указан, то нужно проверять возвращаемое значение на равенство NULL
, что сигнализирует об ошибке. Флаг SLAB_PANIC
здесь используется потому, что этот каш является необходимым для работы системы (без дескрипторов процессов работать как-то не хорошо).
Каждый раз, когда процесс вызывает функцию fork()
, должен создаваться новый дескриптор процесса (вспомните главу 3, "Управление процессами"). Это выполняется следующим образом в функции dup_task_struct()
, которая вызывается из функции do_fork()
.
struct task_struct *tsk;
tsk = kmem_cache_alloc(task struct_cachep, GFP_KERNEL);
if (!tsk)
return NULL;
Когда процесс завершается, если нет порожденных процессов, которые ожидают на завершение родительского процесса, то дескриптор освобождается и возвращается обратно в кэш task_struct_cachep
. Эти действия выполняются в функции free_task_struct()
, как показано ниже (где параметр tsk
указывает на удаляемый дескриптор).
kmem_cache_free(task_struct_cachep, tsk);
Так как дескрипторы процессов принадлежат к основным компонентам ядра и всегда необходимы, то кэш task_struct_cachep
никогда не ликвидируется. Если бы он ликвидировался, то делать это необходимо было бы следующим образом.
int err;
err = kmem_cache_destroy(task_struct_cachep);
if (err)
/* ошибка ликвидации кэша */
Достаточно просто, не так ли? Уровень слябового распределения памяти скрывает все низкоуровневые операции, связанные с выравниванием, "раскрашиванием", выделением и освобождением памяти, "сборкой мусора" в случае нехватки памяти. Коли часто необходимо создавать много объектов одного типа, то следует подумать об использовании слябового кэша. И уж точно не нужно писать свою реализацию списка свободных ресурсов!
Статическое выделение памяти в стеке
В пространстве пользователя многие операции выделения памяти, в частности некоторые рассмотренные ранее примеры, могут быть выполнены с использованием стека, потому что априори известен размер выделяемой области памяти. В пространстве пользователя доступна такая роскошь, как очень большой и динамически увеличивающийся стек задачи, однако в режиме ядра такой роскоши нет — стек ядра маленький и фиксирован по размеру. Когда процессу выделяется небольшой и фиксированный по размеру стек, то затраты памяти уменьшаются и ядру нет необходимости выполнять дополнительные функции по управлению памятью.
Читать дальшеИнтервал:
Закладка: