Главная » Ядро Linux » Получение страниц памяти

0

Теперь, имея  некоторое понятие  о  том, как  ядро  упрапляет памятью с помощью страниц, зон  и  так  далее, давайте  рассмотрим интерфейсы,  которые реализованы в  ядре  для  того,  чтобы  выделять  и  освобождать  память   внутри  ядра.  Ядро  предоставляет  один  низкоуровневый интерфейс для  выделения памяти и несколько интерфейсов для  доступа  к  ней.  Все  эти  интерфейсы выделяют память  в объеме, кратном размеру  страницы,  и  определены в файле   <linux/gfp.h> .  Основная функция выделения памяти  следующая.

struct page * alloc_pages(unsigned int gfp_mask, unsigned int order)

Данная функция позволяет выделить   2 orde r    (т.е.  1   <<   order )  смежных  страниц (один   непрерывный участок)   физической памяти и  возвращает указатель  на  структуру page , которая соответствует первой  выделенной странице памяти. В случае  ошибки  возвращается значение  NULL.  Параметр  gfp_mask   будет  рассмотрен  несколько позже.  Полученную страницу памяти можно  конвертировать в ее логический адрес  с помощью  следующей функции.

void * page_address(struct page *page)

Эта  функция  возвращает указатель   на  логический адрес, которому в данный момент  соответствует начало  указанной страницы физической  памяти.  Если  нет  необходимости  в  соответствующей  структуре   struc t   page , то  можно   использовать  следующую  функцию.

unsigned long   get_free_pages(unsigned int gfp_mask, unsigned int order)

Эта  функция работает  так  же, как  и  функция  alloc_page s () , за  исключением того,  что  она  сразу  возвращает логический  адрес  первой   выделенной  страницы  памяти.   Так  как  выделяются смежные страницы  памяти,  то  другие  страницы просто следуют  за  первой.

Если  необходима всего  одна  страница памяти,  то для  этой  цели  определены следующие  функции-оболочки,   которые  позволяют уменьшить количество  работы   по набору  кода  программы.

struct page * alloc_page(unsigned int gfp_mask)

unsigned long   get_free_page(unsigned int gfp_mask)

Эти  функции работают  так же, как  и  ранее  описанные, по  для  них  в качестве  параметра   orde r передается нуль  (20   = одна  страница памяти).

Получение страниц заполненных нулями

Для  того  чтобы  получаемые страницы памяти были  заполнены нулями, необходимо  использовать следующую  функцию.

unsigned long get_zeroed_page(unsigned int gfp_mask)

Эта  функция  аналогична  функции   __get_free_pag e  ()  , за  исключением того, что  после  выделения  страницы  памяти  она  заполняется  нулями.  Это  полезно для страниц памяти,  которые возвращаются в  пространство пользователя,  так  как  слу-

чайный "мусор", который находится в страницах памяти,  может  оказаться не  совсем случайным и  случайно может  содержать некоторые  (например,  секретные) данные. Все  данные  необходимо обнулить или  очистить каким-либо  другим  образом  перед тем, как  возвращать информацию в пространство пользователя,  чтобы  при  этом  не пострадала безопасность системы.  В табл.  11.2  приведен список всех  низкоуровневых  средств  выделения памяти.

Таблица 11.2 . Низкоуровневые  средства выделения памяти

Функция                                                 Описание

alloc_pag e (gfp_mask )                          Выделяет одну страницу памяти и возвращает указатель на соответствующую ей структуру page

alloc_page s (gfp_mask ,  order )            Выделяет 2 o r d e r  страниц памяти и возвращает указатель на структуру page первой страницы

__get_free_pag e (gfp_mask )                Выделяет одну страницу памяти и возвращает указатель на ее логический адрес

__get_free_page s (gfp_mask, order )      Выделяет 2 o r d e r  страниц памяти и возвращает указатель на логический адрес первой страницы

get_zeroed_pag e (gfp_mask )                Выделяет одну страницу памяти,  обнуляет ее содержимое и возвращает указатель на ее логический адрес

Освобождение страниц

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

void   free_pages(struct page *page, unsigned int order) void free_pages(unsigned long addr, unsigned int order) void free_page(unsigned long addr)

