В Win32 виртуальное адресное пространство каждого процесса составляет 4 Гб, т.е. соответствующий 32-битный указатель может быть числом от $00000000 до $FFFFFFFF.
Для 64-разрядных процессов размер адресного пространства равен 16 экзабайтам, поскольку 64-битный указатель может быть любым числом от 0x00000000 00000000 до 0xFFFFFFFF FFFFFFFF.
В DOS или в 16-разрядной Windows все процессы находились в едином адресном пространстве, поэтому сбой в процессе A мог привести к краху процесса B или всей ОС.
В Win32 каждому процессу отводится закрытое адресное пространство:
Например, процесс А хранит массив информации по адресу $12345678. В то же время процесс B также хранит свою информацию по тому же адресу $12345678.
Но процесс A имеет доступ к своему массиву, а процесс В к своей информации. Обращаясь к памяти по адресу 0x12345678, потоки, выполняемые в процессе А, получают доступ к структуре данных процесса А, Но, когда по тому же адресу обращаются потоки, выполняемые в процессе В, они получают доступ к структуре данных процесса В. Иначе говоря, потоки процесса А не могут обратиться к структуре данных в адресном пространстве процесса В, и наоборот
В Windows NT память ОС скрыта от других процессов, но в Windows 95 это не реализовано, поэтому любой процесс может случайным образом нарушить нормальную работу ОС.
В Win32 существует три механизма управления памятью:
виртуальная память – наиболее подходящая для операций с большими массивами объектов или структур;
проецируемые в память файлы – наиболее подходящие для операций с интенсивными потоками данных;
кучи – наиболее подходящие для работы с множеством малых объектов.
Использование виртуальной памяти:
Для резервирования региона в адресном пространстве используется функция
LPVOID WINAPI VirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
Функция возвращает базовый адрес региона или NULL, если зарезервировать регион не удалось.
lpAddress – указывает, где именно система должна зарезервировать регион. Если передается lpAddress=NULL, система выбирает место размещения региона автоматически.
dwSize – указывает размер резервируемого региона в байтах. Система округляет этот параметр до величины кратной размеру страницы.
flAllocationType – режим работы функции. В случае резервирования региона режим равен MEM_RESERVE. Если вы хотите выделить память и не освобождать ее в ближайшее время, то лучше всего выделить его в диапазоне старших адресов. В этом случае можно вместе с флагом MEM_RESERVE скомбинировать флаг MEM_TOP_DOWN.
flProtect – указывает атрибут защиты региона. Возможны следующие значения:
Атрибут защиты |
Описание |
PAGE_NOACCESS |
Попытки чтения, записи или исполнения содержимого памяти на этой странице вызывают нарушение доступа |
PAGE_READONLY |
Попытки записи или исполнения содержимого памяти на этой странице вызывают нарушение доступа |
PAGE_READWRITE |
Попытки исполнения содержимого памяти на этой странице вызывают нарушение доступа |
PAGE_EXECUTE |
Попытки чтения или записи на этой странице вызывают нарушение доступа |
PAGE_EXECUTE_READ |
Попытки записи на этой сфанице вызывают нарушение доступа |
PAGE_EXECUTE_READWRITE |
На этой странице возможны любые операции |
PAGE_WRITECOPY |
Попытки исполнения содержимого памяти на этой странице выбывают нарушение доступа, попытка записи приводит к тому, что процессу предоставляется «личная» копия данной страницы |
PAGE_EXECUTE_WRITECOPY |
На этой странице возможны любые операции, попытка записи приводит к тому, что процессу предоставляется «личная» копия данной страницы |
Прежде чем обращаться к содержимому зарезервированного региона, необходимо передать ему физическую память. Для этого используется та же функция с параметром flAllocationType равным MEM_COMMIT.
Так как передавать физическую память всему региону сразу не обязательно, вы можете указать адрес и размер.
Можно использовать функцию VirtualAlloc для одновременного резервирования и передачи физической памяти. Для этого необходимо скомбинировать MEM_RESERVE и MEM_COMMIT.
Предположим, вам необходимо в своей программе использовать электронную таблицу размером 200 строк на 256 колонок. Для описания ячейки данной таблицы вам необходима структура CellData, размер которой 126 байт
Проще всего описать двумерный массив. Для этого массива вам понадобится 200*256*128=6 553 600≈6 Мб байтов физической памяти. Такое использование памяти можно назвать расточительным, так как пользователь, как правило, использует небольшое количество ячеек. Для экономии памяти используют связанные списки, но такой подход усложняет манипулирование данными.
Для решения поставленной задачи можно использовать виртуальную память, для чего необходимо:
1. зарезервировать регион виртуальной памяти для всего массива,
2. при вводе данных в ячейку вычислить адрес размещения данных и передать по этому адресу физическую память. Размер передаваемой памяти равен размеру структуры,
3. инициализировать элементы структуры.
Существует проблема, связанная с необходимостью определения, передана ли физическая память под конкретную ячейку или еще нет.
Существует ряд решений:
1. Всегда передавать физическую память. Это возможно, так как функция VirtualAlloc проверяет, спроецирована ли физическая память по данному адресу.
2.Использовать структурную обработку исключительных ситуаций.
Для возврата физической памяти, отображенной на регион, или освобождения всего региона используется функция
BOOL WINAPI VirtualFree(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD dwFreeType
);
Для освобождения всего зарезервированного региона:
в lpAddress необходимо поместить базовый адрес региона,
в параметре dwSize указать 0 (размер региона известен системе),
при этом параметр dwFreeType должен быть равен MEM_RELEASE.
Для возврата физической памяти необходимо:
в lpAddress указать адрес первой освобождаемой страницы,
в параметре dwSize – размер освобождаемой физической памяти,
параметр dwFreeType должен быть равен MEM_DECOMMIT.
В случае, если необходимо освободить все страницы зарезервированного региона, dwSize должен быть равен 0.
В Win32 существуют функции необходимые для изменения атрибутов защиты страниц переданной физической памяти.
BOOL WINAPI VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
lpAddress – содержит адрес первой страницы региона памяти.
dwSize – размер региона.
flNewProtect – содержит новый атрибут защиты.
lpflOldProtect – содержит адрес выделенной памяти для размещения старого атрибута защиты.
ОС сама определяет, когда ей необходимо выгрузить или загрузить страницы из страничного файла в оперативную память. Тем не менее в Win32 API существуют функции, позволяющие вмешаться в этот процесс.
BOOL WINAPI VirtualLock(LPVOID lpAddress, SIZE_T dwSize);
Функция блокирует группу страниц в оперативной памяти, начиная с адреса lpAddress размером dwSize. Необходимо учесть, что заблокированные страницы остаются таковыми, если хотя бы один поток процесса выполняется.
Если все потоки вытеснены, ОС может выгрузить заблокированные страницы из RAM. Если потоки вашего процесса вновь потребуют процессорного времени заблокированные страницы будут предварительно загружены в RAM.
BOOL WINAPI VirtualUnlock(LPVOID lpAddress, SIZE_T dwSize);
Функция позволяет разблокировать группу страниц размером dwSize, начиная с адреса lpAddress.