Неожиданная обработка страницы (также VirtualLock = no op?) - PullRequest
5 голосов
/ 24 октября 2011

Этим утром я наткнулся на удивительное количество ошибок страницы, где я их не ожидал.Да, я, наверное, не должен волноваться, но это все равно кажется мне странным, потому что в моем понимании они не должны происходить.И я бы хотел, чтобы они лучше этого не делали.

Приложение (под WinXP Pro 32bit) резервирует большую часть (1 ГБ) адресного пространства с VirtualAlloc(MEM_RESERVE), а затем выделяет умеренно большие блоки (20-50 МБ) памяти с VirtualAlloc(MEM_COMMIT).Это делается на рабочем месте раньше времени с намерением остановить основной поток как можно меньше.Очевидно, что вы никогда не сможете гарантировать, что сбои страниц не произойдут, если область памяти в данный момент не заблокирована, но некоторые из них, безусловно, терпимы (и неизбежны).Удивительно каждая страница неисправностей.Всегда.

Предполагалось, что система создает страницы только лениво после их распределения, что тоже имеет смысл (хотя документация предлагает что-то другое).Справедливо, мой плохой.
Поэтому очевидный обходной путь - VirtualLock / VirtualUnlock, который заставляет систему создавать эти страницы, поскольку они должны существовать после возврата VirtualLock.Удивительно, но по-прежнему не справляется с каждой страницей .

Поэтому я написал небольшую тестовую программу, которая выполняла все вышеперечисленные шаги последовательно, спя по 5 секунд между ними, чтобы исключить что-то вдругой код.Результаты были:

  • MEM_RESERVE 1 ГБ ---> успех, ноль ЦП, ноль времени, ничего не происходит
  • MEM_COMMIT 1 ГБ ---> успех, ноль ЦП, нулевое время, рабочий набор увеличивается на 2 МБ, 512 сбоев страниц (соответственно 8 байтов метаданных, выделенных в пространстве пользователя на страницу)
  • for(... += 128kB) { VirtualLock(128kB); VirtualUnlock(128kB); } ---> успех, ноль ЦП, ноль времени, ничего не происходит
  • for(... += 4096) *addr = 0; ---> 262144 сбоев страницы, около 0,25 секунды (~ 95% времени ядра).Увеличение на 1 ГБ как для «рабочего набора», так и для «физического» внутри Process Explorer
  • VirtualFree ---> ноль ЦП, нулевое время, как «рабочий набор», так и «физический» мгновенно переходят в * poof *.

Я ожидал, что, поскольку каждая страница была заблокирована один раз, она должна физически существовать , по крайней мере, после этого .Конечно, он может все еще перемещаться в WS и из WS при превышении квоты (простое изменение одной ссылки, если доступно достаточно оперативной памяти).Тем не менее, ни время выполнения, ни рабочий набор, ни показатели физической памяти, кажется, не поддерживают это.Скорее, как кажется, каждая отдельная доступная страница создается при сбое, даже если она была заблокирована ранее.Конечно, я могу вручную дотронуться до каждой страницы в рабочем потоке, но тоже должен быть чище?

Не правильно ли я делаю предположение о том, что VirtualLock должно делать или я что-то не так понимаю в виртуальной памяти?Любая идея о том, как сказать ОС «чистым, законным, работающим» способом, что я буду нуждаться в памяти, и я буду хотеть это для реального ?

ОБНОВЛЕНИЕ:
В ответ на предложение Гарри Джонстона я попробовал несколько проблематичный подход к фактическому вызову VirtualLock гигабайта памяти.Чтобы это было успешным, вы должны сначала установить размер рабочего набора процесса соответствующим образом, поскольку квоты по умолчанию составляют 200k / 1M, что означает, что VirtualLock не может заблокировать область размером более 200k (или, скорее, она не может заблокировать более 200k *)1056 * всего , и это минус то, что уже заблокировано для ввода-вывода или по другой причине).

После установки минимального размера рабочего набора 1 ГБ и максимум 2 ГБ все страницы сбоевпроизойдет в тот момент, когда VirtualAlloc(MEM_COMMIT) называется.«Виртуальный размер» в Process Explorer мгновенно увеличивается на 1 ГБ.До сих пор это выглядело действительно, действительно хорошо.
Однако, если присмотреться, «физическое» остается таким, как есть, реальная память действительно используется только в тот момент, когда вы касаетесь ее.

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

Однако есть две проблемы с изменением размера WS. Во-первых, у вас обычно не должно быть гигабайта минимального рабочего набора в процессе, потому что ОС старается сохранить этот объем памяти заблокированным. Это было бы приемлемо в моем случае (на самом деле это более или менее то, что я прошу).
Большая проблема в том, что SetProcessWorkingSetSize требуется право доступа PROCESS_SET_QUOTA, которое не является проблемой «администратора», но завершается ошибкой, когда вы запускаете программу как пользователь с ограниченными правами (по уважительной причине), и вызывает «» разрешить возможно вредную программу? предупреждение о каком-то известном российском антивирусном программном обеспечении (для нет веская причина, но, увы, его нельзя отключить).

Ответы [ 3 ]

3 голосов
/ 21 февраля 2013

VirtualLock остается бездействующим (из-за ошибки)

