6314

Программная реализация средствами ОС Windows

Курсовая

Информатика, кибернетика и программирование

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

Русский

2012-12-31

130.5 KB

13 чел.

Введение

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

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

Проецируемые файлы применяются для: 

  •  загрузки и выполнения EXE- и DLL-файлов Это позволяет существенно экономить как на размере страничного файла, так и на времени, необходимом для подготовки приложения к выполнению; 
  •  доступа к файлу данных, размещенному на диске Это позволяет обойтись без операций файлового ввода-вывода и буферизации его содержимого;
  •  разделения данных между несколькими процессами, выполняемыми па одной машине (В Windows есть и другие методы для совместного доступа разных процессов к одним данным — но все они так или иначе реализованы на основе проецируемых в память файлов.)


Раздел 1. Теоретическая часть

Файлы данных, проецируемые в память

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

 

Метод 1: один файл, один буфер

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

Этот довольно простой в реализации метод имеет два существенных недостатка

Во-первых, придется выделить блок памяти такого же размера, что и файл. Это терпимо, если файл небольшой. А если он занимает 2 Гб? Система просто не позволит приложению передать такой объем физической памяти. Значит, к большим файлам нужен совершенно иной подход.

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

Метод 2: два файла, один буфер

Открываем существующий файл и создаем на диске новый — нулевой длины. Затем выделяем небольшой внутренний буфер размером, скажем, 8 Кб. Устанавливаем указатель файла в позицию 8 Кб от конца, считываем в буфер последние 8 Кб содержимого файла, меняем в нем порядок следования байтов на обратный и переписываем буфер в только что созданный файл. Повторяем эти операции, пока нс дойдем до начала исходного файла, Конечно, если длина файла не будет кратна 8 Кб, операции придется немного усложнить, но это не страшно. Закончив обработку, закрываем оба файла и удаляем исходный файл.

Этот метод посложнее первого, зато позволяет гораздо эффективнее использовать память, так как требует выделения лишь 8 Кб, Но и здесь не без проблем, и вот две главных.. Во-первых, обработка идет медленнее, чем при первом методе: на каждой итерации перед считыванием приходится находить нужный фрагмент исходного файла. Во-вторых, может понадобиться огромное пространство па жестком диске. Если длина исходного файла 400 Мб, новый файл постепенно вырастет до этой вели чины, и перед самым удалением исходного файла будет занято 800 Мб, т. e. на 400 Мб больше, чем следовало бы. Так что все пути ведут... к третьему методу

Метод 3: один файл, два буфера

Программа инициализирует два раздельных буфера, допустим, по 8 Кб и считывает первые 8 Кб файла в один буфер, а последние 8 Кб — в другой. Далее содержимое обоих буферов обменивается в обратном порядке и первый буфер записывается в конец, а второй — в начало того же файла. На каждой итерации программа перемещает восьмикилобайтовые блоки из одной половины файла в другую. Разумеется, нужно предусмотреть какую-то обработку на случай, если длина файла не кратна 16 Кб, и эта обработка будет куда сложнее, чем в предыдущем методе. Но разве это испугает опытного программиста?

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

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

Метод 4: один файл и никаких буферов

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

Использование проецируемых в память файлов

Для этого нужно выполнить три операции:

1. Создать или открыть объект ядра "файл", идентифицирующий дисковый файл, который Вы хотите использовать как проецируемый в память.

2. Создать объект ядра "проекция файла", чтобы сообщить системе размер файла и способ доступа к нему.

3. Указать системе, как спроецировать в адресное пространство Вашего процесса объект «проекция файла» — целиком или частично.

Закончив работу с проецируемым в память файлом, следует выполнить тоже три операции:

1. Сообщить системе об отмене проецирования на адресное пространство процесса объекта ядра "проекция файла".

2. Закрыть этот объект.

3. Закрыть объект ядра "файл".

Этап1: создание или открытие объекта ядра «файл»

Для этого Вы должны применять только функцию CreateFile

HANDLE CreateFile( PCSTR pszFileName, DWORD dwDesiredAccess, DWORD dwShareMode, PSECURITY_AIIRIBUTES psa, DWORD dwCreationDisposition, DWORD dwFlagsAndAttribules, HANDLE hTemplateFile);

Как видите, у функции CrealeFile довольно много параметров. Здесь я сосредото чусь только на первых трех: pszFileName, dwDesiredAccess и dwSbareMode.

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

Значение

