vmalloc и друзья

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

Следующей функцией распределения памяти, которую мы показываем вам, является vmalloc, которая выделяет непрерывную область памяти в виртуальном адресном пространстве. Хотя страницы не являются последовательными в физической памяти (каждая страница получена отдельным вызовом alloc_page), ядро видит их как непрерывный диапазон адресов. vmalloc возвращает 0 (NULL адрес), если происходит ошибка, в противном случае, она возвращает указатель на линейную область памяти как минимум заданного размера.

 

Мы описываем здесь vmalloc, потому что это один из основных механизмов распределения памяти Linux. Однако, следует отметить, что использование vmalloc в большинство ситуаций не рекомендуется. Память, полученная от vmalloc, работает чуть менее эффективно и на некоторых архитектурах размер адресного пространства, отведённый для vmalloc, относительно мал. Код, который использует vmalloc, может получить холодный приём, если направляется для включения в ядро. Если возможно, следует работать непосредственно с отдельными страницами вместо того, чтобы сгладить проблемы с помощью vmalloc.

 

Тем не менее, давайте посмотрим, как работает vmalloc. Прототипами функции и её родственников (ioremap, которая является не строго функцией выделения, обсуждаемой ниже в этом разделе) являются:

 

#include <linux/vmalloc.h>

 

void *vmalloc(unsigned long size);

void vfree(void * addr);

void *ioremap(unsigned long offset, unsigned long size);

void iounmap(void * addr);

 

Стоит подчеркнуть, что адреса памяти, возвращаемые kmalloc и _get_free_pages, также являются виртуальными адресами. Их фактическим значением всё ещё манипулирует MMU (memory management unit (блок управления памятью), как правило, часть центрального процессора), прежде чем для них будут использованы адреса физической памяти. (* На самом деле некоторые архитектуры определяют диапазоны "виртуальных" адресов как зарезервированные при адресации физической памяти. Когда это происходит, ядро Linux использует эту особенность, и ядро и __get_free_pages используют адреса, лежащие в одном из этих диапазонов памяти. Разница невидима для драйверов устройств и другого кода, который не связан непосредственно с подсистемой управления памяти ядра.) vmalloc не отличается в том, как она использует аппаратные средства, а скорее в том, как ядро выполняет задачу выделения.

 

(Виртуальный) диапазон адресов, используемый kmalloc и __get_free_pages имеет связь один к одному с физической памятью, возможно, сдвинутый на постоянную величину PAGE_OFFSET; эти функции не требуют изменения таблицы страниц для этого диапазона адресов. Диапазон адресов, используемый vmalloc и ioremap, с другой стороны, является полностью синтетическим и каждое выделение памяти строит (виртуальную) область памяти соответствующей настройкой таблиц страниц.

 

Эта разница может быть понята сравнением указателей, возвращаемых функциями выделения. На некоторых платформах (например, x86), адреса, возвращаемые vmalloc, только вне адресов, которые использует kmalloc. На других платформах (например, MIPS, IA-64 и x86_64), они принадлежат совершенно другому адресному диапазону. Доступные для vmalloc адреса находятся в диапазоне от VMALLOC_START до VMALLOC_END. Оба символа определены в <asm/pgtable.h>.

 

Адреса, выделенные vmalloc, не могут быть использованы вне микропроцессора, потому что они имеют смысл только поверх MMU процессора. Когда драйверу требуется реальный физический адрес (например, адрес DMA, используемый периферийным оборудованием для управления системной шиной), вы не сможете легко использовать vmalloc. Правильным временем для вызова vmalloc является то, когда вы выделяете память для большого последовательного буфера, который существует лишь в программе. Важно отметить, что vmalloc имеет большие накладные расходы, чем __get_free_pages, потому что она должна получить память и построить таблицы страниц. Таким образом, не имеет смысла вызывать vmalloc для выделения только одной страницы.

 

Примером функции в ядре, которая использует vmalloc, является системный вызов create_module, использующий vmalloc для получения пространства для создаваемого модуля. Код и данные модуля позднее копируются в выделенное пространство, используя copy_from_user. Таким образом, модуль выглядит загруженным в непрерывную память. Вы можете проверить, просмотрев в /proc/kallsyms, что символы ядра, экспортируемые модулями, лежат в диапазоне памяти отличном от символов, экспортируемых самим ядром.

 

Память, выделенная vmalloc, освобождается vfree, таким же образом, как kfree освобождает память, выделенную kmalloc.

 

Как и vmalloc, ioremap строит новые таблицы страниц; однако, в отличие от vmalloc, она на самом деле не выделяет какую-то память. Возвращаемым значением ioremap является специальный виртуальный адрес, который может быть использован для доступа к указанному физическому диапазону адресов; виртуальный адрес, полученный впоследствии, освобождается вызовом iounmap.

 

