Я хотел бы зарезервировать область памяти, а затем файлы карты, смежные в зарезервированную память. Между отображением файлов могут быть большие промежутки времени, в течение которых другие функции могут выделять память из кучи. После сопоставления файл не может быть сопоставлен и сопоставлен с новой областью памяти.
В Linux это было бы что-то вроде:
#include <iostream>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <cerrno>
int main(){
void *memory = mmap(nullptr, getpagesize() * 2,
PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, -1, 0); // reserve memory
int handle1 = ::open("1", O_CREAT | O_RDWR, S_IRWXU); // open file1
int handle2 = ::open("2", O_CREAT | O_RDWR, S_IRWXU); // open file2
void *data = mmap(memory, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED |MAP_FIXED, handle1, 0); // map first file into reserved memory
void *data2 = mmap(static_cast<char *>(memory) + getpagesize(), getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, handle2, 0); // map second file into reserved memory
}
На Windows, однако, я не могу найти правильный способ сделать это. Кто-нибудь знает, как это делается?
Редактировать:
Поскольку моя цель, кажется, не так легко понять. Еще раз упростил:
Я хотел бы иметь память, которая отображена в памяти. После того, как отображенная память заполнена данными, я хотел бы отобразить новый файл в память непосредственно после уже отображенной памяти, чтобы расширить отображаемую память (не оставляя зазора между двумя отображенными областями памяти).
При более позднем запуске программы файлы могут быть использованы для восстановления всех данных предыдущего запуска.
Проблема с решением с разреженными файлами и расширением существующего файла:
Программа должна иметь возможность удалять файлы, если они больше не нужны. Поэтому важно, чтобы сопоставления всегда были новыми файлами.
Вы можете думать об этом больше как распределитель. Память на карте памяти нужна. Библиотека отображает блок памяти и возвращает указатель на подблок. Память больше не нужна, она возвращается распределителю. Если полное сопоставление больше не требуется, связанный файл удаляется (данные не должны записываться сопоставлением, если оно больше не требуется).
Вам на самом деле не нужно резервировать его при отображении в файлах, вам просто нужно знать, что вы можете сопоставить два файла где-то последовательно. Например, ваш пример кода Linux, будучи однопоточным, будет работать так же хорошо, если вы немедленно отключите зарезервированный регион перед отображением в файлах.
В Windows, обрабатывая возможное многопоточное состояние гонки, вы можете сделать что-то вроде:
while(1) {
char *memory = VirtualAlloc(NULL, page_size * 2, MEM_RESERVE, 0);
VirtualFree(memory);
if (MapViewOfFileEx(handle1, FILE_MAP_WRITE, 0, 0, page_size, memory) == NULL
&& GetLastError() == ERROR_INVALID_ADDRESS) {
continue;
}
if (MapViewOfFileEx(handle2, FILE_MAP_WRITE, 0, 0, page_size, memory + page_size) == NULL
&& GetLastError() == ERROR_INVALID_ADDRESS) {
UnMapViewOfFile(memory);
continue;
}
break;
}
мое решение использовалось незадокументированный апи
NTSYSAPI NTSTATUS NTAPI ZwExtendSection ( HANDLE SectionHaqndle, PLARGE_INTEGER SectionSize );
нет аналога win32 для этой функции, но это ключевой момент решения.
также нам нужно использовать ZwMapViewOfSection
но нет MapViewOfFileEx
(оболочка win32 закончена ZwMapViewOfSection
) так как MapViewOfFileEx
иметь меньше параметров, чем ZwMapViewOfSection
— мы не можем установить ULONG AllocationType
в MEM_RESERVE
— но это тоже ключевой момент. для других задач мы можем использовать аналог win32, но для единообразия и стиля я буду использовать NT API.
конечно, многие просто говорят, что он недокументированный, неподдерживаемый и т. д. используют ntdll api direct — но на самом деле это работает, и здесь я не рассматриваю решение, основанное только на win32. так что кто хочет, тот может использовать, кто не хочет, тот не может использовать. как есть
идея — мы просто зарезервировали необходимый большой регион с призывом ZwMapViewOfSection
(это невозможно сделать MapViewOfFileEx
) и затем, когда это необходимо, мы можем расширить этот регион, позвонив ZwExtendSection
Решение проверено и сработало.
class SECTION_EX
{
LARGE_INTEGER _CurrentSize, _MaximumSize;
HANDLE _hSection;
PVOID _BaseAdress;
public:
NTSTATUS Create(POBJECT_ATTRIBUTES poa, SIZE_T InitialSize, SIZE_T MaximumSize);
NTSTATUS Extend(SIZE_T NewSize);
SECTION_EX()
{
_BaseAdress = 0;
_hSection = 0;
}
~SECTION_EX()
{
if (_hSection)
{
if (_BaseAdress) ZwUnmapViewOfSection(NtCurrentProcess(), _BaseAdress);
ZwClose(_hSection);
}
}
};
NTSTATUS SECTION_EX::Extend(SIZE_T NewSize)
{
LARGE_INTEGER Size;
Size.QuadPart = NewSize;
if (Size.QuadPart <= _CurrentSize.QuadPart)
{
return STATUS_SUCCESS;
}
if (Size.QuadPart > _MaximumSize.QuadPart)
{
return STATUS_SECTION_TOO_BIG;
}
NTSTATUS status = ZwExtendSection(_hSection, &Size);
if (0 <= status)
{
_CurrentSize = Size;
}
return status;
}
NTSTATUS SECTION_EX::Create(POBJECT_ATTRIBUTES poa, SIZE_T InitialSize, SIZE_T MaximumSize)
{
HANDLE hFile;
IO_STATUS_BLOCK iosb;
NTSTATUS status = ZwCreateFile(&hFile, FILE_GENERIC_READ|FILE_GENERIC_WRITE, poa,
&iosb, 0, 0, FILE_SHARE_VALID_FLAGS, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_ALERT, 0, 0);
if (0 <= status)
{
_MaximumSize.QuadPart = MaximumSize;
LARGE_INTEGER Size, *pSize = &Size;
Size.QuadPart = InitialSize;
if (iosb.Information == FILE_OPENED)
{
FILE_STANDARD_INFORMATION fsi;
if (0 <= (status = ZwQueryInformationFile(hFile, &iosb, &fsi, sizeof(fsi), FileStandardInformation)))
{
if (fsi.EndOfFile.QuadPart)
{
pSize = 0;// in case file already exist with not zero size - use it
}
}
}
if (0 <= status)
{
status = ZwCreateSection(&_hSection, SECTION_ALL_ACCESS, 0, pSize,
PAGE_READWRITE, SEC_COMMIT, hFile);
}
ZwClose(hFile);
if (0 <= status)
{
SECTION_BASIC_INFORMATION sbi;
if (0 <= ZwQuerySection(_hSection, SectionBasicInformation, &sbi, sizeof(sbi), 0))
{
_CurrentSize = sbi.Size;// real file size in bytes, without align
// !!! use MEM_RESERVE !!!
// MaximumSize - will be reserved, but not all commited
status = ZwMapViewOfSection(_hSection, NtCurrentProcess(), &_BaseAdress, 0,
0, 0, &MaximumSize, ViewUnmap, MEM_RESERVE, PAGE_READWRITE);
}
}
}
return status;
}
void demoS()
{
SECTION_EX se;
STATIC_OBJECT_ATTRIBUTES(oa, "\\??\\c:\\***");
// reserve 256Mb,but initially commit only 32kb or file size
if (0 <= se.Create(&oa, 0x8000, 0x10000000))
{
se.Extend(0x18000);
se.Extend(0x1e245);
se.Extend(0x74100);
}
}
Обновление: я обнаружил, что начиная с Win 8.1 мы можем зарезервировать область памяти с разделом, используемым также незадокументированный FILE_MAP_RESERVE
— так нужно позвонить
_BaseAdress = MapViewOfFileEx(_hSection, FILE_MAP_ALL_ACCESS|FILE_MAP_RESERVE, 0, 0, MaximumSize, 0);
но в Windows 7, Vista и XP — это не будет работать. тем не мение ZwMapViewOfSection
работал с MEM_RESERVE
флаг даже в XP.
так обычная ситуация — часто оболочка win32 имеет менее функциональное сравнение с соответствующей функцией Nt * / Zw *.
и до сих пор нет никакого аналога / оболочки win32 для ZwExtendSection
(этот вызов расширяет как файл, так и вид)
Правильное решение состоит в том, чтобы перестроить архитектуру, чтобы исключить требование для смежных отображений.
В зависимости от ваших конкретных потребностей, один из подходов заключается в использовании одного разреженного файла (как описано здесь), чья начальная длина равна количеству адресного пространства, которое вы хотите зарезервировать. Поскольку файл является разреженным, только фактически используемые блоки занимают место на диске.
В противном случае вам может потребоваться изменить базовые алгоритмы, обрабатывающие данные, чтобы они больше не зависели от непрерывности памяти. Обычно это не так сложно, как может показаться.
Одной (совсем не правильной!) Альтернативой было бы перехватить функцию VirtualAlloc, чтобы вы могли блокировать ее при необходимости. Это позволило бы вам эффективно управлять вашим зарезервированным диапазоном памяти — вы можете освободить его, отобразить его часть, а затем повторно зарезервировать остальное, аналогично ответу Росса Риджа, не опасаясь, что другой поток выделит память пока ты так делаешь.
(Это не защитит вас от драйверов устройств, но AFAIK крайне редко для драйвера устройства самопроизвольно выделяет память в адресном пространстве пользователя.)
NB: Я предполагаю, что использование одного файла по какой-то причине неприемлемо. Если один файл подойдет, вы должны использовать разреженный файл, как предложено выше. Если по какой-то причине подходит один файл, но использование разреженного файла не подходит, я бы порекомендовал подход RbMm к этому — ни один хорошо решение, но я думаю, что это немного более рискованно. (Это, конечно, сложнее.)