Описание

0

Содержимое файла нельзя считывать или записывать, указывайте это значение, если Вы хотите всею лишь получить атрибуты файла

GENERIC _READ

Чтение файла разрешено

GENERIC_WRITE

Запись в файл разрешена

GENERIC_READ | ENERIC_WRITE

Разрешено и то и другое

Создавая или открывая файл данных с намерением использовать его в качестве проецируемого в память, можно установить либо флаг GENERIC_READ (только для чтения), либо комбинированный флаг GENERIC_READ | GENУRIC_WRITE (чтение/запись)

Третий параметр, dwShareMode, указывает тип совместного доступа к данному файлу(см следующую таблицу)

Значение

Описание

0

Другие попытки открыть файл закончатся неудачно

FILE_SHARE_REAU

Попытка постороннего процесса открыть файл с флагом GENERIC_WRITE не удается

FILE_SHARF_WRlTE

Попытка постороннего процесса открыть файл с флагом GENERIC_READ не удается

FILE SHARE RFAD | FILE_SHARE_WRTTE

Посторонний процесс может открывать файл без ограничений

 

Создав или открыв указанный файл, CreateFile возвращает его описатель, в ином случае — идентификатор INVALID_HANDLE_VALUE

Этап 2: создание объекта ядра «проекция файла»

Вызвав CreateFile, Вы указали операционной системе, где находится физическая па мять для проекции файла на жестком диске в сети, на CD-ROM или в другом месте Теперь сообщите системе, какой обьем физической памяти нужен проекции файла Для этого вызовите функцию CreateFileMapping

HANDLE CreateFileMapping( HANDLE hFile, PSECURITY_ATTRIBUTES psa, DWORD fdwProtect, DWOPD dwMaximumSizeHigh, DWORD dwMaximumSizcLow, PCSTR pszName);

Первый параметр, hFile, идентифицирует описатель файла, проецируемою на адресное пространство процесса этот описатель Вы получили после вызова CreateFile Параметр psa — указатель на структуру SECURITY_ATTRIBUTES, которая относится к обьекту ядра "проекция файла", для установки защиты по умолчанию ему присваивается NULL

Как я уже говорил в начале этой главы, создание файла, проецируемого в память, аналогично резервированию региона адресного пространства с последующей передачей ему физической памяти Разница лишь в том, что физическая память для проецируемого файла — сам файл на диске, и для него не нужно выделять пространство в страничном файле. При создании объекта «проекция файла» система не резервирует регион адресного пространства и не увязывает его с физической памятью из файла (как это сделать, я расскажу в следующем разделе). Но, как только дело дойдет до отображения физической памяти на адресное пространство процесса, системе понадобится точно знать атрибут защиты, присваиваемый страницам физической памяти Поэтому в fdwProteсе надо указать желательные атрибуты защиты. Обычно ис пользуется один из перечисленных в следующей таблице.

Атрибут защиты

Описание

PAGE_READONLY

Отобразив объект «проекция файла» на адресное пространство, можно считывать данные из файла. При этом Вы должны были передать в CreateFile флаг GENERIC_READ.

PAGE_READWRITE

Отобразив объект «проекция файла» на адресное пространство, можно считывать данные из файла и записывать их. При этом Вы должны были передать в CreateFile комбинацию флагов GENERIC_READ | GENERIC_WRITE.

PAGE_WRITECOPY

Отобразив объект "проекция файла" на адресное пространство, можно считывать данные из файла и записывать их. Запись приведет к созданию закрытой копии страницы При этом Вы должны были передать в CreateFile либо GENERIC_READ, либо GENERIC_READ | GENERIC_WRITE

Кроме рассмотренных выше атрибутов защиты страницы, существует еще и четыре атрибута раздела; их можно ввести в параметр fdwProtect функции CreateFile Mapping побитовой операцией OR. Раздел (section) — всего лишь еще одно название проекции памяти.

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

Второй атрибут, SEC_IMAGE, указывает системе, что данный файл является пере носимым исполняемым файлом (portable executable, PE). Отображая его на адресное пространство процесса, система просматривает содержимое файла, чтобы определить, какие атрибуты защиты следует присвоить различным страницам проецируемого образа (mapped image). Например, раздел кода РЕ-файла (text) обычно проецируется с атрибутом PAGE_EXECUTE_READ, тогда как раздел данных этого же файла (.data) — с атрибутом PAGE_READWRITE Атрибут SEC_IMAGE заставляет систему спроецировать образ файла и автоматически подобрать подходящие атрибуты защиты страниц.