Необходимо быть  внимательными и освобождать только  те страницы памяти, которые  вам  выделены. Передача неправильного  значения  параметра page , add r  или orde r  может  привести  к  порче  данных.  Следует  помнить,  что  ядро  доверяет себе. В  отличие от пространства пользователя, ядро  с удовольствием зависнет, если  вы  попросите. Рассмотрим пример. Мы  хотим  выделить 8 страниц памяти.

page=   get_free_pages(GFP_KERNEL,3);

if(!page){

/* недостаточно памяти: эту ошибку необходимо обработать самим! */

return -ENOMEM;

}

/* переменная ‘page’ теперь содержит адрес первой из восьми страниц памяти*/

free_pages(page, 3);

/*

* наши страницы памяти теперь освобождены и нам больше нельзя

* обращаться по адресу, который хранится в переменной ‘page’

*/

Значение GFP_KERNEL, которое  передается в качестве  параметра, — это пример флага  gfp_mask, который скоро  будет рассмотрен детально.

Обратите внимание на проверку ошибок после вызова  функции     __get_free _ page s () . Выделение памяти в ядре  может быть неудачным, поэтому  код  должен проверить и при  необходимости  обработать соответствующую ошибку.  Это  может означать, что придется пересмотреть все операции, которые были до этого сделаны. В связи  с этим, часто  имеет  смысл  выделять  память  в самом  начале  подпрограммы, чтобы  упростить  обработку  ошибок. В противном случае после  попытки выделения памяти отмена  ранее  выполненных действий может  оказаться сложной.

Низкоуровневые функции выделения памяти полезны, когда необходимы участки памяти, которые находятся в смежных  физических страницах, особенно если  необходима  одна  страница или  очень  большое  количество страниц. Для  более  общего случая, когда необходимо выделить  заданное количество байтов  памяти, ядро  предоставляет  функцию kmalloc() .

Функция kmallo c ()

Функция kmalloc  ()  работает  аналогично функции mallос  ()  пространства пользователя, за исключением того, что добавляется еще один  параметр flags . Функция kmalloc ()  — это простой интерфейс для выделения в ядре участков  памяти размером  в заданное количество байтов.  Если  необходимы смежные страницы физической  памяти, особенно когда их количество близко  целой  степени двойки, то ранее рассмотренные интерфейсы могут оказаться лучшим  выбором. Однако для большинства операций выделения памяти в ядре функция kmalloc () — наиболее предпочтительный интерфейс.

Рассматриваемая функция определена в файле   <linux/slab.h >  следующим образом.

void * kmalloc(size_t size, int flags)

Данная функция возвращает указатель  на участок  памяти, который имеет  размер хотя бы siz e байт3.  Выделенный участок памяти содержит физически смежные страницы. В случае  ошибки функция возвращает значение NULL. Выделение памяти в ядре  заканчивается успешно, только  если доступно  достаточное количество памяти. Поэтому после  вызова  функции kmalloc  ()  всегда  необходимо проверять возвращаемое значение на равенство значению NULL и соответственным образом обрабатывать ошибку.

Рассмотрим пример. Допустим, нам необходимо выделить  достаточно памяти для того, чтобы  в ней  можно  было  разместить некоторую воображаемую структуру  dog.

struct dog *ptr;

ptr = kmalloc(sizeof(struct dog), GFP_KERNEL);

if (!ptr)

/* здесь обработать ошибку … */

3   Данная  функция может выделить  памяти  больше,  чем указано,  и нет никакой возможности узнать, на  сколько  больше !   Поскольку   в  своей  основ е  система  выделени я  памят и  в ядр е  базируется   на страницах,   некоторы е запросы  на  выделение  памяти  могут округляться,  чтобы  хорош о вписываться  е области  доступной  памяти.  Ядр о  никогда  не  выделит  меньше  памяти,   чем  необходимо.   Если ядро  не v, состоянии найти  хотя бы указанное  количество  байтов,  то операци я завершится неудачно и функции возвратит  значени е NULL.

Если вызов функции  kmalloc () завершится  успешно,  то переменная  pt r будет указывать на область памяти,  размер которой больше указанного  значения  или равен ему. Флаг GFP_KERNEL определяет тип поведения  системы выделения  памяти, когда она пытается  выделить необходимую память при вызове функции  kmalloc ().

