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

26 октября 2015

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

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

#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

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

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

Открываем файл на чтение:

int fd = open(fname, O_RDONLY, 0);
if(fd < 0) {
  std::cerr << "fileMappingCreate - open failed, fname =  "
            << fname << ", " << strerror(errno) << std::endl;
  return nullptr;
}

Узнаем размер файла:

struct stat st;
if(fstat(fd, &st) < 0) {
  std::cerr << "fileMappingCreate - fstat failed, fname = "
            << fname << ", " << strerror(errno) << std::endl;
  close(fd);
  return nullptr;
}

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 << "fileMappingCreate - mmap failed, fname = "
            << fname << ", " << strerror(errno) << std::endl;
  close(fd);
  return nullptr;
}

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

FileMapping * mapping = (FileMapping *)malloc(sizeof(FileMapping));
if(mapping == nullptr) {
  std::cerr << "fileMappingCreate - malloc failed, fname = "
            << fname << std::endl;
  munmap(dataPtr, fsize);
  close(fd);
  return nullptr;
}

mapping->fd = fd;
mapping->fsize = fsize;
mapping->dataPtr = dataPtr;

return mapping;

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

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

munmap(mapping->dataPtr, mapping->fsize);
close(mapping->fd);
free(mapping);

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

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

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

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

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

Метки: , .


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