Использование памяти в scull

Предыдущая  Содержание  Следующая V*D*V

Перед знакомством с операциями read и write мы рассмотрим получше, как и почему scull выполняет выделение памяти. "Как" необходимо, чтобы глубоко понимать код, а "почему" демонстрирует варианты выбора, который должен делать автор драйвера, хотя scull, безусловно, не типичен, как устройство.

 

Этот раздел имеет дело только с политикой распределения памяти в scull и не показывает навыки аппаратного управления, необходимые для написания реальных драйверов. Эти навыки будут введены в Главах 9 и 10. Таким образом, вы можете пропустить этот раздел, если вы не заинтересованы в понимании внутренней работы ориентированного на память драйвера scull.

 

Область памяти, используемая scull, также называемая устройством, имеет переменную длину. Чем больше вы пишете, тем больше она растёт; укорачивание производится перезаписью устройства более коротким файлом.

 

Драйвер scull знакомит с двумя основными функциями, используемыми для управления памятью в ядре Linux. Вот эти функции, определённые в <linux/slab.h>:

 

void *kmalloc(size_t size, int flags);

void kfree(void *ptr);

 

Вызов kmalloc пытается выделить size байт памяти; возвращаемая величина - указатель на эту память или NULL, если выделение не удаётся. Аргумент flags используется, чтобы описать, как должна быть выделена память; мы изучим эти флаги подробно в Главе 8. Сейчас мы всегда используем GFP_KERNEL. Выделенная память должна быть освобождена kfree. Вы никогда не должны передавать kfree что-то, что не было получено от kmalloc. Однако, правомерно передать kfree указатель NULL.

 

kmalloc это не самый эффективный способ распределения больших областей памяти (смотрите Главу 8), поэтому сделанный для scull выбор не особенно умный. Исходный код для изящной реализации будет более трудным для восприятия, а целью этого раздела является показать read и write, а не управление памятью. Вот почему код просто использует kmalloc и kfree, без пересортировки выделенных целых страниц, хотя такой подход был бы более эффективным.

 

С другой стороны, мы не хотим ограничивать размер области "устройства" по философским и практическим соображениям. Философски, это всегда плохая идея - поставить произвольные ограничения на управляемые объекты данных. Практически, scull может быть использован для временного поедания всей памяти вашей системы в целях выполнения тестов в условиях малого количества памяти. Выполнение таких тестов могло бы помочь вам понять внутренности системы. Вы можете использовать команду cp /dev/zero /dev/scull0, чтобы съесть всю реальную оперативную память с помощью scull, и вы можете использовать утилиту dd, чтобы выбрать, какой объём данных скопируется на устройство scull.

 

В scull каждое устройство представляет собой связный список указателей, каждый из которых указывает на структуру scull_qset. По умолчанию каждая такая структура может ссылаться на более чем четыре миллиона байт через массив промежуточных указателей. Реализованный исходник использует массив из 1000 указателей на области по 4000 байта. Мы называем каждую область памяти квантом (quantum), а массив (или его длину) - набором квантов (quantum set). Устройство scull и его области памяти показаны на Рисунке 3-1.

 

Рисунок 3-1. Схема устройства scull

Рисунок 3-1. Схема устройства scull

 

Выбранные числа таковы, что запись одного байта в scull потребляет 8000 или 12000 байт памяти: 4000 для кванта и 4000 или 8000 для набора квантов (в зависимости от того, является ли на целевой платформе указатель 32-х разрядным или 64-х разрядным). Если, наоборот, вы пишете большое количество данных, накладные расходы на связный список не такие большие. Существует только один список элементов для каждых четырёх мегабайт данных и максимальный размер устройства ограничен объёмом памяти компьютера.

 

Выбор соответствующего значения для кванта и квантового набора - это вопрос политики, а не механизма, и их оптимальные размеры зависят от того, как используется устройство. Таким образом, драйвер scull не должен заставлять использовать какие-то определённые значения для размеров кванта и набора квантов. В scull пользователь может изменять эти значения несколькими способами: путём изменения макросов SCULL_QUANTUM и SCULL_QSET в scull.h во время компиляции, устанавливая целочисленные значения scull_quantum и scull_qset, во время загрузки модуля, или изменяя текущее значение и значение по умолчанию с помощью ioctl во время выполнения. Использование макроса и целой величины позволяют выполнять конфигурацию и во время компиляции и во время загрузки и напоминают выбор старшего номера. Мы используем эту технику для любого, произвольного или связанного с политикой, значения в драйвере.

 

Остался только вопрос, как были выбраны значения по умолчанию. В данном случае проблема состоит в нахождении лучшего соотношения между потерей памяти в результате полузаполненного кванта и квантового набора, и накладных расходов на выделения, освобождения, и указатель связывания, что происходит, если кванты и наборы малы. Кроме того, должен быть принят во внимание внутренний дизайн kmalloc. (Однако, мы не будем исследовать это сейчас; внутренности kmalloc рассматриваются в Главе 8.) Выбор чисел по умолчанию делается из предположения, что во время тестирования в scull могут быть записаны большие объёмы данных, хотя при обычном использовании устройства, скорее всего, будут передаваться только несколько килобайт данных.

 

Мы уже видели структуру scull_dev, которая является внутренним представлением нашего устройства. Это поля структуры quantum и qset, содержащие квант и размер квантового набора устройства, соответственно. Фактические данные, однако, отслеживаются другой структурой, которую мы назвали структурой scull_qset:

 

struct scull_qset {

    void **data;

    struct scull_qset *next;

};

 

Следующий фрагмент кода показывает на практике, как структуры scull_dev и scull_qset используются для хранения данных. Функция scull_trim отвечает за освобождение всей области данных и вызывается из scull_open, когда файл открывается для записи. Это просто прогулка по списку и освобождение любого кванта и набора квантов при их нахождении.

 

int scull_trim(struct scull_dev *dev)

{

    struct scull_qset *next, *dptr;

    int qset = dev->qset; /* "dev" является не-null */

    int i;

    for (dptr = dev->data; dptr; dptr = next) { /* все элементы списка */

        if (dptr->data) {

            for (i = 0; i < qset; i++)

                kfree(dptr->data[i]);

            kfree(dptr->data);

            dptr->data = NULL;

        }

        next = dptr->next;

        kfree(dptr);

    }

    dev->size = 0;

    dev->quantum = scull_quantum;

    dev->qset = scull_qset;

    dev->data = NULL;

    return 0;

}

 

scull_trim также используется в функции очистки модуля, чтобы вернуть системе память, используемую scull.

Предыдущая  Содержание  Следующая