ioremap является наиболее полезной для связи (физического) адреса PCI буфера с (виртуальным) пространством ядра. Например, она может быть использована для доступа в кадровому буферу видео PCI устройства; такие буферы, как правило, связаны с верхним диапазоном адресов, для которого ядро строит таблицы страниц во время загрузки. Вопросы PCI объяснены более подробно в Главе 12.

 

Стоит отметить, что ради переносимости вы не должны прямо обращаться к адресам, возвращаемым ioremap, как если бы они были указателями на память. Наоборот, следует всегда использовать readb и другие функции ввода/вывода, представленные в Главе 9. Это требование применяется, поскольку некоторые платформы, такие как Alpha, не в состоянии непосредственно связать области памяти PCI с адресным пространством процессора из-за различий между спецификациями передачи данных PCI и процессоров Alpha.

 

Обе ioremap и vmalloc являются странично ориентированными (они работают, изменяя таблицы страниц), следовательно, перевыделенный или выделенный размер округляется до ближайшей границы страницы. ioremap имитирует невыровненное связывание "округлением вниз" адреса для переназначения и возвращением смещения в первой переназначенной странице. Один небольшим недостатком vmalloc является то, что она не может быть использована в атомарном контексте, потому что внутри она использует kmalloc(GFP_KERNEL) для запроса места для хранения таблиц страниц и поэтому может заснуть. Это не должно быть проблемой - если использование __get_free_page недостаточно хорошо для обработчика прерывания, дизайн программы нуждается в некоторой очистке.

scull использующий виртуальные адреса: scullv

Пример кода, использующего vmalloc, приводится в модуле scullv. Как и scullp, этот модуль является урезанной версией scull, которая использует другую функции выделения памяти для получения пространство для хранения данных устройства.

 

Модуль выделяет память по 16 страниц за раз. Для достижения лучшей производительности распределение производится большими блоками, чем в scullp, и чтобы показать то, что делается слишком долго с другими методами распределения чтобы быть нецелесообразным. Выделение более одной страницы с помощью __get_free_pages склонно к ошибке и даже если это удастся, может быть медленным. Как мы видели ранее, vmalloc быстрее, чем другие функции при выделении нескольких страниц, но несколько медленнее при получении одной страницы из-за накладных расходов при построении таблицы страниц. scullv разработан подобно scullp. order определяет "порядок" каждого выделения памяти и по умолчанию равен 4. Единственным различием между scullv и scullp является управление выделением памяти. Для получения новой памяти эти строки используют vmalloc:

 

/* Выделяем квант используя виртуальные адреса */

if (!dptr->data[s_pos]) {

    dptr->data[s_pos] = (void *)vmalloc(PAGE_SIZE << dptr->order);

    if (!dptr->data[s_pos])

        goto nomem;

    memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order);

}

 

а эти строки освобождают память:

 

/* Освободить набор квантов */

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

    if (dptr->data[i])

        vfree(dptr->data[i]);

 

Если вы скомпилируете оба модулей с включённой отладкой, вы сможете увидеть распределение их данных, читая файлы, которые они создают в /proc. Это снимок был сделан на системе x86_64:

 

salma% cat /tmp/bigfile > /dev/scullp0; head -5 /proc/scullpmem

 

Device 0: qset 500, order 0, sz 1535135

 item at 000001001847da58, qset at 000001001db4c000

         0:1001db56000

         1:1003d1c7000

 

salma% cat /tmp/bigfile > /dev/scullv0; head -5 /proc/scullvmem

 

Device 0: qset 500, order 4, sz 1535135

 item at 000001001847da58, qset at 0000010013dea000

         0:ffffff0001177000

         1:ffffff0001188000

 

Следующий вывод, вместо этого, пришёл из системы x86:

 

rudo% cat /tmp/bigfile > /dev/scullp0; head -5 /proc/scullpmem

 

Device 0: qset 500, order 0, sz 1535135

 item at ccf80e00, qset at cf7b9800

         0:ccc58000

         1:cccdd000

 

rudo% cat /tmp/bigfile > /dev/scullv0; head -5 /proc/scullvmem

 

Device 0: qset 500, order 4, sz 1535135

 item at cfab4800, qset at cf8e4000

         0:d087a000

         1:d08d2000

 

Значения показывают два различных поведения. На x86_64 физические адреса и виртуальные адреса связаны с совершенно разными диапазонами адресов (0x100 и 0xffffff00), в то время как на x86 компьютерах vmalloc возвращает виртуальные адреса просто выше связанных с использованием физической памяти.

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