Флаги  gfp_mask

Выше были показаны  различные примеры использования флагов, которые модифицируют работу системы выделения памяти,  как при вызове низкоуровневых  функций, работающих на уровне страниц,  так и при использовании функции  kmalloc {) . Теперь давайте рассмотрим  их более детально.

Флаги разбиты на три категории:  модификаторы  операций,  модификаторы  зон и флаги типов. Модификаторы операций указывают, каким образом ядро должно выделять указанную  память.  В некоторых  ситуациях только некоторые  методы могут использоваться  для выделения  памяти.  Например,  обработчики  прерываний  могут потребовать  от ядра,  что нельзя  переходить  в состояние  ожидания  при выделении памяти  (поскольку обработчики  прерывания  не могут быть перепланированы), Модификаторы зоны указывают, откуда нужно выделять память. Как было рассказано, ядро делит физическую  память на несколько  зон, каждая из которых служит для различных целей. Модификаторы зоны указывают, из какой зоны выделять память. Флаги типов представляют собой различные комбинации модификаторов  операций и зон,  которые необходимы для определенного  типа выделения памяти.  Флаги типов содержат в себе различные  модификаторы, вместо которых можно просто использовать один флаг типа. Флаг GFP_KERNEL — это флаг типа, который используется кодом,  выполняющимся в ядре в контексте  процесса.  Рассмотрим  все флаги отдельно.

Модификаторы операций

Все флаги,  включая модификаторы  операций,  определены в заголовочном файле

<linux/gfp.h>.  Подключение  файла  <linux/slab.h > также подключает  и этот заголовочный  файл,  поэтому его не часто приходится подключать явно. На практике обычно лучше использовать  флаги типов,  которые будут рассмотрены  дальше. Тем не менее полезно  иметь представление  об индивидуальных флагах. В табл. 11.3 показан список модификаторов  операций.

Описанные  модификаторы  можно указывать вместе, как показано  в следующем примере.

ptr = kmalioc(size,   GFP_WAIT |    GFP_IO | __GFP_FS);

Этот код дает инструкцию  ядру (а именно  функции  alloc_page s ()),  что операция выделения памяти может быть блокирующей,  выполнять операции  ввода-вывода и операции  файловой  системы,  если это необходимо.  В данном  случае ядру предоставляется  большая  свобода в отношении  того,  где оно будет искать необходимую память,  чтобы удовлетворить запрос.

Большинство  запросов на выделение памяти указывают эти модификаторы, но это делается косвенным  образом с помощью флагов типа,  которые скоро будут рассмотрены.  Не нужно волноваться,  у вас не будет необходимости  каждый раз разбираться,  какие из этих ужасных флагов использовать при выделении памяти!

Таблица 11.3 . Модификаторы операций

Флаг                                  Описание

  GFP_WAIT

  GFP_HIGH

  GFP_IO

  GFP_FS

  GFP_COLD

  GFP_NOWARN

  GFP_REPEAT

  GFP_NOFAIL

  GFP_NORETRY

  GFP_NO_GROW

  GFP_COMP

Операция выделения памяти может переводить текущий процесс в состояние ожидания

Операция выделения памяти может обращаться к аварийным запасам

Операция выделения памяти может использовать дисковые операции ввода-вывода

Операция выделения памяти может использовать операции вводавывода файловой системы

Операция выделения памяти должна использовать страницы памяти, содержимое которых не находится в кэше процессора (cache cold)

Операция выделения памяти не будет печатать сообщения об ошибках

Операция выделения памяти повторит попытку выделения в случае ошибки

Операция выделения памяти будет повторять попытки выделения неопределенное количество раз

Операция выделения памяти никогда не будет повторять попытку выделения памяти

Используется внутри слябового распределителя памяти (slab layer) Добавить метаданные составной (compound) страницы памяти.

Используется кодом поддержки больших страниц памяти (hugetlb)

Модификаторы зоны

Модификаторы зоны  указывают, из какой зоны  должна  выделяться память. Обычно  выделение может  происходить из  любой  зоны.   Однако ядро  предпочитает зону ZONE_NORMAL, чтобы  гарантировать, что  в других зонах, когда  это  необходимо, есть свободные страницы.

