Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Программирование для Windows NT

© Александр Фролов, Григорий Фролов
Том 27, часть 2, М.: Диалог-МИФИ, 1996, 272 стр.

[Назад] [Содеожание] [Дальше]

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

В операционную систему Microsoft Windows NT встроен эффективный механизм виртуальной памяти, описанный нами в предыдущем томе “Библиотеки системного программиста”. При использовании этого механизма приложениям доступно больше виртуальной оперативной памяти, чем объем физической оперативной памяти, установленной в компьютере.

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

Почему мы вспомнили о виртуальной памяти в главе, посвященной файлам?

Потому что механизм работы с файлами, отображаемыми на память, напоминает механизм работы вирутальной памяти.

Напомним, что в операционной системе Microsoft Windows NT каждому процессу выделяется 2 Гбайта адресного пространства. Любой фрагмент этого пространства может быть отображен на фрагмент файла соответствующего размера.

На рис. 1.1 показано отображение фрагмента адресного пространства приложения, размером в 1 Гбайт, на фрагмент файла, имеющего размер 5 Гбайт.

Рис. 1.1. Отображение фрагмента адресного пространства приложения на файл

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

Как выполняется отображение фрагмента адресного пространства на фрагмент файла?

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

В процессе отображения адресного пространства память не выделяется, а только резервируется. Поэтому отображение фрагмента размером 1 Гбайт не вызовет переполнение файлов виртуальной памяти. Более того, при отображении файлы виртуальной памяти вообще не используются, так как страницы фрагмента связываются с отображаемым файлом.

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

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

Заметим, что операционная система Microsoft Windows NT активно работает с файлами, отобажаемыми в память. В частности, при загрузке исполнимого модуля приложения соответствующий exe- или dll-файл отображается на память, а затем ему передается управление. Когда пользователь запускает вторую копию приложения, для работы используется файл, который уже отображается в память. В этом случае соответствующие страницы виртуальной памяти отображаются в адресные пространства обоих приложений. При этом возникает одна интересная проблема, связанная с использованием глобальных переменных.

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

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

Аналогичная методика используется и для страниц, содержащих программный код. Если приложение (например, отладчик) пытается изменить страницу, содержащую исполнимый код, операционная система Microsoft Windows NT создает еще одну копию страницы и отображает ее в адресное пространство соответствующего процесса. Подробнее об этом мы расскажем в разделе, посвященном использованию файлов, отображаемых на память, для передачи данных между различными процессами.

Создание отображения файла

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

Прежде всего, приложение должно открыть файл при помощи функции CreateFile, известной вам из предыдущего тома “Библиоткеи системного программиста”. Ниже мы привели прототип этой функции:


HANDLE CreateFile(
  LPCTSTR lpFileName,              // адрес строки имени файла 
  DWORD   dwDesiredAccess,         // режим доступа 
  DWORD   dwShareMode,// режим совместного использования файла 
  LPSECURITY_ATTRIBUTES lpSecurityAttributes, // дескриптор 
                                              // защиты 
  DWORD  dwCreationDistribution,   // параметры создания 
  DWORD  dwFlagsAndAttributes,     // атрибуты файла 
  HANDLE hTemplateFile);   // идентификатор файла с атрибутами

Через параметр lpFileName вы, как обычно, должны передать этой функции адрес текстовой строки, содержащей путь к открываемому файлу.

С помощью параметра dwDesiredAccess следует указать нужный вам вид доступа. Если файл будет открыт только для чтения, в этом параметре необходимо указать флаг GENERIC_READ. Если вы собираетесь выполнять над файлом операции чтения и записи, следует указать логическую комбинацию флагов GENERIC_READ и GENERIC_WRITE XE "GENERIC_WRITE" . В том случае, когда будет указан только флаг GENERIC_WRITE, операция чтения из файла будет запрещена.

