Map file to memory linux

Файлы, отображаемые в память

В этой статье я хотел бы рассказать о такой замечательной штуке, как файлы, отображаемые в память(memory-mapped files, далее — MMF).
Иногда их использование может дать довольно таки существенный прирост производительности по сравнению с обычной буферизированной работой с файлами.

Это механизм, который позволяет отображать файлы на участок памяти. Таким образом, при чтении данных из неё, производится считывание соответствующих байт из файла. С записью аналогично.
«Клёво, конечно, но что это даёт?» — спросите вы. Поясню на примере.
Допустим, перед нами стоит задача обработки большого файла(несколько десятков или даже сотен мегабайт). Казалось бы, задача тривиальна — открываем файл, поблочно копируем из него в память, обрабатываем. Что при этом происходит. Каждый блок копируется во временный кэш, затем из него в нашу память. И так с каждым блоком. Налицо неоптимальное расходование памяти под кэш + куча операций копирования. Что же делать?
Тут-то нам на помощь и приходит механизм MMF. Когда мы обращаемся к памяти, в которую отображен файл, данные загружаются с диска в кэш(если их там ещё нет), затем делается отображение кэша в адресное пространство нашей программы. Если эти данные удаляются — отображение отменяется. Таким образом, мы избавляемся от операции копирования из кэша в буфер. Кроме того, нам не нужно париться по поводу оптимизации работы с диском — всю грязную работу берёт на себя ядро ОС.
В своё время я проводил эксперимент. Замерял с помощью quantify скорость работы программы, которая буферизировано копирует большой файл размером 500 мб в другой файл. И скорость работы программы, которая делает то же, но с помощью MMF. Так вот вторая работает быстрее почти на 30% (в Solaris, в других ОС результат может отличаться). Согласитесь, неплохо.
Чтобы воспользоваться этой возможностью, мы должны сообщить ядру о нашем желании отобразить файл в память. Делается это с помощью функции mmap().

Читайте также:  Xpress m2020 driver linux

Она возвращает адрес начала участка отображаемой памяти или MAP_FAILED в случае неудачи.
Первый аргумент — желаемый адрес начала участка отбраженной памяти. Не знаю, когда это может пригодится. Передаём 0 — тогда ядро само выберет этот адрес.
len — количество байт, которое нужно отобразить в память.
prot — число, определяющее степень защищённости отображенного участка памяти(только чтение, только запись, исполнение, область недоступна). Обычные значения — PROT_READ, PROT_WRITE (можно кобминировать через ИЛИ). Не буду на этом останавливаться — подробнее читайте в манах. Отмечу лишь, что защищённость памяти не установится ниже, чем права, с которыми открыт файл.
flag — описывает атрибуты области. Обычное значение — MAP_SHARED. По поводу остальных — курите маны. Но замечу, что использование MAP_FIXED понижает переносимость приложения, т.к. его подержка является необязательной в POSIX-системах.
filedes — как вы уже догались — дескриптор файла, который нужно отобразить.
off — смещение отображенного участка от начала файла.

Важное замечание. Если вы планируете использовать MMF для записи в файл, перед маппингом необходимо установить конечный размер файла не меньше, чем размер отображенной памяти! Иначе нарвётесь на SIGBUS.

Ниже приведён пример(честно стырен из замечательной книжки «Unix. Профессиональное программирование») программы, которая копирует файл с использованием MMF.

#include
#include
int main( int argc, char *argv[])
<
int fdin, fdout;
void *src, *dst;
struct stat statbuf;
if (argc != 3)
err_quit( «Использование: %s » , argv[0]);
if ( (fdin = open(argv[1], O_RDONLY)) < 0 )
err_sys( «невозможно открыть %s для чтения» , argv[1]);
if ( (fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0 )
err_sys( «невозможно создать %s для записи» , argv[2]);
if ( fstat(fdin, &statbuf) < 0 ) /* определить размер входного файла */
err_sys( «fstat error» );
/* установить размер выходного файла */
if ( lseek(fdout, statbuf.st_size — 1, SEEK_SET) == -1 )
err_sys( «ошибка вызова функции lseek» );
if ( write(fdout, «» , 1) != 1 )
err_sys( «ошибка вызова функции write» );
if ( (src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED )
err_sys( «ошибка вызова функции mmap для входного файла» );
if ( (dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, 0)) == MAP_FAILED )
err_sys( «ошибка вызова функции mmap для выходного файла» );
memcpy(dst, src, statbuf.st_size); /* сделать копию файла */
exit(0);
>
* This source code was highlighted with Source Code Highlighter .

Вот вобщем-то и всё. Надеюсь, эта статья была полезной. С удовольствием приму конструктивную критику.

Читайте также:  Linux получить размер терминала

Источник

Записки программиста

Отображения файла в память под Linux с помощью mmap

В прошлый раз мы поговорили об отображении файлов в память при помощи WinAPI, а сегодня разберемся, как то же самое делается под nix-системами, в частности Linux и MacOS. Проверить код под FreeBSD я поленился, но по идее все должно работать и в этой операционной системе. Повторюсь — я почти уверен, что многие читатели сего блога уже знакомы с отображением файлов в память, поэтому пост предназначен для всех остальных читателей.

Укажем необходимые инклуды и объявим структуру FileMapping, хранящую файловый дескриптор, размер файла и указатель на участок памяти с отображением:

struct FileMapping {
int fd ;
size_t fsize ;
unsigned char * dataPtr ;
} ;

Рассмотрим чтение из файла с использованием отображения.

size_t fsize = ( size_t ) st. st_size ;

Вызовом mmap создаем отображение файла в память:

unsigned char * dataPtr = ( unsigned char * ) mmap ( nullptr, fsize,
PROT_READ,
MAP_PRIVATE,
fd, 0 ) ;
if ( dataPtr == MAP_FAILED ) {
std :: cerr close ( fd ) ;
return nullptr ;
}

Наконец, заполняем структуру FileMapping и возвращаем указатель на нее в качестве результата:

FileMapping * mapping = ( FileMapping * ) malloc ( sizeof ( FileMapping ) ) ;
if ( mapping == nullptr ) {
std :: cerr munmap ( dataPtr, fsize ) ;
close ( fd ) ;
return nullptr ;
}

mapping — > fd = fd ;
mapping — > fsize = fsize ;
mapping — > dataPtr = dataPtr ;

Теперь по адресу mapping->dataPtr мы можем читать mapping->fsize байт содержимого файла.

Как всегда, не забываем освобождать за собой ресурсы, когда они становятся ненужны:

Вот и все! Сожалею, если вы ожидали чего-то более сложного 🙂 Полную версию исходного кода вы найдете здесь.

Те, кому представленный материал показался слишком простым, могут в качестве домашнего задания сделать следующее:

  • Взять одну из *BSD систем и проверить, работает ли код на ней;
  • Переписать пример так, чтобы файл можно было не только читать, но и писать в него;
  • Выяснить, можно ли менять размер файла, отображенного в память;
  • Выяснить, что будет, если создать отображение файла, а затем записать в него что-то через обычный вызов write;
  • Погуглить на тему использования mmap в качестве IPC, написать соответствующий пример;
Читайте также:  Discord voice changer linux

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

Дополнение: Обратите также внимание на системные вызовы mlock / munlock, msync, madvise и mremap. В определенных типах приложений (например, СУБД) они могут быть очень и очень полезны!

Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.

Источник

Оцените статью
Adblock
detector