Последние два атрибута (SEC_RESERVE и SEC_COMMIT) взаимоисключают друг друга и неприменимы для проецирования в память файла данных. Эти флаги мы рас смотрим ближе к концу главы. CreateFileMapping их игнорирует.

Следующие два параметра этой функции (dwMaximumSizeHigh и dwMaximum SizeLow) самые важные. Основное назначение CreateFileMapping — гарантировать, что объекту "проекция файла" доступен нужный объем физической памяти Через эти параметры мы сообщаем системе максимальный размер файла в байтах. Так как Windows позволяет работать с файлами, размеры которых выражаются 64-разрядными числами, в параметре dwMaximumSizeHigh указываются старшие 32 бита, а в dwMaxi mumSizeI.ow - младшие 32 бита этого значения. Для файлов размером менее 4 Гб dwMaximumSizeHigh всегда равен 0. Наличие 64-разрядного значения подразумевает, что Windows способна обрабатывать файлы длиной до l6 экзабайтов.

Для создания объекта «проекция файла" таким, чтобы он отражал текущий раз мер файла, передайте в обоих параметрах нули. Так же следует поступить, если Вы собираетесь ограничиться считыванием или как-то обработать файл, не меняя его раз мер Для дозаписи данных в файл выбирайте его размер максимальным, чтобы оста вить пространство «для маневра» Если в данный момент файл на диске имеет нуле вую длину, в параметрах dwMaximumSizeHigh и dwMaximumSizeLow нельзя передавать нули Иначе система решит, что Вам нужна проекция файла с объемом памяти, равным 0. А это ошибка, и CreateFileMapping вернет NULL

Если Вы еще следите за моими рассуждениями, то, должно быть, подумали: что-то тут нс все ладно. Очень, конечно, мило, что Windows поддерживает файлы и их про екции размером вплоть до 16 экзабайтов, но как, интересно, спроецировать такой файл на адресное пространство 32-разрядного процесса, ограниченное 4 Гб, из которых и использовать-то можно только 2 Гб? На этот вопрос я отвечу в следующем разделе. (Конечно, адресное пространство 64-разрядного процесса, размер которого составляет 16 экзабайтов, позволяет работать с еще большими проекциями файлов, но аналогичное ограничение существует и там)

Вызов CreateFileMapping с флагом PAGE_READWRITE заставляет систему проверять, чтобы размер соответствующего файла данных на диске был не меньше, чем указано в параметрах dwMaximumSizeHigh и dwMaximumSizeLow. Если файл окажется меньше заданного, CreateFileMapping увеличит его размер до указанной величины. Это делается специально, чтобы выделить физическую память перед использованием файла в качестве проецируемого в память. Если объект "проекция файла" создан с флагом PAGE_READONLY или PAGE_WRITECOPY, то размер, переданный функции CreateFileMapping, не должен превышать физический размер файла на диске (так как Вы не сможете что-то дописать в файл).

Последний параметр функции CreateFileMapping — pszName — строка с нулевым байтом в конце; в ней указывается имя объекта "проекция файла", которое используется для доступа к данному объекту из другого процесса. Но обычно совместное использование проецируемого в память файла не требуется, и поэтому в данном параметре передают NULL.

Система создает объект «проекция файла» и возвращает его описатель в вызвавший функцию поток. Если объект создать не удалось, возвращается нулевой описатель (NULL). И здесь еще раз обратите внимание на отличительную особенность функции CreateFile — при ошибке она возвращает не NULL, а идентификатор INVALID_ HANDLE_VALlJE (определенный как - 1).

Этап 3: проецирование файловых данных на адресное пространство процесса

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

PVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap);

Параметр hFileMappingObject идентифицирует описатель объекта "проекция файла", возвращаемый предшествующим вызовом либо CreateFtleMapping, либо OpenFileMapping (ее мы рассмотрим чуть позже) Параметр dwDesiredAccess идентифицирует вид доступа к данным. Bce правильно придется опять указывать, как именно мы хотим обращаться к файловым данным Можно задать одно из четырех значений, описанных в следующей таблице.

Значение

Описание

FILE_MAP_WRITE

Файловые данные можно считывать и записывать, Вы должны были передать функции CreateFileMapping атрибут PAGE_READWRITE

FILE MAF_READ

Файловые данные можно только считывать Вы должны были вызвать CreateFileMapping с любым из следующих атрибутов PAGE_READONLY, PAGE_READWRITE или PAGE_WRITECOPY

FILE_MAP_ALL_ACCESS

То же, что и FILE_MAP_WRITE

FILE_MAP_COPY

Файловые данные можно считывать и записывать, но запись приводит к созданию закрытой копии страницы Вы должны были вызвать CrealeFileMapping с любым из следующих атрибутов PAGE_READONIY, PAGE_READWRITE или РАСЕ_WRITECOPY (Windows 98 требует вызывать CreateFileMapping с атрибутом PACE_WRITECOPY)

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

Остальные три параметра относятся к резервированию региона адресного пространства и к отображению на него физической памяти При этом необязательно проецировать на адресное пространство весь файл сразу Напротив, можно спроецировать лишь малую его часть, которая в таком случае называется представлением (view) — теперь-то Вам, наверное, понятно, откуда произошло название функции MapViewOfFile

Проецируя на адресное пространство процесса представление файла, нужно сделать две вещи Во-первых, сообщить системе, какой байт файла данных считать в представлении первым Для этого предназначены параметры dwFileOffsetHigh и dwFileOffsetLow. Поскольку Windows поддерживает файлы длиной до 16 экзабайтов, приходится определять смещение в файле как 64 разрядное число старшие 32 бита пере даются в параметре dwFileOffsetHigh, а младшие 32 бита — в параметре dwFileOffsetLow Заметьте, что смещение в файле должно быть кратно гранулярности выделения памяти в данной системе (В настоящее время во всех реализациях Windows она составляет 64 Кб).

Во-вторых, от Baс потребуется указать размер представления, т.e. сколько байтов файла данных должно быть спроецировано на адресное пространство Это равносильно тому, как если бы Вы задали размер региона, резервируемого в адресном пространстве Размер указывается в параметре dwNumberOfBytesToMap Если этот параметр равен 0, система попытается спроецировать представление, начиная с указанного смещения и до конца файла.

Если при вызове MapViewOfFile указан флаг FILE_MAP_COPY, система передает физическую память из страничного файла. Размер передаваемого пространства определяется параметром dwNumberOfBytesToMap. Пока Вы лишь считываете данные из представления файла, страницы, переданные из страничного файла, не используются. Но стоит какому-нибудь потоку в Вашем процессе совершить попытку записи по адресу, попадающему в границы представления файла, как система тут же берет из страничного файла одну из переданных страниц, копирует на нее исходные данные и проецирует ее на адресное пространство процесса. Так что с этого момента потоки Вашего процесса начинают обращаться к локальной копии данных и теряют доступ к исходным данным.

Создав копию исходной страницы, система меняет ее атрибут защиты с PAGE_WRI TECOPY на PAGE_READWRITE. Рассмотрим пример приложение А.

Этап 4: отключение файла данных от адресного пространства процесса

Когда необходимость в данных файла (спроецированного на регион адресного пространства процесса) отпадет, освободите регион вызовом:

BOOL UnmapViewOfFile(PVOID pvBaseAddress);

Ее единственный параметр, pvBaseAddress, указывает базовый адрес возвращаемо го системе региона. Он должен совпадать со значением, полученным после вызова MapViewOfFile. Вы обязаны вызывать функцию UnmapViewOfFile. Если Вы не сделаете этoгo, регион не освободится до завершения Вашего процесса. И еще: повторный вызов MapVietvOfFile приводит к резервированию нового региона в пределах адресного пространства процесса, но ранее выделенные регионы не освобождаются.

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

BOOL FlushViewOfFile( PVOID pvAddress, SIZE_T dwNuuiberOfBytesToFlush);

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

В случае проецируемых файлов, физическая память которых расположена на сетевом диске, FlushViewOfFile гарантирует, что файловые данные будут перекачаны с рабочей станции. Но она не гарантирует, что сервер, обеспечивающий доступ к этому файлу, запишет данные на удаленный диск, так как он может просто кэшировать их. Для подстраховки при создании объекта «проекция файла» и последующем проецировании его представления используйте флаг FILE_FLAG_WRITE_THROUGH. При открытии файла с этим флагом функция FlushViewOfFile вернет управление только после сохранения на диске сервера всех файловых данных.

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

Поэтому о сохранении измененных данных придется заботиться самостоятельно. Например, для уже спроецированного файла можно создать еще один объект «проекция файла» с атрибутом PAGE_READWRITE и спроецировать его представление на адресное пространство процесса с флагом FILE_MAP_WRITE. Затем просмотреть пер вое представление, отыскивая страницы с атрибутом PAGE_READWRITE. Найдя страницу с таким атрибутом. Вы анализируете ее содержимое и решаете: записывать ее или нет Если обновлять файл не нужно, Вы продолжаете просмотр страниц. А для сохранения страницы с измененными данными достаточно вызвать MoveMemory и скопировать страницу из первого представления файла во второе. Поскольку второе представление создано с атрибутом PAGE_READWRITE, функция MoveMemory обновит содержимое дискового файла. Так что этот метод вполне пригоден для анализа изменений и сохранения их в файле.

Этапы 5 и 6: закрытие объектов «проекция файла» и «файл»

Закончив работу с любым открытым Вами объектом ядра, Вы должны его закрыть, иначе в процессе начнется утечка ресурсов. Конечно, по завершении процесса система автоматически закроет объекты, оставленные открытыми Но, если процесс по работает еще какое-то время, может накопиться слишком много незакрытых описателей. Поэтому старайтесь придерживаться правил хорошего тона и пишите код так, чтобы открытые объекты всегда закрывались, как только они станут не нужны. Для закрытия объектов «проекция файла» и «файл» дважды вызовите функцию CloseHandle. Рассмотрим это подробнее на фрагменте псевдокода.

HANDLE hFile = CreateFile(...);

HANDLE hFileMapping = CreateFileMapping(hFile,...);

PVOID pvFilfi = MapViewOfFile(hFileMapping, );

// работаем с файлом, спроецированным в память

UnmapViewOfFile(pvFile);

CloseHandle(hFileMapping);

CloseHandle(hFile);

Этот фрагмент иллюстрирует стандартный метод управления проецируемыми файлами. Но он не отражает того факта, что при вызове MapViewOfFile система увеличивает счетчики числа пользователей объектов «файл» и "проекция файла". Этот побочный эффект весьма важен, так как позволяет переписать показанный выше фрагмент кода следующим образом:

HANDLE hFile = CreateFile( . );

HANDLE hFileMapping = CreateFileMapping(hFile, );

CloseHandle(hFile);

PVOID pvFile = MapViewOfFile(hFileMapping,...);

CloseHandle(hFileMapping);

// работаем с файлом, спроецированным в память

UnmapViewOfFile(pvFile);

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

Если Вы будете создавать из одного файла несколько объектов "проекция файла" или проецировать несколько представлений этого объекта, применить функцию CloseHandle в начале кода не удается — описатели еще понадобятся Вам для дополни тельных вызовов CreateFileMapping и MapViewOfFile

Обработка больших файлов

Я обещал рассказать, как спроецировать на небольшое адресное пространство файл длиной 16 экзабайтов. Так вот, этого сделать нельзя Вам придется проецировать не весь файл, а его представление, содержащее лишь некую часть данных Вы начнете с того, что спроецируете представление самого начала файла Закончив обработку данных в этом представлении, Вы отключите его и спроецируете представление следую щей части файла — и так до тех пор, пока нс будет обработан весь файл Конечно, это делает работу с большими файлами, проецируемыми в память, не слишком удобной, но утешимся тем, что длина большинства файлов достаточно мала.

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

Этот алгоритм проецирует представления по 64 Кб (в соответствии с гранулярностью выделения памяти) или менее. Кроме того, функция MapViewOfFile требует, чтобы передаваемое ей смещение в файле тоже было кратно гранулярности выделения памяти. Подпрограмма проецирует на адресное пространство сначала одно представление, подсчитывает в нем количество нулей, затем переходит к другому представлению, и все повторяется. Спроецировав и просмотрев все 64-килобайтовые блоки, подпрограмма закрывает объект «проекция файла».

Проецируемые файлы и когерентность

Система позволяет проецировать сразу несколько представлений одних и тех же файловых данных. Например, можно спроецировать в одно представление первые 10 Кб файла, а затем — первые 4 Кб того же файла в другое представление Пока Вы проецируете один и тот же объект, система гарантирует когерентность (согласованность) отображаемых данных. Скажем, если программа изменяет содержимое файла в одном представлении, это приводит к обновлению данных и в другом. Так происходит потому, что система, несмотря па многократную проекцию страницы на виртуальное адресное пространство процесса, хранит данные на единственной странице оперативной памяти. Поэтому, если представления одного и того же файла данных создаются сразу несколькими процессами, данные по-прежнему сохраняют когерентность — ведь они сопоставлены только с одним экземпляром каждой страницы в оперативной памяти. Bcе это равносильно тому, как если бы страницы оперативной памяти были спроецированы на адресные пространства нескольких процессов одновременно.

Кстати, функция CreateFile позволяет Вашему процессу открывать файл, проецируемый в память другим процессом После этого Ваш процесс сможет считывать или записывать данные в файл (с помощью функций ReadFite или WriteFile), Разумеется, при вызовах упомянутых функций Ваш процесс будет считывать или записывать данные не в файл, а в некий буфер памяти, который должен быть создан именно этим процессом; буфер не имеет никакого отношения к участку памяти, используемому для проецирования данного файла. Но надо учитывать, что, когда два приложения открывают один файл, могут возникнуть проблемы. Дело в том, что один процесс может вызвать ReadFile, считать фрагмент файла, модифицировать данные и записать их обратно в файл с помощью WriteFile, а объект «проекция файла», принадлежащий второму процессу, ничего об этом не узнает. Поэтому, вызывая для проецируемого файла функцию CreateFile, всегда указывайте нуль в параметре dwShareMode. Тем самым Вы сообщите системе, что Вам нужен монопольный доступ к файлу и никакой посторонний процесс не должен его открывать.

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


Раздел 2. Практическая часть

Задание

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

Код программы

#include <stdio.h>

#include <process.h>

#include <windows.h>

#include <string.h>

int main(int argc, char **argv)

{

int         b,i,a,j,k;

int   blocks=0,block;

int         result,error;

HANDLE      hMapFile, hFile;

char        *map_data, *map_data1,buf;

DWORD dwFileSizeHigh,qwFileSize;

DWORD dwBytesInBlock,dwBytesInBlock1 ;

unsigned long long qwFileOffset,qwFileOffset1,qwFileSize64,qwFileSize64_,appendicitis,appendicitis1;

SYSTEM_INFO sinf;

   GetSystemInfo(&sinf);

hFile = CreateFile("qwerty.doc", GENERIC_READ | GENERIC_WRITE, 0,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

hMapFile = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);

if (hMapFile != NULL && GetLastError() == ERROR_ALREADY_EXISTS) {

 printf("FileMapping handle %p exist.\n", hMapFile);

 CloseHandle(hMapFile);

 return;

}

if(hMapFile == NULL) {

 printf("FileMapping handle not created.\n");

 return;

}

qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);

// printf("qwFileSize= %d \n",qwFileSize);

qwFileSize64 = qwFileSize + (( (unsigned long long)dwFileSizeHigh) << 32);

printf("qwFileSize= %d\n",qwFileSize);

qwFileOffset=0;

//printf("qwFileOffset1= %d qwFileSize64= %d\n",qwFileOffset1,qwFileSize64);

qwFileSize64_=qwFileSize64/2;

//printf("qwFileOffset1= %d\n", qwFileOffset1);

block=qwFileSize64/65536;

blocks=qwFileSize64_/65536;

appendicitis=qwFileSize64_-blocks*65536;

appendicitis1=65536-appendicitis;

printf("blocks= %d\n",appendicitis);

qwFileOffset1=65536*blocks;

dwBytesInBlock =64*1024 ;

dwBytesInBlock1=64*1024;

a=1;b=1;

for(k=0;k<=block;k++)

//while(qwFileSize64_>0)

{

if (a==1)

{

 if (qwFileSize64_< 64*1024)

 {

  dwBytesInBlock = (DWORD)qwFileSize64_;

 }

 printf("dwBytesInBlock= %d   ",dwBytesInBlock);

 map_data = (char *) MapViewOfFile(hMapFile, FILE_MAP_WRITE, (DWORD) (qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF),  dwBytesInBlock);

 qwFileOffset += dwBytesInBlock;

}

if (b==1)

{

 if (qwFileSize64_< 64*1024)

 {

  dwBytesInBlock1 = (DWORD)qwFileSize64_;

 }

 printf("dwBytesInBlock1= %d   ",dwBytesInBlock1);

 map_data1 = (char *) MapViewOfFile(hMapFile, FILE_MAP_WRITE, (DWORD) (qwFileOffset1 >> 32), (DWORD)(qwFileOffset1 & 0xFFFFFFFF),  dwBytesInBlock1);

 qwFileOffset1 += dwBytesInBlock1;

}

printf("qwFileSize64_= %d\n",qwFileSize64_);

if( map_data == NULL || map_data1 == NULL )

{

  error=GetLastError();

  printf("Can't lock mapzone.\n");

  printf("%d",error);

 return;

}

if (a==1)

{

 j=appendicitis;

 for (i=0;i<appendicitis1-1;i++)

 {

  buf=map_data[i];

  map_data[i]=map_data1[j];

  map_data1[j]=buf;

  j++;

 }

 a=0;b=1;

 UnmapViewOfFile(map_data1);

 qwFileSize64_ -= i;

 //printf("j= %d  i= %d               ",j,i);

}

else

{

 j=0;

 for (i=appendicitis1;i<dwBytesInBlock-1;i++)

 {

  buf=map_data[i];

  map_data[i]=map_data1[j];

  map_data1[j]=buf;

  j++;

 }

 a=1;b=0;

 UnmapViewOfFile(map_data);

 qwFileSize64_ -= j;

 //printf("j= %d  i= %d\n",j,i);

}

 

}

CloseHandle(hMapFile);

CloseHandle(hFile);

return(0);

}


Заключение

Файл, спроецированный на память - это механизм Win32 API, позволяющий любой открытый файл рассматривать как блок памяти с произвольным доступом (все записи и чтения байта по адресу, лежащему в пределах такого блока, приводят к изменению соотв. байта в файле). Это может быть очень удобно, когда необходимо работать с файлами очень большого объема: вместо того, чтобы читать всё содержимое файла в память, можно спроецировать файл на память и сразу прочитать или модифицировать нужный фрагмент памяти. Windows подгружает спроецированный файл постранично, по мере обращения к тем или иным адресам памяти; всё делается автоматически, на уровне механизма виртуальной памяти Win32, что избавляет от необходимости вручную следить за подгрузкой из файла или записью в файл ненужных участков памяти.


Список использованных источников

1. http://web.znu.edu.ua/bdp/index.htm

2. http://www.wasm.ru

3. http://www.vsokovikov.narod.ru/


Приложение А

// открываем файл, который мы собираемся спроецировать

HANDLE hFile = CreaTeFile(pszFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

// создаем для файла объект "проекция файла"

HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_WRITECOPY, 0, 0, NULL);

// Проецируем представление файла с атрибутом "копирование при записи";

// система передаст столько физической памяти из страничного файла,

// сколько нужно для размещения всего файла. Первоначально все страницы

// в представлении получат атрибут PAGE_WRITECOPY.

PBYTE pbFile = (PBYTE) MapViewOfFile (hFileMapping, FILE_MAP_COPY, 0, 0, 0);

// считываем байт из представления файла

BYTE bSomeByte = pbFile[0];

// при чтении система не трогает страницы, переданные из страничного файла;

// страница сохраняет свой атрибут PAGE_WRITECOPY

// записываем байт в представление файла

pbFile[0] = 0;

// При первой записи система берет страницу, переданную из страничного файла,

// копирует исходное содержимое страницы, расположенной по запрашиваемому адресу

// в памяти, и проецирует новую страницу (копию) на адресное пространство процесса.

// Новая страница получает атрибут PAGE_READWRITE.

// записываем еще один байт в представление файла

pbFile[1] = 0;

// поскольку теперь байт располагается на странице с атрибутом PAGE_RFADWRITE,

// система просто записывает его на эту страницу (она связана со страничным файлом)

// закончив работу с представлением проецируемого файла, прекращаем проецирование;

// функция UnmapViewOfFile обсуждается в следующем разделе

UnmapViewOfFile(pbFile);

// вся физическая память, взятая из страничного файпа, возвращается системе;

// все, что было записано на эти страницы, теряется

CloseHandle(hFileMapping);

CloseHandle(hFile);


Приложение В

__int64 CountOs(void)

{

// начальные границы представлений всегда начинаются no адресам,

// кратным гранулярности выделения памяти

SYSTEM_INFO sinf;

GetSystemInfo(&sinf);

// открываем файл данных

HANOLE hFile = CreateFile( "С:\\HugeFile.Big , GENERIC_READ, FILE_SHARE_READ NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL SCAN, NULL);

// создаем объект проекция файла

HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);

DWORD dwFileSizeHigh;

__int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);

qwFileSize += (((__int64) dwFileSizeHigh) << 32);

// доступ к описателю объекта файл нам больше не нужен

CloseHandle(hFile);

__int64 qwFileOffset = 0;

qwNumOfOs = 0;

while (qwFileSize > 0)

{

// определяем, сколько байтов надо спроецировать

DWORD dwBytesInBlock = sinf.dwAllocationGranularity;

if (qwFileSize < sinf.dwAllocationGranularity)

dwBytesInBlock = (DWORD)qwFileSize;

PBYTE pbFile = (PBYTE)MapViewOfFile(hFileMapping, FILE_MAP_READ, (DWORD) (qwFileOffset >> 32), // начальный байт (DWORD) (qwFileOffset & 0xFFFFFFFF), // в файле dwBytesInBlock); // число проецируемых байтов

// подсчитываем количество нулевых байтов в этом блоке

for (DWORD dwByte = 0; dwByte < dwBytesInBlock; dwByte++)

{

if (pbFilfe[dwByte] == 0)

qwNumOfOs++;

}

// прекращаем проецирование представления, чтобы в адресном пространстве

// не образовалось несколько представлений одного файла

UnmapViewOfFiie(pbFile);

// переходим к следующей группе байтов в файле

qwFileOffset += dwBytesInBlock;

qwFileSize -= dwBytesInBlock;

}

CloseHandle(hFileMapping);

return(qwNumOfOs);

}


 

А также другие работы, которые могут Вас заинтересовать

63902. Эмоциональные реакции, маркирующие трансгрессивный переход 36 KB
  Случается ли в бытии человека такая настроенность которая подводит его к самому Ничто и отвечает Ужас приоткрывает Ничто Но действительно ли это настроенность подводит человека к Ничто или всё же ситуация столкновения с Ничто вызывает эту настроенность Мы всё же возьмём ужас...
63903. Трансгрессия глобальной системы международно-правового регулирования 65.5 KB
  Основой глобального права может быть только международное право которое уже приобрело некоторые черты универсального регулятора. Глобализация это состояние мировой экономической системы охватывающей весь спектр человеческой деятельности происходящей от углубленной...
63904. Экономические условия трансформации социокультурного пространства. Доверие в экономике: миф или реальность 63 KB
  Выделяя общественные индивидуальные и самобытные отношения автор рассматривает доверие как феномен изначально характерный для индивидуальных отношений. Однако сегодня доверие становится неотъемлемой частью экономики так как с одной стороны наличие доверия...
63905. Эгоизм как общественная проблема в период социально-культурных трансформаций 25.51 KB
  Проблема эгоистичного устройства общества стояла перед человечеством с возникновением первых цивилизаций. Для нашего рассуждения мы выбирали именно эти произведения так как они относятся к одной и той же эпохе описывают одну и ту же социальную реальность...
63906. Гендерные стереотипы в трансформационном аспекте 44 KB
  Стереотипы плотно окутали общество они сопровождают нас во всех сферах нашей жизни и представляют собой более или менее непротиворечивую картину мира. Так уж сложилось что такие стереотипы в обществе существовали и будут существовать всегда.
63907. Теоретико-методологические проблемы социального контроля 16.08 KB
  Для современной России изучение социального контроля и его различных интерпретаций в социологическом знании является насущной задачей в силу ряда причин: страна находится на стадии трансформации складывается ряд новых структур функционирование которых невозможно обеспечить...
63908. Экстремизм в молодежной среде: региональный аспект 48 KB
  По национальному составу: большинство респондентов 663 идентифицируют себя с русскими 87 респондентов указали что являются бурятами 14 татарами. Выявлено следующее: 45 респондентов считают что знаю что такое экстремизм еще 415 полагают что знаю но не уверены правильно ли.
63909. Межпоколенческая преемственность и социокультурный раскол в современной России 45.5 KB
  На мой взгляд эта фраза как бы подчеркивает то что одно поколение не способно перенять все ценности взгляды правила другого поколения. Как правило между поколениями есть возрастная разница в 2025 лет. Ведь несмотря на то что преемственность между поколениями не бывает...
63910. К вопросу о личностной и социальной идентификации современных студентов 241 KB
  Одной из наиболее дискутируемых и актуальных проблем в социологии философии и психологии на сегодняшний день занимает проблема социальной и личностной идентификации. Особенно интересной на наш взгляд является проблема выявления особенностей и технологий взаимовлияния социальной и личностной...