Не забудьте также про параметр dwShareMode. Если файл будет использоваться одновременно несколькими процессами, через этот параметр необходимо передать режимы совместного использования файла: FILE_SHARE_READ или FILE_SHARE_WRITE.

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

В случае успешного завершения, функция CreateFile возвращает идентификатор открытого файла. При ошибке возвращается значение INVALID_HANDLE_VALUE. Здесь все как обычно, пока никакого отображения еще не выполняется.

Для того чтобы создать отображение файла, вы должны вызвать функцию CreateFile Mapping, прототип которой приведен ниже:


HANDLE CreateFileMapping(
  HANDLE hFile,           // идентификатор отображаемого файла 
  LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // дескриптор
                                                 // защиты 
  DWORD flProtect,         // защита для отображаемого файла
  DWORD dwMaximumSizeHigh, // размер файла (старшее слово) 
  DWORD dwMaximumSizeLow,  // размер файла (младшее слово) 
  LPCTSTR lpName);         // имя отображенного файла

Через параметр hFile этой функции нужно передать идентификатор файла, для которого будет выполняться отображение в память, или значение 0xFFFFFFFF. В первом случае функция CreateFile Mapping отобразит заданный файл в память, а во втором - создаст отображение с использованием файла виртуальной памяти. Как мы увидим позже, отображение с использованием файла виртуальной памяти удобно для организации передачи данных между процессами.

Обратим ваше внимание на одну потенциальную опасность, связанную с использованием в паре функций CreateFile и CreateFileMapping. Если функция CreateFile завершится с ошибкой и эта ошибка не будет обработана приложением, функция CreateFileMapping получит через параметр hFile значение INVALID_HANDLE_VALUE XE "INVALID_HANDLE_VALUE" , численно равное 0xFFFFFFFF. В этом случае она сделает совсем не то, что предполагал разработчик приложения: вместо того чтобы выполнить отображение файла в память, функция создаст отображение с использованием файла виртуальной памяти.

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

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

 Значение

 Описание

 PAGE_READONLY

 К выделенной области памяти предоставляется доступ только для чтения. При создании или открывании файла необходимо указать флаг GENERIC_READ

 PAGE_READWRITE

 К выделенной области памяти предоставляется доступ для чтения и записи. При создании или открывании файла необходимо указать флаги GENERIC_READ и GENERIC_WRITE

 PAGE_WRITECOPY

 К выделенной области памяти предоставляется доступ для копирования при записи. При создании или открывании файла необходимо указать флаги GENERIC_READ и GENERIC_WRITE. Режим копирования при записи будет описан позже в главе, посвященной обмену данными между процессами

Эти значения можно комбинировать при помощи логической операции ИЛИ со следующими атрибутами:

 Атрибут

 Описание

 SEC_COMMIT

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

 SEC_IMAGE

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

 SEC_NOCACHE

 Отмена кэширования для всех страниц отображаемой области памяти. Должен использоваться вместе с атрибутами SEC_RESERVE или SEC_COMMIT

 SEC_RESERVE

 Если указан этот атрибут, вместо выделения выполняется резервирование страниц виртуальной памяти. Зарезервированные таким образом страницы можно будет получить в пользование при помощи функции VirtualAlloc XE "VirtualAlloc" . Атрибут SEC_RESERVE XE "SEC_RESERVE" можно указывать только в том случае, если в качестве параметра hFile функции CreateFile XE "CreateFile" Mapping XE "CreateFileMapping" передается значение 0xFFFFFFFF

С помощью параметров dwMaximumSizeHigh и dwMaximumSizeLow необходимо указать функции CreateFileMapping 64-разрядный размер файла. Параметр dwMaximumSizeHigh должен содержать старшее 32-разрядное слово размера, а параметр dwMaximumSizeLow - малдшее 32-разрядное слово размера. Для небольших файлов, длина которых укладывается в 32 разряда, нужно указывать нулевое значение параметра dwMaximumSizeHigh.

