Отображение файла в память (Операционные Системы)

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 21:27, 28 ноября 2016.

Отображение файла в память — это способ работы с файлами в некоторых операционных системах, при котором всему файлу или некоторой непрерывной его части ставится в соответствие определённый участок памяти (диапазон адресов оперативной памяти). При этом чтение данных из этих адресов фактически приводит к чтению данных из отображенного файла, а запись данных по этим адресам приводит к записи этих данных в файл. Примечательно то, что отображать на память часто можно не только обычные файлы, но и файлы устройств[1].

Суть работы

Отображение файла в память

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

  #include<sys/mman.h>
  void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);

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

Ниже приведен пример программы, которая копирует файл с использованием MMF.

 #include <fcntl.h> 
 #include <sys/mman.h> 
 int main(int argc, char *argv[]) 
 { 
   int fdin, fdout; 
   void *src, *dst; 
   struct stat statbuf; 
   if (argc != 3) 
   err_quit("Использование: %s <fromfile> <tofile>", 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); 
 }

Достоинства метода

Альтернативой отображению может служить прямое чтение файла или запись в файл. Такой способ работы менее удобен по следующим причинам:

Необходимо постоянно помнить текущую позицию файла и вовремя её передвигать на позицию, откуда будет производиться чтение или куда будет идти запись. Каждый вызов смены/чтения текущей позиции, записи/чтения — это системный вызов, который приводит к потере времени. Для работы через чтение/запись всё равно приходится выделять буферы определённого размера, таким образом, в общем виде работа состоит из трёх этапов: чтение в буфер -> модификация данных в буфере -> запись в файл. При отображении же работа состоит только из одного этапа: модификации данных в определённой области памяти. Дополнительным преимуществом использования отображения является меньшая по сравнению с чтением/записью нагрузка на операционную систему — дело в том, что при использовании отображений операционная система не загружает в память сразу весь файл, а делает это по мере необходимости, блоками размером со страницу памяти (как правило 4 килобайта). Таким образом, даже имея небольшое количество физической памяти (например 32 мегабайта), можно легко отобразить файл размером 100 мегабайт или больше, и при этом для системы это не приведет к большим накладным расходам. Также выигрыш происходит и при записи из памяти на диск: если вы обновили большое количество данных в памяти, они могут быть одновременно (за один проход головки над диском) записаны на диск[4].

Файл, отображенный на память, удобен также тем, что можно достаточно легко менять его размер и при этом (после переотображения) получать в своё распоряжение непрерывный кусок памяти нужного размера. С динамической памятью такой трюк не всегда возможен из-за явления фрагментации. Когда же мы работаем с отображенным на память файлом — менеджер памяти автоматически настраивает процессор так, что странички ОЗУ, хранящие соседние фрагменты файла, образуют непрерывный диапазон адресов[5].

Недостатки

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

Другой недостаток в том, что размер отображения зависит от используемой архитектуры. Теоретически, 32-битные архитектуры (Intel 386, ARM 9) не могут создавать отображения длиной более 4 Гб.

Платформы, поддерживающие отображение файлов на память

Большинство современных операционных систем или оболочек поддерживают те или иные формы работы с файлами, отображенными на память. Например, функция mmap(), создающая отображение для файла с данным дескриптором, начиная с некоторого места в файле и с некоторой длиной — является частью спецификации POSIX. Таким образом, огромное количество POSIX-совместимых систем, таких как UNIX, Linux, FreeBSD, Mac OS X или OpenVMS, поддерживают общий механизм отображения файлов. ОС Microsoft Windows также поддерживает определённый API для этих целей, например, CreateFileMapping().

Примечания

  1. Создание на Delphi [Электронный ресурс] : Материал из http://www.delphiplus.org/: — Режим доступа: http://www.delphiplus.org/delphi2005-secrety-programirovania/otobrazhenie-failov-v-pamyati.html
  2. Отображение файла в память на WinAPI [Электронный ресурс] : Материал из http://eax.me/: — Режим доступа: http://eax.me/winapi-file-mapping/
  3. Пример отображения файла в память под Linux и MacOS [Электронный ресурс] : Материал из http://eax.me/: — Режим доступа: http://eax.me/linux-file-mapping/
  4. Файлы, отображаемые в память [Электронный ресурс] : Материал из Википедии — свободной энциклопедии: — Режим доступа: https://habrahabr.ru/post/55716/
  5. Отображение файла в память [Электронный ресурс] : Материал из https://ru.wikipedia.org/: — Режим доступа: https://ru.wikipedia.org/wiki/Отображение_файла_в_память