Существует всего  два  модификатора зоны, поскольку, кроме  зоны  ZONE_N0RMAL (из  которой по  умолчанию  идет  выделение  памяти),  существует всего  две  зоны. В табл.  11.4 приведен список модификаторов зоны.

Таблица 11.4. Модификаторы зоны

Флаг                                               Описание

    GFP_DMA

    GFP_HIGHMEM

Выделять память только из зоны ZONE_ DMA

Выделять память только из зон ZONE_HIGHME M и ZONE_NORMA L

Указание одного  из  этих  флагов  изменяет зону, из  которой ядро  пытается выделить  память. Флаг  __GFP_DMA требует, чтобы  ядро  выделило память только  из зоны ZONE_DMA. Этот  флаг  эквивалентен  следующему высказыванию  в  форме жесткого требования: "Мне абсолютно необходима память, в которой можно выполнять операции прямого  доступа к  памяти".  Флаг      GFP_HIGHMEM,   наоборот, требует, чтобы  выделение  памяти было  из  зон  ZONE_NORMAL и  ZOHE_HIGHMEM  (вторая более  предпочтительна). Этот  флаг  эквивалентен запросу: "Я могу использовать  верхнюю  память,  но мне насамомделе,всеравно,иделайте,чтохотите,обычнаяпамятьтожеподойдет".

Если  не  указан   ни  один   из  флагов,  то  ядро  пытается  выделять память  из  зон

ZONE_NORMAL  и   ZONE_DMA,  отдавая  значительное   предпочтение   зоне   ZONE_NORMAL.

