В целом флаг MAP_FIXED не следует применять в портируемых приложениях; вместо этого лучше передать аргументу addr значение NULL, позволяя системе автоматически выбрать адрес для размещения отображения.
Но существует ситуация, в которой портируемое приложение могло бы воспользоваться флагом MAP_FIXED. Речь идет о случае, когда при вызове mmap() участок памяти длиной length, начинающийся с адреса addr, перекрывает собой страницы предыдущего отображения и заменяет их. Можно отобразить несколько участков файла (или файлов) на один непрерывный участок памяти. Для этого нужно сделать следующее.
1. Использовать mmap() для создания анонимного отображения (см. раздел 45.7). В данном вызове аргументу addr передается значение NULL, а флаг MAP_FIXED опускается. Это позволяет ядру выбрать адрес для отображения.
2. Задействовать несколько вызовов mmap() с флагом MAP_FIXED, чтобы отобразить (то есть наложить) участки файла на разные участки отображения, созданного в предыдущем шаге.
Мы могли бы пропустить первый шаг и сразу применить вызов mmap() с флагом MAP_FIXED для создания набора смежных отображений в диапазоне адресов, выбранном приложением, но такой подход был бы менее универсальным. Как отмечалось выше, портируемое приложение должно воздерживаться от попыток создания нового отображения по фиксированному адресу. Первый шаг позволяет избежать проблем с портируемостью, так как выбор цельного диапазона адресов для нашего отображения перекладывается на ядро.
Начиная с Linux 2.6 того же эффекта можно достичь, используя системный вызов remap_file_pages(), который будет описан в следующем разделе. Однако применение флага MAP_FIXED является более портируемым, так как вызов remap_file_pages() доступен только в Linux.
Файловые отображения, созданные с помощью вызова mmap(), являются линейными: между страницами отображенного файла и страницами участка памяти существует точное, последовательное соответствие. Это подходит для большинства приложений. Но в ряде ситуаций приходится создавать большое количество нелинейных отображений — таких, в которых порядок размещения страниц файла в памяти меняется. Пример данного отображения показан на рис. 45.5.
Один из способов создания нелинейного отображения описан в предыдущем разделе и заключается в использовании нескольких вызовов mmap() с флагом MAP_FIXED. Однако такой подход не очень хорошо масштабируется. Проблема в том, что каждый вызов mmap() создает в области виртуальной памяти ядра (англ. virtual memory area, VMA) структуру данных. И каждый раз область VMA тратит время на подготовку и потребляет некий объем памяти ядра, не предназначенной для сбрасывания на диск. Кроме того, наличие большого количества таких областей может ухудшить производительность диспетчера виртуальной памяти; в частности, есть вероятность существенного увеличения времени обработки отказа страницы (с этой проблемой сталкивались некоторые большие системы управления базами данных, хранящие в одном файле несколько разных отображений).
Каждая строка в файле /proc/PID/maps представляет одну область VMA.
Начиная с версии 2.6, ядро Linux поддерживает системный вызов remap_file_pages() для создания нелинейных отображений, который не приводит к появлению множества областей VMA. Он используется следующим образом.
1. Делается вызов mmap() для создания отображения.
2. Выполняются несколько вызовов remap_file_pages(), меняющие соответствие между страницами памяти и страницами файла (вызов remap_file_pages() всего лишь манипулирует таблицами с записями о страницах, принадлежащими процессу).
3. Вызов remap_file_pages() дает возможность отобразить одну и ту же страницу файла на разные сегменты отображенного участка памяти.
#define _GNU_SOURCE
#include
int remap_file_pages(void *
int
Возвращает 0 при успешном завершении или -1 при ошибке
Аргументы pgoff и size обозначают участок файла, который нужно переместить в памяти. Первый из них указывает на начальную позицию участка файла в единицах, равных размеру страницы памяти (возвращаемый вызовом sysconf(_SC_PAGESIZE)). Аргумент size определяет длину участка в байтах. Значение addr служит двум целям:
• определяет существующее отображение, чьи страницы мы хотим поменять местами. Иными словами, это адрес, который должен находиться в пределах участка памяти, отображенного ранее с помощью вызова mmap();
• указывает на адрес в памяти, по которому находятся страницы файла, заданные с использованием аргументов pgoff и size.
Аргументы addr и size должны быть кратными размеру страницы памяти в системе. В противном случае они округляются до ближайшего кратного.
Представьте, что мы применяем следующий вызов mmap() для отображения трех страниц открытого файла, на который ссылается дескриптор fd, и в итоге аргументу addr присваивается адрес 0x4001a000:
ps = sysconf(_SC_PAGESIZE); /* Получаем размер страницы в системе */