Я пытался воспроизвести это, но это сработало, как и следовало ожидать.Выполнение примера кода, показанного внизу этого поста:

  • запуск приложения (523 ошибки страницы)
  • настройка размера рабочего набора (21 ошибка страницы)
  • VirtualAlloc с MEM_COMMIT 2500 МБ ОЗУ (2 ошибки страницы)
  • VirtualLock все это ( около 641 250 страниц ошибок )
  • выполняет запись для всехэтой оперативной памяти в бесконечном цикле (ошибки нулевой страницы)

Все это работает в значительной степени, как и ожидалось.2500 МБ ОЗУ - это 640 000 страниц.Числа складываются.Кроме того, что касается счетчиков оперативной памяти в масштабе всей ОС, то объем фиксации увеличивается на VirtualAlloc, а использование физической памяти увеличивается на VirtualLock.

Так что VirtualLock определенно не не работает на моей машине с Win7 x64.Если я этого не сделаю, то страницы с ошибками, как и ожидалось, сместятся туда, где я начинаю запись в ОЗУ.Они по-прежнему составляют чуть более 640 000.Кроме того, первое время записи в память занимает больше времени.


Скорее, как кажется, каждая страница, к которой обращались, создается при сбое, даже если она была заблокирована ранее.

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

Что бы это ни стоило, в моей системе с несколькими гигабайтами свободной оперативной памяти это работает так, как вы надеялись: даже если я буду следоватьmy VirtualLock с немедленным VirtualUnlock и установкой минимального размера рабочего набора обратно на что-то маленькое, больше никаких сбоев страницы не происходит.

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

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

Другими словами, по крайней мере, в Win7 x64 все работает точно так, как вы ожидали, используя код, представленный ниже.


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

Ну ... если вы хотите VirtualLock, вы уже вмешиваетесь в него.Единственное, что делает SetProcessWorkingSetSize, это позволяет вам вмешиваться.Это не ухудшает производительность само по себе;это VirtualLock, что делает - но только если в системе фактически не хватает физической памяти.


Вот полная программа:

#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <iostream>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    SIZE_T chunkSize = 2500LL * 1024LL * 1024LL; // 2,626,568,192 = 640,000 pages
    int sleep = 5000;

    Sleep(sleep);

    cout << "Setting working set size... ";
    if (!SetProcessWorkingSetSize(GetCurrentProcess(), chunkSize + 5001001L, chunkSize * 2))
        return -1;
    cout << "done" << endl;

    Sleep(sleep);

    cout << "VirtualAlloc... ";
    UINT8* data = (UINT8*) VirtualAlloc(NULL, chunkSize, MEM_COMMIT, PAGE_READWRITE);
    if (data == NULL)
        return -2;
    cout << "done" << endl;

    Sleep(sleep);

    cout << "VirtualLock... ";
    if (VirtualLock(data, chunkSize) == 0)
        return -3;
    //if (VirtualUnlock(data, chunkSize) == 0) // enable or disable to experiment with unlocks
    //    return -3;
    //if (!SetProcessWorkingSetSize(GetCurrentProcess(), 5001001L, chunkSize * 2))
    //    return -1;
    cout << "done" << endl;

    Sleep(sleep);

    cout << "Writes to the memory... ";
    while (true)
    {
        int* end = (int*) (data + chunkSize);
        for (int* d = (int*) data; d < end; d++)
            *d = (int) d;
        cout << "done ";
    }

    return 0;
}

Обратите внимание, что этот код помещает поток вспать после VirtualLock.Согласно сообщению 2007 Раймонда Чена , операционная система может свободно выгружать все данные из физической памяти на этом этапе и до тех пор, пока поток не проснется снова.Также обратите внимание, что MSDN утверждает иначе , говоря, что эта память не будет выгружена, независимо от того, все ли потоки спят или нет.В моей системе они, конечно, остаются в физической памяти, пока единственный поток спит.Я подозреваю, что совет Рэймонда был применен в 2007 году, но больше не верен в Win7.

3 голосов
/ 07 марта 2012

Технически VirtualLock - это подсказка, поэтому ОС разрешено игнорировать ее. Он поддерживается системным вызовом NtLockVirtualMemory, который в Reactos / Wine реализован как бездействующий, однако Windows поддерживает системный вызов с реальной работой (MiLockVadRange).

VirtualLock не гарантирует успеха. Для вызова этой функции требуется, чтобы SE_LOCK_MEMORY_PRIVILEGE работал, а адреса должны соответствовать ограничениям безопасности и квотам. Кроме того, после VirtualUnlock ядро ​​больше не обязано хранить вашу страницу в памяти, поэтому сбой страницы после этого является допустимым действием.

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

0 голосов
/ 15 мая 2014

У меня недостаточно репутации, чтобы комментировать, поэтому мне придется добавить это как ответ.

Обратите внимание, что этот код переводит поток в спящий режим после VirtualLock.Согласно сообщению Рэймонда Чена, опубликованному в 2007 году, операционная система может свободно загружать все данные из физической памяти на этом этапе, и пока поток не проснется снова [...] Я подозреваю, что совет Рэймонда применялся в 2007 году, но больше не соответствует действительностиWin7.

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

Вероятно, это не тот случай, потому что, согласно msdn, это контракт

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...