Флаг     GFP_HIGHMEM нельзя укалывать при  вызове  функций  __get_free_page s  () или  kmallo c (>  .  Это  связано с тем, что  они  возвращают логический адрес, а не структуру  page , и  появляется возможность, что  эти  функции выделят память,  которая  в данный момент не  отображается п виртуальное адресное пространство ядра  и поэтому не  имеет  логического адреса.   Только  функция  alloc_page a ()   может  выделять  страницы в верхней памяти. Однако в большинстве случаев  в запросах на выделение памяти не  нужно  указывать модификаторы зоны,  так  как  достаточно того, что  используется   зона   ZONE_NORMAL.

Флаги типов

Флаги   типов   указывают модификаторы  операций  и  зон,  которые необходимы для  выполнения  запросов определенных типов.   В связи  с  этим, в  коде  ядра  стараются  использовать правильный флаг  типа  и  не  использовать больших наборов модификаторов.   Это  одновременно   и  проще   и  при   этом   меньше   шансов   ошибиться. В табл.  11.5  приведен список возможных флагов  типов, а в табл.  11.6 показано, какие модификаторы  соответствуют какому флагу.

Таблица 11.5. Флаги типов

Флаг                       Описание

GFP_ATOMI C       Запрос на выделение памяти высокоприоритетный и в состояние ожидания переходить нельзя. Этот флаг предназначен для  использования в обработчиках прерываний, нижних половин и в других ситуациях, когда нельзя переходить в состояние ожидания

GFP_NOI O          Запрос на выделение памяти может блокироваться, но при  его выполнении нельзя выполнять операции дискового ввода-вывода. Этот флаг предназначен для использования в коде блочного ввода-вывода, когда нельзя инициировать новые операции ввода-вывода

GFP_NOF S            Запрос на выделение памяти может блокироваться и выполнять дисковые операции ввода-вывода, но запрещено выполнять операции, связанные с файловыми системами. Этот флаг предназначен для использования в коде файловых систем, когда нельзя начинать выполнение  новых файловых операций

GFP_KERNE L       Обычный запрос на выделение памяти, который может блокироваться. Этот флаг предназначен для использования в коде, который выполняется в контексте процесса, когда безопасно переходить в состояние ожидания

GFP_USE R           Обычный запрос на выделение памяти, который может блокироваться. Этот флаг используется для выделения памяти процессам пространства пользователя

GFP_HIGHUSE R  Запрос на выделение памяти из зоны ZONE_HIGHMEM , который может блокироваться. Этот флаг используется для выделения памяти процессам пространства пользователя

GFP_DM A              Запрос на выделение памяти из зоны ZONE_DMA . Драйверам устройств, которым нужна память для выполнения операций по ПДП, необходимо использовать этот флаг обычно в комбинации с одним из описанных выше флагов

Таблица 11.6. Список модификаторов, соответствующих каждому флагу типа

Флаг                                         Модификаторы

GFP_ATOMIC GFP_NOIO GFP_NOFS GFP_KERNEL GFP_USER GFP_HIGHUSER GFP_DMA

__GFP_HIGH

  GFP_WAIT

(   GFP_WAIT |    GFP_IO)

( GFP_WAIT |   GFP_IO |   GFP_FS) (   GFP_WAIT |    GFP_IO |   GFP_FS)

(  GFP_WAIT |  GFP_IO |   GFP_FS | __GFP_HIGHMEM)

  GFP_DMA

Рассмотрим наиболее часто  используемые  флаги  и  для  чего  они  могут  быть  нужны.  Подавляющее большинство операций выделения памяти в ядре  используют флаг GFP_KERNEL. В результате  операция выделения памяти имеет  обычный приоритет и может  переводить процесс в состояние ожидания. Поскольку этот  вызов  может  блокироваться, его  можно  использовать только   в контексте процесса,  выполнение которого  может  быть  безопасно перепланировано  (т.е.  нет  удерживаемых блокировок и т.д.).  При  использовании этого  флага  нет  никаких оговорок по  поводу  того, каким образом  ядро  может  получить   необходимую память,  поэтому операция  выделения памяти имеет  большой шанс  выполниться успешно.

Можно сказать, что  свойства флага  GFP_ATOMIC лежат  на  противоположном конце  спектра. Так  как  этот  флаг  указывает,  что  операция выделения памяти не  может переходить в состояние ожидания,  то  такая  операция очень  ограничена в  том, какую  память   можно   использовать для  выделения.  Если  нет  доступного участка  памяти  заданного размера,  то  ядро,  скорее   всего,  не  будет  пытаться  освободить память, поскольку вызывающий  код  не  может  переходить в  состояние  ожидания. Пр и  использовании флага  GFP_KERNEL,  наоборот, ядро  может  перевести вызывающий код в  состояние ожидания, чтобы  во время  ожидания вытеснить страницы на диск  (swap out), очистить измененные страницы памяти путем  записи их в дисковый файл  (flush dirty  pages)  и т.д.  Поскольку при  использовании флага  GFP_ATOMIC нет  возможности выполнить ни  одну  из  этих  операций,  то  и  шансов успешно выполнить выделение памяти тоже  меньше  (по  крайней мере, когда  в системе  недостаточно памяти).  Тем не  менее  использование флага  GFP_ATOMIC— это  единственная возможность,  когда вызывающий код  не  может  переходить в  состояние ожидания, как  в  случае  обработчиков  прерываний и  нижних половин.

По  своим  свойствам между  рассмотренными  флагами находятся флаги  GFP_NOIC и GFP_NOFS.  Операции пыделения памяти,  которые запущены с  этими  флагами, могут  блокироваться,  но  они  воздерживаются  от  выполнения  некоторых действий. Выделение памяти  с  флагом   GFP_NOIO не  будет  запускать никаких операций дискового  ввода-вывода. С  другой  стороны, при  использовании флага  GFP_NOFS могут запускаться  операции  дискового  ввода-вывода,  но  не  могут  запускаться  операции файловых систем.   Когда  эти  флаги  могут  быть  полезны? Они   соответственно необходимы   для  определенного  низкоуровневого  кода  блочного  ввода-вывода  или  кода файловых  систем.   Представьте  себе,  что  в  некотором часто  используемом  участке кода  файловых систем  используется выделение памяти  без указания  флага  GFP_NOFS. Если  выделение памяти требует  выполнения  операций  файловой  системы,  то  вы-

деление памяти приведет к  еще  большему количеству операций файловой системы, которые потребуют дополнительного выделения памяти и  еще  большего количества файловых операций!  При  разработке кода,  который  использует выделение памяти, необходимо гарантировать,  что  операции выделения памяти не  будут использовать этот  код, как  в рассмотренном случае, иначе  может  возникнуть самоблокировка. Не удивительно,  что  и  ядре  рассматриваемые флаги  используются только  в небольшом количестве мест.

Флаг  GFP_DMA применяется для  указания, что  система выделения памяти должна  при  выполнении запроса предоставить память из  зоны  ZONE_DMA. Этот  флаг  используется драйверами устройств,  для  которых необходимо  выполнение  операций прямого доступа  к  памяти.  Обычно этот  флаг  должен   комбинироваться с  флагами CFP_ATOMIC или GFP_KERNEL.

В  подавляющем большинстве случаев  при  разработке кода  вам  будет  необходимо использовать флаги  GFP_ATOMIC или  GFP_KERNEL. В табл.  11.7 показано какие флаги и  в каких  ситуациях необходимо использовать. Независимо  от типа  операции выделения памяти,  необходимо проверять результат  и  обрабатывать ошибки.

Таблица 11.7. Какой флаг и когда необходимо использовать

Ситуация                                                                      Решение

Контекст процесса, можно переходить в со           Используется флаг GFP_KERNEL

стояние ожидания

Контекст процесса, нельзя переходить в со        Используется флаг GFP_ATOMI C или память выстояние ожидания                                                     деляется с использованием флага GFP_KERNE L

но в более ранний или поздний момент, когда можно переходить в состояние ожидания

Обработчик прерывания                                           Используется флаг GFP_ATOMI C Обработка нижней половины                                   Используется флаг GFP_ATOMI C Необходима память для выполнения операций      Используются флаги

ПДП, можно переходить в состояние ожидания      (GFP_DM A  | GFP_KERNEL )

Необходима память для выполнения операций     Используются флаги (GFP_DMA | GFP_ATOMIC ) ПДП, нельзя переходить в состояние ожидания      или выделение выполняется в более поздний

или более ранний момент времени

Функция kfree( )

Обратной к функции kmallo c ()  является функция kfre e () , которая определена в файле   <linux/slab.h >  следующим образом.

void kfree(const void *ptr)

Функция kfre e ()   позволяет освободить память,  ранее  выделенную с  помощью функции kmallo c () . Вызов  этой  функции для  памяти, которая ранее  не  была  выделена  с помощью функции  kmallo c ()   или  уже  была  освобождена,  приводит к очень плохим последствиям, таким  как  освобождение памяти, которая принадлежит другим частям  ядра.  Так  же  как  и  в пространстве пользователя,  количество операций  выделения памяти должно быть  равно  количеству операций освобождения,  чтобы  предотвратить  утечку  памяти и  другие  проблемы.  Следует  обратить внимание,  что  случай вызова  kfre e (NULL)  специально проверяется и поэтому является безопасным.

Рассмотрим пример выделения памяти в обработчике прерывания. В этом  примере  обработчику прерывания  необходимо  выделить буфер   памяти для  хранения входных  данных. С помощью препроцессора определяется константа. BUF_SIZE,  как размер   буфера  памяти в  байтах, который,  скорее  всего, должен   быть  больше,  чем несколько байт.

char *buf;

buf = kmalloc(BUF_SIZE, GFP_ATOMIC);

if (!buf)

/* ошибка выделения памяти! */

Позже, когда  память больше  не  нужна, нужно   не  забыть  освободить ее  с  помощью  вызова  функции

kfree(buf);

Функция vmallo c ()

Функция vmallo c ()   работает  аналогично функции kmallo c () , за  исключением того,  что  она  выделяет страницы  памяти,  которые только   виртуально смежные и необязательно смежные физически.  Точно  так  же  работают и  функции выделения памяти в пространстве пользователя: страницы памяти,  которые выделяются с помощью  функции  malloc   () , япляются смежными в виртуальном адресном пространстве  процесса, но  нет  никакой гарантии, что  они  смежные в физической оперативной  памяти. Функция  kmallo c ()    отличается тем,  что  гарантирует, что  страницы памяти будут физически (и  виртуально) смежными. Функция vmallo c ()  гарантирует только, что  страницы будут  смежными в  виртуальном  адресном  пространстве  ядра. Это  реализуется  путем  выделения  потенциально  несмежных участков физической памяти и  "исправления" таблиц  страниц, чтобы  отобразить эту физическую память в  непрерывный участок  логического адресного пространства.

Большей частью, только   аппаратным  устройствам необходимо выделение  физически   непрерывных участков памяти.  Аппаратные устройства существуют по  другую  сторону модуля  управления памятью и  не  "понимают" виртуальной адресации. Следовательно,  все  области памяти,  с  которыми  работают аппаратные устройства, должны состоять из  физически  смежных блоков,  а  не  из  виртуально непрерывных участков. Для  участков памяти,  которые используются только  программным обеспечением, например буферы  памяти, связанные с  процессами,  прекрасно подходят области памяти,  которые только  виртуально непрерывны.  При  программировании заметить разницу невозможно. Это  связано с тем, что память ядром  воспринимается как  логически непрерывная.

Несмотря на  то что  физически  смежные страницы памяти необходимы только  в определенных случаях,  большая  часть  кода  ядра  использует для  выделения памяти функцию kmallo c ()  , а не  vmallo c () .  Это, в основном, делается  из  соображений производительности. Для  того  чтобы  физически несмежные страницы памяти сделать  смежными в виртуальном адресном пространстве,  функция  vmallo c ()   должна соответствующим образом заполнить таблицы страниц.  Хуже  того,  страницы  памяти, которые получаются с помощью функции vmallo c () , должны отображаться посредством страниц памяти,  которые принадлежат к таблицам страниц  (потому что

выделяемые страницы памяти  физически  несмежные).  Это  приводит к  значительно  менее  эффективному использованию буфера  TLB4,  чем  в  случае,  когда  страницы памяти  отображаются напрямую.  Исходя   из  этих  соображений функция  vmallo c () используется только   тогда,  когда  она  абсолютно  необходима,  обычно   это  делается для  выделения очень  больших  областей   памяти.  Например,  при  динамической загрузке  модулей   ядра,  модули  загружаются  в  память,  которая  выделяется с  помощью функции vmallo c  () .

Функция vmallo c  ()   объявлена в  файле    <linux/vmalloc.h >   и  определена в файле  mm/vmalloc.с .  Использование этой  функции аналогично функции mallo c () пространства  пользователя.

void * vmalloc(unsigned long size)

Функция  возвращает указатель   на  виртуально  непрерывную  область  памяти размером   по  крайней мере  siz e  байт.  В  случае  ошибки эта  функция  возвращает значение  NULL.  Данная функция может  переводить процесс в состояние ожидания и  соответственно не  может  вызываться в контексте прерывания или  в других  ситуациях, когда  блокирование  недопустимо.

Для  освобождения памяти,  выделенной с помощью функции vmallo c () , необходимо  использовать функцию

void vfree(void *addr)

Эта  функция  освобождает участок  памяти,  который начинается  с  адреса  add r  и был  ранее  выделен   с  помощью функции  vmallo c () .  Данная функция также  может переводить процесс  в состояние ожидания и  поэтому   не  может  вызываться из  контекста  прерывания. Функция не  возвращает никаких значений.

Использовать рассмотренные функции  очень  просто.   Например,  следующим образом.

char *buf;

buf = vmalloc (16 * PAGE_SIZE); /* получить 16 страниц памяти */

if (!buf)

/* ошибка! Не удалось выделить память */

/*

* переменная buf теперь указывает на область памяти

* размером, по крайней мере, 16*PAGE_SIZE байт, которая состоит

* из виртуально смежных блоков памяти

*/

После  того  как  память  больше  не  нужна,  необходимо убедиться, что  она  освобождается  с  помощью  следующего вызова.

vfre e  (buf) ;

4   Буфер  TLB  (translation lookside  buffer или  буфер  быстрого  преобразования адреса)  — это аппаратны й буфер  памяти, которы й используется в большинстве аппаратных платформ для кэширования отображени й виртуальных адресов  памяти  в физически е адреса.  Этот буфер  позволяет существенно  повысит ь производительност ь системы, так  как  большинство операци й доступа  к памяти  выполняются с использованием виртуальной адресации.

Источник: Лав,  Роберт. Разработка ядра  Linux, 2-е  издание. : Пер.  с англ.  — М.  : ООО  «И.Д.  Вильяме» 2006. — 448 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии