Открыть Электронные книги
Категории
Открыть Аудиокниги
Категории
Открыть Журналы
Категории
Открыть Документы
Категории
1. Резервирует регион адресного пространства - такой, чтобы в него мог поме ститься
заданный DLL-файл Желательное расположение этого региона указы вается внутри
самого DLL-файла. По умолчанию Microsoft Visual C++ присваи вает DLL-
модулям базовый адрес 0x10000000 (в 64-разрядной DLL под управ лением 64-
разрядной Windows 2000 этот адрес может быть другим). При ком поновке DLL это
значение можно изменить с помощью параметра /BASE. У всех стандартных
системных DLL, поставляемых с Windows, разные базовые здре ca, чтобы не
допустить их перекрытия при загрузке в одно адресное простран ство
2. Если зарезервировать регион по желательному для DLL базовому адресу не
удается (из-за того, что он слишком мал либо занят каким-то еще EXE- или DLL
файлом), система пытается найти другой регион. Но по двум причинам такая
ситуация весьма неприятна. Во-первых, если в DLL нет информации о возмож ной
переадресации (relocation information), загрузка может вообще не полу читься.
(Такую информацию можно удалить из DLL при компоновке с парамет ром
/FIXED. Это уменьшит размер DLL-файла, но тогда модуль должен грузить ся
только по указанному базовому адресу) Во-вторых, системе приходится выполнять
модификацию адресов (relocations) внутри DLL. В Windowы 98 эта операция
осуществляется по мере подкачки сграниц в оперативную память. Но в Windows
2000 на это уходит дополнительная физическая память, выделяе мая из
страничного файла, да и загрузка такой DLL займет больше времени.
3. Отмечает, что физическая память, связанная с зарезервированным регионом, —
DLL-файл на диске, а не страничный файл. Если Windows 2000 пришлось вы
полнять модификацию адресов из-за того, что DLL не удалось загрузить по
желательному базовому адресу, она запоминает, что часть физической памяти для
DLL связана со страничным файлом.
Если Вы запустите второй экземпляр, оба диалоговых окна сообщат, что теперь
выполняется два экземпляра.
Выбрав имя файла и щелкнув кнопку Reverse File Contents, Вы активизируете фун
кцию, которая меняет порядок символов в файле на обратный, Программа коррект но
работает только с текстовыми файлами. В какой кодировке создан текстовый файл (ANSI
или Unicode), FileRev определяет вызовом IsTextUnicode (см. главу 2).
WINDOWS 98
В Windows 98 функция IsTextUnitode определена, но не реализована, она про сто
возвращает FALSE, а последующий вызов GetLastError дает ERROR_CALL_
NOT_IMPLEMENTED. Это значит, что программа FileRev, выполняемая в Win dows 98,
всегда считает, что файл содержит текст в ANSI-кодировке.
После щелчка кнопки Reverse File Contents программа создает копию файла с
именем FileRev.dat, Делается это для того, чтобы не испортить исходный файл, изме нив
порядок следования байтов на обратный. Далее программа вызывает
функцию FileReverse — она меняет порядок байтов на обратный и после этого
вызывает Create File, открывая FileRev.dat для чтения и записи
Как я уже говорил, простейший способ «перевернуть* содержимое файла — выз
вать функцию _strrev из библиотеки С. Но для этого последний символ в строке дол жен
быть нулевой. И поскольку текстовые файлы не заканчиваются нулевым симво лом,
программа FileRev подписывает его в конец файла. Для этого сначала вызывает ся
функция GetFileSize:
dwFileSize = GetFileSize(hFile, NULL);
Теперь, вооружившись знанием длины файла, можно создать объект "проекция
файла", вызвав CreateFileMapping. При этом размер объекта равен dwFileSize плюс размер
«широкого» символа, чтобы учесть дополнительный нулевой символ в конце файла.
Создав объект "проекция файла", программа проецирует на свое адресное пространство
представление этого объекта. Переменная pvFile содержит значение, возвращенное
функцией MapViewOfFile, и указывает на первый байт текстового файла.
Следующий шаг — запись нулевого символа в конец файла и реверсия строки:
PSTR pchANSI = (PSFR) pvFile;
pchANSI[dwFileSize / sizeof(CHAR)] = 0;
// "переворачиваем" содержимое файла
_strrev(pchANSI);
В текстовом файле каждая строка завершается символами возврата каретки ("\r") и
перевода строки ('\n') К сожалению, после вызова функции _strrev эти символы тоже
меняются местами. Поэтому для загрузки преобразованного файла в текстовый ре дактор
придется заменить все пары «\n\r» на исходные «\r\n». B программе этим за нимается
следующий цикл.
while (pchANSI != NULL)
{
// вхождение найдено
*pchANSI++ = '\r ; // заменяем '\n' на '\r'
*pchANSI++ = '\n', // заменяем '\r на \n'
pchANSI = strchr(pchANSI, \n');
// ищем следующее вхождение
}
Закончив обработку файла, программа прекращает отображение на адресное про
странство представления объекта «проекция файла» и закрывает описатель всех объ ектов
ядра Кроме того, программа должна удалить нулевой символ, добавленный в конец файла
(функция strrev не меняет позицию этого символа) Если бы програм ма не убрала нулевой
символ, то полученный файл оказался бы на 1 символ длиннее, и тогда повторный запуск
программы FileRev не позволил бы вернуть этот файл в исходное состояние Чтобы
удалить концевой нулевой символ, надо спуститься на уровень ниже и воспользоваться
функциями, предназначенными для работы непос редственно с файлами на диске.
Прежде всего установите указатель файла в требуемую позицию (в данном слу чае
— в конец файла) и вызовите функцию SetEndOfFile:
SetFilePointer(hFile, dwFileSize, NULL, FILE_BEGIN);
SetEndOfFile(hFile);
NOTE:
Функцию SetEndOfFile нужно вызывать после отмены проецирования представ ления и
закрытия объекта «проекция файла», иначе она вернет FALSE, а функ ция GetLastError
— ERROR_USER_MAPPED_FILE. Данная ошибка означает, что операция перемещения
указателя в конец файла невозможна, пока этот файл связан с объектом "проекция
файла".
Последнее, что делает FileRev, — запускает экземпляр Notepad, чтобы Вы могли
увидеть преобразованный файл. Вот как выглядит результат работы программы FileRev
применительно к собственному файлу FileRev.cpp.
FileRev
Обработка больших файлов
Я обещал рассказать, как спроецировать на небольшое адресное просранство файл
длиной 16 экзабайтов. Так вот, этого сделать нельзя Вам придется проецировать не весь
файл, а сго представление, содержащее лишь некую часть данных Вы начнете с того, что
спроецируете представление самого начала файла Закончив обработку дан ных в этом
представлении, Вы отключите его и спроецируете представление следую щей части файла
— и так до тсх пор, пока нс будет обработан весь файл Конечно, это делает работу с
большими файлами, проецируемыми в память, не слишком удоб ной, но утешимся тем,
чго длина большинства файлов достаточно мала
Рассмотрим сказанное на примере файла размером 8 Гб Ниже приведен текст
подпрограммы, позволяющей в несколько этапов подсчитывать, сколько раз встреча ется
нулевой байт в том или ином двоичном файле данных.
__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);
}
Этот алгоритм проецирует представления по 64 Кб (в соответствии с грануляр
ностью выделения памяти) или менее Кроме того, функция MapViewOfFile требует, чтобы
передаваемое ей смещение в файле тоже было кратно гранулярности выделе ния памяти.
Подпрограмма проецирует на адресное пространство сначала одно пред ставление,
подсчитывает в нем количество нулей, затем переходит к другому пред ставлению, и все
повторяется. Спроецировав и просмотрев все 64-килобайтовые бло ки, подпрограмма
закрывает объект «проекция файла».
Проецируемые файлы и когерентность
Система позволяет проецировать сразу несколько представлений одних и тех же
файловых данных. Например, можно спроецировать в одно представление первые 10 Кб
файла, а затем — первые 4 Кб того же файла в другое представление Пока Вы
проецируете один и тот же объект, система гарантирует когерентность (согласован
ность) отображаемых данных. Скажем, если программа изменяет содержимое файла в
одном представлении, это приводит к обновлению данных и в другом. Так проис ходит
потому, что система, несмотря па многократную проекцию страницы на вир туальное
адресное пространство процесса, хранит данные на единственной страни це оперативной
памяти Поэтому, ссли представления одного и того же файла дан ных создаются сразу
несколькими процессами, данные по-прежнему сохраняют ко герентность — ведь они
сопоставлены только с одним экземпляром каждой страни цы в оперативной памяти Bcc
это равносильно тому, как если бы страницы опера тивной памяти были спроецированы
на адресные пространства нескольких процес сов одновременно.
NOTE:
Windows позволяет создавать несколько объектов «проекция файла», связан ных с одним
и тем же файлом данных. Но тогда у Вас не будет гарантий, что содержимое
представлений этих объектов когерентно Такую гарантию Win dows дает только для
нескольких представлений одного объекта «проекция файла".
Кстати, функция CreateFile позволяет Вашему процессу открывать файл, проеци
руемый в память другим процессом После этого Ваш процесс сможет считывать или
записывать данные в файл (с помощью функций ReadFite или WriteFile), Разумеется, при
вызовах упомянутых функций Ваш процесс будет считывать или записывать дан ные не в
файл, а в некий буфер памяти, который должен быть создан именно этим процессом;
буфер не имеет никакого отношения к участку памяти, используемому для проецирования
данного файла. Но надо учитывать, что, когда два приложения откры вают один файл,
могут возникнуть проблемы. Дсло в том, что один процесс может
вызвать ReadFile, считать фрагмент файла, модифицировать данные и записать их
обратно в файл с помощью WriteFile, а объект «проекция файла», принадлежащий вто
рому процессу, ничего об этом не узнает. Поэтому, вызывая для проецируемого фай ла
функцию CreateFile, всегда указывайте нуль в параметре dwShareMode. Тем самым Вы
сообщите системе, что Вам нужен монопольный доступ к файлу и никакой посто ронний
процесс не должен его открывать.
Файлы с доступом "только для чтения" не вызывают проблем с когерентностью —
значит, это лучшие кандидаты на отображение в память. Ни в коем случае не исполь зуйте
механизм проецирования для доступа к записываемым файлам, размещенным на сетевых
дискях, так как система не сможет гарантировать когерентность представ лений данных.
Если один компьютер обновит содержимое файла, то другой, у кото рого исходные
данные содержатся в памяти, не узнает об изменении информации.
Базовый адрес файла, проецируемого в память
Помните, как Вы с помощью функции VirtualAlloc указывали базовый адрес
региона, резервируемого в адресном пространстве? Примерно так же можно указать
системе спроектировать файл по определенному адресу — только вместо
функции MapView PVOID нужна MapViewOfFileEx;
PVOID MapViewOfFileEx( HANDLE hFileMappingObject, DWORD
dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T
dwNumberOfBytesToMap, PVOID pvBaseAddress);
Все параметры и возвращаемое этой функцией значение идентичны применяе мым
в MapViewOfFile, кроме последнего параметра — pvBaseAddress. B нем можно за дать
начальный адрес файла, проецируемого в память Как и в случае VirtualAlloc, ба повый
адресдолжен быть кратным гранулярности выделения памяти в системе (обыч но 64 Кб),
иначе MapViewOfFileEx вернет NULL. сообщив тем самым об ошибке.
Если Вы укажете базовый адрес, не кратный гранулярности выделения памяти,
то MapViewOfFileEx в Windows 2000 завершится с ошибкой, и GetLastError вернет код
1132 (ERROR_MAPPED_ALIGNMENT) а в Windows 98 базовый адрес будет округлен до
бли жайшего меньшего значения, кратного гранулярности выделения памяти.
Если система не в состоянии спроецировать файл по этому адресу (чаще всего из за
того, что файл слишком велик и мог бы перекрыть другие регионы зарезервиро ванного
адресного пространства), функция также возвращает NULL B этом случае она не пытается
подобрать диапазон адресов, подходящий для данного файла Но если Вы укажете NUI,L в
параметре pvBaseAddress, она поведет себя идентично MapViewOfFile.
MapViewOfFileEx удобна, когда механизм проецирования файлов в память приме
няется для совместного доступа нескольких процессов к одним данным. Поясню
Допустим, нужно спроецировать файл в память по определенному адресу; при этом два
или более приложений совместно используют одну группу структур данных, со держащих
указатели на другие структуры данных Отличный тому пример — связан ный список
Каждый узел, или элемент, такого списка хранит адрес другого узла спис ка. Для
просмотра списка надо узнать адрес первого узла, а затем сделать ссылку на то его поле,
где содержится адрес следующего узла Но при использовании файлов, проецируемых в
память, это весьма проблематично.
Если один процесс подготовил в проецируемом файле связанный список, а затем
разделил его с другим процессом, нс исключено, что второй процесс спроецирует этот
файл в своем адресном пространстве на совершенно иной регион. Адальше бу дет вот что.
Попытавшись просмотреть связанный список, второй процесс проверит первый узсл
списка, прочитает адрес следующего узла и, сделав на нсго ссылку, полу чит совсем не то,
что ему было нужно, — адрес следующего элемента в первом узле некорректен для
второго процесса.
У этой проблемы два решения Во-первых, второй процесс, проецируя файл со
связанным списком на свое адресное пространство, может
вызвать MapViewOfFileEx вместо MapVtewOfFile. Для этого второй процесс должен знать
адрес, по которому файл спроецирован на адресное пространство первого процесса на
момент создания спис ка Если оба приложения разработаны с учетом взаимодействия
друг с другом (а так чаще всего и делают), нужный адрес может быть просто заложен в
код этих программ или же один процесс как-то уведомляет другой (скажем, посылкой
сообщения в окно).
А можно и так Процесс, создающий связанный список, должен записывать в каж
дый узел смещение следующего узла в пределах адресного пространства. Тогда про
грамма, чтобы получить доступ к каждому узлу, будет суммировать это смещение с
базовым адресом проецируемого файла. Несмотря на простоту, этот способ не луч ший:
дополнительные операции замедлят работу программы и увеличат объем ее кода (так как
компилятор для выполнения всех вычислений, естественно, сгенерирует до полнительный
код) Кроме того, при этом способе вероятность ошибок значительно выше. Тем не менее
он имеет право на существование, и поэтому компиляторы Micro soft поддерживают
указатели со смещением относительно базового значения (based pointers), для чего
предусмотрено ключевое слово _based
WINDOWS 98
В Windows 98 при вызове MapViewOfFileEx следует указывать адрес в диапазо не от
0x80000000 до 0xBFFFFFFF, иначе функция вернет NULL
WINDOWS 2000
В Windows 2000 при вызове MapViewOfFileEx следует указывать адрес в грани цах
пользовательского раздела адресного пространства процесса, иначе фун кция вернет
NULL.
Особенности проецирования файлов на разных платформах
Механизм проецирования файлов в Windows 2000 и Windows 98 реализован по-раз
ному Вы должны знать об этих отличиях, поскольку они могут повлиять на код про грамм
и целостность используемых ими данных
В Windows 98 представление всегда проецируется на раздел адресного простран
ства, расположенный в диапазоне от 0x80000000 до 0xBFFFFFFF. Значит, после успеш
ного вызова функция MapViewOfFile вернет какой-нибудь адрес из этого диапазона. Но
вспомните: данные в этом разделе доступны всем процессам. Если один из про цессов
отображает сюда представление объекта «проекция файла, то принадлежащие этому
объекту данные физически доступны всем процессам, и уже неважно: проеци руют ли они
сами представление того же объекта. Если другой процесс
вызывает MapViewOJFile, используя тот же объект «проекция файла», Windows 98
возвращает адрес памяти, идентичный тому, что она сообщила первому процессу.
Поэтому два процесса обращаются к одним и тсм же данным и представления их объектов
коге рентны.
В Windows 98 один процесс может вызвать MapViewOJFile и, воспользовавшись
какой-либо формой межпроцессной связи, передать возвращенный ею адрес памяти
потоку другого процесса. Как только этот поток получит нужный адрес, ему уже нич то не
помешает получить доступ к тому же представлению объекта "проекция фай ла". Но
прибегать к такой возможности не следует по двум причинам:
Когда Вы щелкнсте кнопку Create а 1 MB (1024 KB) Sparse MMF, программа попы
тается создать разреженный файл «C:\MMFSpanse». Если Ваш диск С не является томом
NTFS 5, у программы ничего не получится, и ее процесс завершится А если Вы созда ли
том NTFS 5 на каком-то другом диске, модифицируйте мою программу и переком
пилируйте ее, чтобы посмотреть, как она работает
После создания разреженный файл проецируется на адресное пространство про
цесса. В поле Allocated Kangcs (внизу окна) показывается, какие части файла действи
тельно связаны с дисковой памятью. Изначально файл не связан ни с какой памятью, и в
этом поле сообщается «No allocated ranges in the file» («В файле нет выделенных
диапазонов»).
Чтобы считать байт, просто введите число в поле Offset и щелкните кнопку Read
Byte. Введенное Вами число умножается на 1024 (1 Кб), и программа, считав байт по
полученному адресу, выводит его значение в поле Byte Если адрес попадает в область, не
связанную с физической памятью, в этом поле всегда показывается нулевой байт.
Для записи байта введите число в поле Offset, a значение байта (0-255) — в
поле Byte. Потом, когда Вы щелкнете кнопку Wrice Byte, смещение будет умножено на
1024, и байт по соответствующему адресу получит новое значение Операция записи мо
жет заставить файловую систему передать физическую память какой-либо части фай ла
Содержимое поля Allocated Ranges обновляется после каждой операции чтения или
записи, показывая, какие части файла связаны с физической памятью на данный мо мент.
Вот как вьплядит окно программы после записи всего одного байта по смеще нию 1 024
000 (1000 x 1024).
На этой иллюстрации видно, что физическая память выделена только одномуди
апазону адресов — размером 65 536 байтов, начиняя с логического смещения 983 040 от
начала файла С помощью ExpIorer Вы можете просмотреть свойства файла
C:\MMFSparbe, как показано ниже.
Заметьте: на этой странице свойств сообщается, что длина файла равна 1 Мб (это
виртуальный размер фаЙла), по на деле он занимает на диске только 64 Кб.
Последняя кнопка, Free All Allocated Regions, заставляет программу высвободить
всю физическую память, выделенную для файла; таким образом, соответствующее
дисковое пространство освобождается, а все байты в файле обнуляются.
Теперь поговорим о том, как работает эта программа. Чтобы упростить ее исход ный
код, я создал С++-класс CSparseStream (который содержится в файле Sparse Stream.h) Этот
класс инкапсулирует поддержку операций с разреженным файлом или потоком данных
(stream). В файле MMFSparse.cpp я создал другой С++-класс, CMMFSparse, производный
от CSparseStream. Так что объект класса CMMFSparse обла дает не только
функциональностью CSparseStream, но и дополнительной, необходи мой для
использования разреженного потока данных как проецируемого в память файла. В
процессе создается единственный глобальный экземпляр класса CMMF Sparse —
переменная g_mmf. Манипулируя разреженным проецируемым файлом, про грамма часто
ссылается на эту глобальную переменную.
Когда пользователь щелкает кнопку Create а 1MB (1024 KB) Sparse MMF, програм
ма вызывает CreateFile для создания нового файла в дисковом разделе NTFS 5. Пока что
это обычный, самый заурядный файл Но потом я вызываю метод Initialize гло бального
объекта g_mmf, передавая ему описатель и максимальный размер файла (1 Мб).
Метод Initialize в свою очередь обращается к CreateFileMapping и создает объект ядра
«проекция файла» указанного размера, а затем вызывает MapViewOfFile, чтобы сделать
разреженный файл видимым в адресном пространстве данного процесса
Когда Initialize возвращает управление, вызывается функция Dlg_ShowAllocated
Ranges Используя Windows-функции, она перечисляет диапазоны логических адре сов в
разреженном файле, которым передана физическая память. Начальное смеще ние и длина
каждого такого диапазона показываются в нижнем поле диалогового окна В момент
инициализации объекта g_mmf файлу на диске еще не выделена физичес кая память, и
данное поле отражает этот факт.
Теперь пользователь может попытаться считать или записать какие-то байты в
пределах разреженного проецируемого файла При записи программа извлекает зна чение
байта и смещение из соответствующих полей, а затем помещает этот байт по
вычисленному адресу в объект g_mmf. Такая операция может потребовать от файло вой
системы передачи физической памяти логическому блоку файла, но программа не
принимает в этом участия.
При чтении объекта g_mmf возвращается либо реальное значение байта, если дан
ному диапазону адресов передана физическая память, либо 0, если память не передана,
Моя программа также демонстрирует, как вернуть файл в исходное состояние,
высвободив все выделенные сму диапазоны адресов (после этого он фактически не
занимает места ня диске) Реализуется это так. Пользователь щелкает кнопку Free All
Allocated Regions. Однако освободить все диапазоны адресов, выделенные файлу, ко
торый проецируется в память, нельзя Поэтому первое, что делает программа, — вы зывает
метод ForceClose объекта g_mmf Этот метод обращается к UnmapViewOfFile, а потом —
к CloseHandle, передавая описатель объекта ядра «проекция файла».
Далее вызывается метод DecommitPortionOfStream, который освобождает всю па
мять, выделенную логическим байтам в файле. Наконец, программа вновь обращает ся к
методу Initialize объекча g_mmf, и тот повторно инициализирует файл, проеци руемый на
адресное пространство данного процесса. Чтобы подтвердить освобожде ние всей
выделенной памяти, программа вызывает функцию Dlg_ShowAllocatedRanges которая
выводит в поле строку «No allocated ranges in the file».
И последнее. Используя разреженный проецируемый файл в реальном приложе нии,
Вы, наверное, захотите при закрытии файла урезать его логический размер до
фактического Отсечение концевой части разреженного файла, содержащей нулевые
байты, не влияет на занимаемый им объем дискового пространства, но позволяет
Explorer и команде dir сообщать точный размер файла С этой целью Вы должны пос ле
вызова метода ForceClose использовать функции SetFilePointer и SetEndOfFile.