Заметим, что вы можете указать нулевые значения и для параметра dwMaximumSizeHigh, и для параметра dwMaximumSizeLow. В этом случае предполагается, что размер файла изменяться не будет.

Через параметр lpName можно указать имя отображения, которое будет доступно всем работающим одновременно приложениям. Имя должно представлять собой текстовую строку, закрытую двоичным нулем и не содержащую символов “\”.

Если отображение будет использоваться только одним процессом, вы можете не задавать для него имя. В этом случае значение параметра lpName следует указать как NULL.

В случае успешного завершения функция CreateFileMapping возвращает идентификатор созданного отображения. При ошибке возвращается значение NULL.

Так как имя отображения глобально, возможно возникновение ситуации, когда процесс пытается создать отображение с уже существующим именем. В этом случае функция CreateFileMapping возвращает идентификатор существующего отображения. Такую ситуацию можно определить с помощью функции GetLastError XE "GetLastError" , вызвав ее сразу после функции CreateFileMapping. Функция GetLastError при этом вернет значение ERROR_ALREADY_EXISTS XE "ERROR_ALREADY_EXISTS" .

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

Итак, мы выполнили первые два шага, необходимые для работы с файлом, отображаемым на память, - открывание файла функцией CreateFile и создание отображения функцией CreateFileMapping. Теперь, получив от функции CreateFileMapping идентификатор объекта-отображения, мы должны выполнить само отображение, вызвав для этого функцию MapViewOfFile XE "MapViewOfFile" или MapViewOfFileEx XE "MapViewOfFileEx" . В результате заданный фрагмент отображенного файла будет доступен в адресном пространстве процесса.

Прототип функции MapViewOfFile приведен ниже:


LPVOID MapViewOfFile(
  HANDLE hFileMappingObject,  // идентификатор отображения 
  DWORD dwDesiredAccess,   // режим доступа 
  DWORD dwFileOffsetHigh,  // смещение в файле (старшее слово) 
  DWORD dwFileOffsetLow,   // смещение в файле (младшее слово) 
  DWORD dwNumberOfBytesToMap);// количество отображаемых байт 

Функция MapViewOfFile создает окно размером dwNumberOfBytesToMap байт, которое смещено относительно начала файла на количество байт, заданное параметрами dwFileOffsetHigh и dwFileOffsetLow. Если задать значение параметра dwNumberOfBytesToMap равное нулю, будет выполнено отображение всего файла.

Смещение нужно задавать таким образом, чтобы оно попадало на границу минимального пространства памяти, которое можно зарезервировать. Значение 64 Кбайта подходит в большинстве случаев.

Более точно гранулярность памяти можно определить при помощи функции GetSystemInfo. Этой функции в качестве единственного параметра необходимо передать указатель на структуру типа SYSTEM_INFO, определенную следующим образом:


typedef struct _SYSTEM_INFO 
{
  union {
    DWORD  dwOemId; // зарезервировано
    struct 
    {
      WORD wProcessorArchitecture; // архитектура системы
      WORD wReserved; // зарезервировано
    };
  };
  DWORD  dwPageSize;                  // размер страницы
  LPVOID lpMinimumApplicationAddress; // минимальный адрес,
    // доступный приложениям и библиотекам DLL
  LPVOID lpMaximumApplicationAddress; // максимальный адрес,
    // доступный приложениям и библиотекам DLL
  DWORD  dwActiveProcessorMask;   // маски процессоров
  DWORD  dwNumberOfProcessors;    // количество процессоров
  DWORD  dwProcessorType;         // тип процессора
  DWORD  dwAllocationGranularity; // гранулярность памяти
  WORD   wProcessorLevel;         // уровень процессора
  WORD   wProcessorRevision;      // модификация процессора
} SYSTEM_INFO;

Функция заполнит поля этой структуры различной информацией о системе. В частности, в поле dwAllocationGranularity будет записан минимальный размер резервируемой области памяти.

Вернемся к описанию функции MapViewOfFile.

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

 Значение

 Описание

 FILE_MAP_WRITE

 Доступ на запись и чтение. При создании отображения функции CreateFileMapping необходимо указать тип защиты

 FILE_MAP_READ

 Доступ только на чтение. При создании отображения необходимо указать тип защиты PAGE_READWRITE или PAGE_READ

 FILE_MAP_ALL_ACCESS

 Аналогично FILE_MAP_WRITE

 FILE_MAP_COPY

 Доступ для копирования при записи. При создании отображения необходимо указать атрибут PAGE_WRITECOPY XE "PAGE_WRITECOPY"

В случае успешного выполнения отображения функция MapViewOfFile возвращает адрес отображенной области памяти. При ошибке возвращается значение NULL.

При необходимости приложение может запросить отображение в заранее выделенную область адресного пространства. Для этого следует воспользоваться функцией MapViewOfFile XE "MapViewOfFile" Ex:


LPVOID MapViewOfFileEx(
  HANDLE hFileMappingObject,  // идентификатор отображения 
  DWORD dwDesiredAccess,      // режим доступа 
  DWORD dwFileOffsetHigh,  // смещение в файле (старшее слово) 
  DWORD dwFileOffsetLow,   // смещение в файле (младшее слово) 
  DWORD dwNumberOfBytesToMap, // количество отображаемых байт 
  LPVOID lpBaseAddress);      // предполагаемый адрес 
                              // для отображения файла

Эта функция аналогична только что рассмотренной функции MapViewOfFile за исключением того, что она имеет еще один параметр lpBaseAddress - предполагаемый адрес для выполнения отображения.

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

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

Приложение может создавать несколько отображений для разных или одинаковых фрагментов одного и того же файла.

Открывание отображения

Если несколько процессов используют совместно одно и то же отображение, первый процесс создает это отображение с помощью функции CreateFileMapping, указав имя отображения, а остальные должны открыть его, вызвав функцию OpenFileMapping:


HANDLE OpenFileMapping(
  DWORD   dwDesiredAccess, // режим доступа 
  BOOL    bInheritHandle,  // флаг наследования 
  LPCTSTR lpName);         // адрес имени отображения файла 

Через параметр lpName этой функции следует передать имя открываемого отображения. Имя должно быть задано точно также, как при создании отображения функцией CreateFileMapping.

Параметр dwDesiredAccess определяет требуемый режим доступа к отображению и указывается точно также, как и для описанной выше функции MapViewOfFile.

Параметр bInheritHandle определяет возможность наследования идентификатора отображения. Если он равен TRUE, порожденные процессы могут наследовать идентификатор, если FALSE - то нет.

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

Если созданное отображение больше не нужно, его следует отменить с помощью функции UnmapViewOfFile:


BOOL UnmapViewOfFile(LPVOID lpBaseAddress);

Через единственный параметр этой функции необходимо передать адрес области отображения, полученный от функций MapViewOfFile или MapViewOfFileEx.

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

Если приложение создало несколько отображений для файла, перед завершением работы с файлом все они должны быть отменены с помощью функции UnmapViewOfFile. Далее с помощью функции CloseHandle следует закрыть идентификаторы отображения, полученный от функции CreateFile XE "CreateFile" Mapping XE "CreateFileMapping" и CreateFile.

Принудительная запись измененных данных

Как мы только что сказали, после отмены отображения все измененные страницы памяти записываются в отображаемый файл. Если это потребуется, приложение может в любое время выполнить принудительную запись измененных страниц в файл при помощи функции FlushViewOfFile XE "FlushViewOfFile" :


BOOL FlushViewOfFile(
  LPCVOID lpBaseAddr, // начальный адрес сохраняемой области 
  DWORD dwNumberOfBytesToFlush); // размер области в байтах

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

[Назад] [Содеожание] [Дальше]