Основы организации операционных систем Microsoft Windows

Функционирование менеджера памяти

Разбить на страницы
Показывать лекцию целиком

После знакомства со структурой виртуального адресного пространства перейдем к рассмотрению вопросов связывания виртуальных адресов с физическими адресами в рамках сегментно-страничной модели памяти Windows.

(рис 10.1) Трансляция адреса в страничной виртуальной схеме

На рис 10.1 представлено отображение различных типов виртуальных адресов в физические. Для трансляции виртуального адреса, сгенерированного процессором, нужно определить номер виртуальной страницы, содержащей данный адрес, а также принадлежность страницы к зарезервированному региону. Некоторым виртуальным страницам таблица страниц ставит в соответствие физические страницы (страничные кадры или фреймы) в оперативной памяти. Любопытно, что код режима ядра, например, драйверов, может выполнить обратную трансляцию - получение виртуального адреса по физическому. Тем страницам, которым места в физической памяти не нашлось, соответствуют теневые страницы в системном файле выгрузки. Таблица страниц здесь представлена схематично, ее более точная версия будет продемонстрирована ниже.

Каждая строка в таблице страниц называется PTE (page table entry). В 32-разрядных архитектурах IA-32 PTE занимает 4 байта (см. рис 10.2).

(рис 10.2) Структура строки таблицы страниц (PTE)

Назначение отдельных атрибутов ясно из рисунка. Например, информация об обращении к странице или ее модификации (биты 5 и 6) позволяет собирать статистику обращений к памяти, которую используют алгоритмы выталкивания страниц. С точки зрения процесса трансляции наиболее важную роль играет бит присутствия , PTE с установленным битом V называются "действительными", а соответствующая страница находится в оперативной памяти.

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

В действительности трансляция происходит более сложно (см., например, ). Существенная часть записей PTE кэшируется в ассоциативной памяти (TLB регистрах) процессора. При этом таблицу страниц в силу ее большого объема конструируют из двух уровней: каталог таблиц страниц (page directory) и таблицы страниц (page table), см. рис 10.3.

(рис 10.3) Трансляция адреса с использованием ассоциативной памяти и двухуровневой таблицы страниц

Размер таблицы страниц подобран таким образом, что она целиком заполняет одну страницу оперативной памяти - 4 Кб. Для быстрого нахождения таблицы страниц один из регистров процессора (CR3 в Intel) указывает на каталог таблиц страниц (page directory) данного процесса, который хранится по адресу 0хС0300000. Значение этого регистра входит в контекст процесса. Поэтому, а также потому, что при смене исполняемого потока буфер ассоциативной памяти нуждается в обновлении, несколько увеличивается время переключения контекстов. Эти особенности архитектуры хорошо известны и здесь не будут описываться подробно. В дальнейшем для упрощения в схеме трансляции адреса таблица страниц будет изображаться одномерной.

Разделяемая память

Как уже было сказано в предыдущей лекции, при отображении файла в память образуется регион в виртуальной памяти, а также сопутствующий ему объект-раздел или объект-секция (section object). Как и другие объекты, объекты-разделы управляются диспетчером объектов. В случае возникновения ошибок страниц подкачка осуществляется из страниц проецируемого файла, а не из общесистемного файла выгрузки.

Для таких регионов формируется массив прототипных PTE, описывающих нахождение всех страниц этого фрагмента памяти. PTE таблицы страниц процесса, выполнившего отображение, ссылаются на прототипные PTE, как это показано на рис 10.4, и тоже считаются недействительными. Если при этом другой процесс выполнил отображение этого же файла, то PTE его таблицы тоже будут ссылаться на эти же самые прототипные PTE (см. рис 10.4). Таким образом, физические страницы памяти будут обобществлены.

(рис 10.4) Реализация разделяемого между двумя процессами региона проецируемого в память файла

Изменения, сделанные в данном фрагменте памяти одним процессом, будут сразу же "видны" другому процессу. Таким образом, наличие таблицы прототипных PTE обеспечивает когерентность разделяемой памяти.

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

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

Прогон программы, демонстрирующей передачу информации от одного процесса к другому через разделяемую память

Рассмотрим текст двух программ first.c и second.c

First.c

#include <windows.h>
#include <stdio.h>

void main(void){
HANDLE hMapFile;
LPVOID lpMapAddress;
HANDLE hFile; 
char * String;
 
hFile = CreateFile("MyFile.txt",                     // имя файла
                   GENERIC_READ | GENERIC_WRITE,     // файл для чтения и записи 
                   FILE_SHARE_READ| FILE_SHARE_WRITE,// режим совместного доступа 
                   NULL,                             // защита по умолчанию
                   OPEN_EXISTING,                    // файл должен существовать 
                   FILE_ATTRIBUTE_NORMAL,            // атрибуты файла
                   NULL);                            // файл атрибутов 
 
if (hFile == INVALID_HANDLE_VALUE)   printf("Could not open file\n"); 

hMapFile = CreateFileMapping(hFile,    // описатель отображаемого файла 
    NULL,                              // атрибуты защиты по умолчанию
    PAGE_READWRITE,                    // режим доступа 
    0,                                 // старшее двойное слово размера буфера
    0,                                 // младшее двойное слово размера буфера
   "MyFileObject");                    // имя объекта

if (hMapFile == NULL) printf("Could not create file-mapping object.\n"); 


lpMapAddress = MapViewOfFile(hMapFile,  // описатель отображаемого файла
    FILE_MAP_ALL_ACCESS,                // режимы доступа
    0, 0,                               // отображение файла с начала
    0);                                 // отображение целого файла

if (lpMapAddress == NULL) printf("Could not map view of file.\n"); 

String = (char *)lpMapAddress;
sprintf(String, "Hello, world");
getchar();
}

second.c

#include <windows.h>
#include <stdio.h>

void main(void){

HANDLE hMapFile;
LPVOID lpMapAddress;
HANDLE hFile; 
char * String;
 
hFile = CreateFile("MyFile.txt",                     // имя файла
                   GENERIC_READ | GENERIC_WRITE,     // файл для чтения и записи 
                   FILE_SHARE_READ| FILE_SHARE_WRITE,// режим совместного доступа 
                   NULL,                             // защита по умолчанию
                   OPEN_EXISTING,                    // файл должен существовать 
                   FILE_ATTRIBUTE_NORMAL,            // атрибуты файла
                   NULL);                            // файл атрибутов 

if (hFile == INVALID_HANDLE_VALUE) 
{ 
        printf("Could not open file\n");   // process error 
} 


hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, // разрешение чтения-записи 
                           FALSE,               // описатель не наследуется
                          "MyFileObject");      // имя объекта проецируемого файла 

if (hMapFile == NULL)  printf("Could not open Filemapping\n"); 

lpMapAddress = MapViewOfFile(hMapFile,  // описатель отображаемого файла
    FILE_MAP_ALL_ACCESS,                // режимы доступа
    0, 0,                               // отображение файла с начала
    0);                                 // отображение целого файла

if (lpMapAddress == NULL) printf("Could not map view of file.\n"); 

String = (char *)lpMapAddress;
printf("%s\n", String);
getchar();
}

Программа first создает в своем адресном пространстве буфер разделяемой памяти, а программа second отображает тот же самый буфер в свое адресное пространство. Затем программа first записывает в этот буфер текстовую строку, а программа second выводит ее содержимое на экран. Обе программы должны быть запущены из одного каталога с уже существующим файлом MyFile.txt. Для наглядности рекомендуется, чтобы длина файла была изначально больше длины строки "Hello, world".

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

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

Физическая память

Физическая (в данном случае оперативная) память и внешняя память также описываются соответствующими структурами данных.

ОС Windows поддерживает до 4 Гб (некоторые версии и более) физической памяти. Память более 32 Мб считается "большой". Объем памяти можно посмотреть на вкладке "Быстродействие" диспетчера задач. Информация о состоянии страниц физической памяти и их принадлежности процессам находится в базе данных PFN (page frame number), а использование внешней памяти осуществляется через страничные файлы или файлы выгрузки.

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

Структура системных страничных файлов недокументирована. Известно, что в системе может быть до 16 страничных файлов. Информация о страничных файлах находится в разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PagingFiles реестра, однако управление страничными файлами рекомендуется осуществлять через апплет "система" административной консоли управления. У каждого файла подкачки есть начальный и максимальный размер. С целью уменьшения вероятной фрагментации их создают максимального размера.

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

Рабочие наборы процессов

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

Согласно документации по ОС Windows, рабочим набором процесса называется совокупность физических страниц, выделенных процессу. Размер рабочего набора должен находиться в некоторых пределах, определяемых константами системы в зависимости от суммарного объема физической памяти. Например, если физической памяти достаточно, то рабочий набор процесса должен быть в диапазоне от 50 до 345 страниц. Имея привилегию Increase Scheduling Priority (о привилегиях см. часть V), эти значения можно менять при помощи функции SetProcessWorkingSet.

Если возникает страничная ошибка и рабочий набор процесса не превысил лимита (при слабой загруженности системы допускается даже превышение лимита), система выделяет ему еще один кадр в физической памяти. В противном случае ОС пытается заменять страницы в рабочем наборе этого процесса (локальный алгоритм замещения).

Эволюцию рабочего набора процесса можно "увидеть", наблюдая за счетчиками Working Set и др. в оснастке "Производительность", а также при помощи Диспетчера задач и утилит Pview, Pviewer и ряда других. Важно понимать, что изменение рабочих наборов является следствием страничных нарушений, которые происходят при фактическом обращении к страницам памяти. Простого выделения и передачи памяти здесь недостаточно.

Прогон программы, иллюстрирующей увеличение рабочего набора процесса

Рассмотрим легкую модификацию программы DemoVM, добавив туда операцию записи одного байта на каждую страницу переданной памяти (программа DemoPageFaults.c).

#include <windows.h>
#include <stdio.h>

void main(void)
{

PVOID pMem = NULL;
int nPageSize = 4096;
int nPages = 200;
long SizeCommit = 0;
int i;
char * Ptr;

SizeCommit = nPages * nPageSize;
getchar();
pMem =  VirtualAlloc(0, SizeCommit, MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
Ptr = (char *)pMem;
for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';
getchar();

pMem =  VirtualAlloc(0, SizeCommit, MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
Ptr = (char *)pMem;
for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';
getchar();

pMem =  VirtualAlloc(0, SizeCommit, MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
Ptr = (char *)pMem;
for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';
getchar();

pMem =  VirtualAlloc(0, SizeCommit, MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
Ptr = (char *)pMem;
for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';
getchar();
}

Наращивание объема переданной памяти и размера рабочего набора будет происходить по нажатию клавиши "Enter". Посмотрим на поведение счетчика "Рабочее множество" для процессов DemoVM и DemoPageFaults. Несмотря на одинаковый объем переданной физической памяти, размеры рабочего набора сильно отличаются. У DemoVM он остается близким к нулю, тогда как у процесса DemoPageFaults идет заметное ступенчатое приращение рабочего набора (см. рис 10.5)

(рис 10.5) Наблюдение за изменениями рабочих наборов процессов

Замещение страниц в рабочем наборе процесса - одна из наиболее ответственных операций. Дело в том, что уменьшение частоты page fault`ов является одной из ключевых задач системы управления памятью (например, известно, что вероятности page fault'а ).

В ОС Windows используются алгоритмы FIFO (first input first output) в многопроцессорном варианте и LRU - в однопроцессорном. На самом деле применяется не LRU, а его программная реализация NFU (not frequently used), согласно которой страница характеризуется не давностью, а частотой использования. Однако, согласно документации по ОС Windows, алгоритм, осуществляющий модификацию размера рабочего набора процесса, называется именно LRU. Что касается алгоритма FIFO, несмотря на известные недостатки, его применение упрощает обработку ссылок на страницу от нескольких процессоров.

База данных PFN. Страничные демоны

В процессе функционирования операционной системы в физической памяти располагаются рабочие наборы процессов, системный рабочий набор, свободные фрагменты и многое другое. Для учета состояния физической памяти поддерживается база данных PFN (page frame number). Это таблица записей фиксированной длины. Количество записей в ней совпадает с количеством страничных кадров.

Известно, что подсистема виртуальной памяти работает производительно при наличии резерва свободных страничных кадров. Тогда в случае страничной ошибки требуется только одна дисковая операция (чтение), и свободная страница может быть найдена немедленно. Алгоритмы, обеспечивающие поддержку системы в оптимальном состоянии, реализованы в составе фоновых процессов (их часто называют демонами или сервисами), которые периодически "просыпаются" и инспектируют состояние памяти. Их задача - обеспечивать достаточное количество свободных страниц, поддерживая систему в состоянии наилучшей производительности.

Формально, каждая страница физической памяти должна находиться в составе рабочего набора или входить в один из поддерживаемых базой связных списков страниц. Перемещение страниц между списками и рабочими наборами осуществляется системными потоками-демонами, входящими в состав менеджера памяти (см. ). Параметры настройки демонов хранятся в разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management реестра

Чаще всего для обслуживания ошибки страницы в соответствии с требованиями защиты уровня C2 (см. часть V) требуется обнуленная страница, которая извлекается из соответствующего списка. Список обнуленных страниц пополняется потоком обнуления страниц (zero page thread) в фоновом режиме за счет списка свободных страниц. Иногда, например, для отображения файла, обнуленные страницы не нужны, и можно обойтись свободными страницами. Если у рабочего набора процесса отбирается страница, она попадает в список модифицированных страниц или в список свободных страниц. Подсистема записи модифицированных страниц (modified page writer) записывает их содержание на диск, когда количество таких страниц превышает установленный лимит. Страницы проецируемого файла можно сбросить на диск явным образом (при помощи функции FlushViewOfFile ). После записи модифицированная страница попадает в список свободных страниц.

Общее руководство и реализацию общих правил управления памятью осуществляет диспетчер рабочих наборов (working set manager), который вызывается системным потоком ядра - диспетчером настройки баланса - раз в секунду или при уменьшении объема свободной памяти ниже порогового значения.

Эксперимент. Наблюдение за ошибками страниц

Количество ошибок страниц, генерируемых процессом, можно наблюдать при помощи счетчика "Ошибок страницы". На рис. приведены графики поведения счетчиков "Ошибок страниц" и "Рабочее множество" для процесса DempPageFaults. (см. программу, описанную выше)

(рис 10.6) Наблюдение за размером рабочего набора процесса и количеством страничных ошибок

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

При помощи утилиты Pfmon.exe из ресурсов Windows можно не только "увидеть" общее количество страничных нарушений, но и определить виртуальные адреса, обращения к которым эти нарушения спровоцировали. На примере 10.1 приведен фрагмент результатов работы данной утилиты для процесса DemoPageFaults.

…
SOFT: RtlFillMemoryUlong+0x10 : 0x00232000
SOFT: RtlFillMemoryUlong+0x10 : 0x00233000
SOFT: GetConsoleInputWaitHandle+0x11a : GetConsoleInputWaitHandle+0x119
SOFT: FindFirstFileExA+0x285 : FindFirstFileExA+0x285
SOFT: main+0xe4 : 0x00440000
SOFT: main+0xe4 : 0x00441000
SOFT: main+0xe4 : 0x00442000
SOFT: main+0xe4 : 0x00443000
SOFT: main+0xe4 : 0x00444000
SOFT: main+0xe4 : 0x00445000
SOFT: main+0xe4 : 0x00446000
SOFT: main+0xe4 : 0x00447000
SOFT: main+0xe4 : 0x00448000
SOFT: main+0xe4 : 0x00449000
SOFT: main+0xe4 : 0x0044a000
SOFT: main+0xe4 : 0x0044b000
SOFT: main+0xe4 : 0x0044c000
SOFT: main+0xe4 : 0x0044d000
SOFT: main+0xe4 : 0x0044e000
SOFT: main+0xe4 : 0x0044f000
SOFT: main+0xe4 : 0x00450000
SOFT: main+0xe4 : 0x00451000
SOFT: main+0xe4 : 0x00452000
SOFT: main+0xe4 : 0x00453000
SOFT: main+0xe4 : 0x00454000
SOFT: main+0xe4 : 0x00455000
…

Отдельные аспекты функционирования менеджера памяти

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

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

Локализация страниц в памяти

По умолчанию, процессу разрешается блокировать максимум 30 страниц памяти. Если увеличить рабочее множество процесса при помощи функции SetProcessWorkingSetSize, то, согласно документации, максимальное число страниц, которое процесс может блокировать, равно минимальному размеру его рабочего набора за вычетом 8 страниц.

Локализация страниц в памяти осуществляется при помощи Win32 функции VirtualLock, а освобождение страниц - при помощи VirtualUnlock. Учет локализованных страниц ведется в страничной базе PFN.

Прогон программы, демонстрирующей блокировку страниц в памяти

Приведенный листинг является примером такой программы.

#include <windows.h>
#include <stdio.h>

void main(void)
{

PVOID pMem = NULL;
int nPageSize = 4096;
int nPages = 400;
int nPageLock = 100;
long SizeCommit = 0;
int i;
char * Ptr;
int nMinPages = 200, nMaxPages = 500;
long dwMinimumWorkingSetSize = 0, dwMaximumWorkingSetSize = 0;
HANDLE hProcess;

hProcess = GetCurrentProcess();
dwMinimumWorkingSetSize = nMinPages * nPageSize;
dwMaximumWorkingSetSize = nMaxPages * nPageSize;

i = SetProcessWorkingSetSize(hProcess, dwMinimumWorkingSetSize, dwMaximumWorkingSetSize);
if(i==0) printf("SetProcessWorkingSetSize Error\n");

SizeCommit = nPages * nPageSize;

pMem =  VirtualAlloc(0, SizeCommit, MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");

Ptr = (char *)pMem;
for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';

i = VirtualLock(pMem, nPageLock * nPageSize);
if(i==0) printf("VirtualLock Error\n");

for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';

VirtualUnlock(pMem, nPageLock * nPageSize);
VirtualFree(pMem, 0, MEM_RELEASE);
}

Копирование при записи

Другой нюанс в работе менеджера памяти, который можно проиллюстрировать на практике, связан с реализацией алгоритма отложенного выделения памяти - копирование при записи (copy-on-write). Это один из примеров алгоритма отложенной оценки (lazy evaluation), которые усложняют систему, но делают её более эффективной.

Рассмотрим ситуацию, когда некоторая приватная область памяти процесса является точной копией уже существующего в системе фрагмента памяти. Например, память дочернего процесса после вызова функции fork() в Unix является копией памяти родительского процесса. Другой пример - совместное использование динамической библиотеки, до тех пор, пока одна из программ не поменяла ее статические данные. В таких случаях разумно не выделять отдельную область памяти для процесса, а отображать в его адресное пространство уже существующую. Собственно выделение можно осуществить тогда, когда процесс приступит к изменению содержимого этой области. Эта техника называется копированием при записи.

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

Прогон программы, иллюстрирующей отложенное выделение памяти

#include <windows.h>
#include <stdio.h>

void main(void)
{
HANDLE hMapFile;
LPVOID lpMapAddress;
HANDLE hFile; 
char * String;
 
hFile = CreateFile("MyFile.txt",GENERIC_READ | GENERIC_WRITE,
		       FILE_SHARE_READ| FILE_SHARE_WRITE, NULL, OPEN_ALWAYS,
                    FILE_ATTRIBUTE_NORMAL,NULL);
 
if (hFile == INVALID_HANDLE_VALUE) printf("Could not open file\n"); 

hMapFile = CreateFileMapping(hFile, NULL, 
                             PAGE_WRITECOPY,  // копирование при записи                                        
                             0,0,"MyFileObject");
                              
if (hMapFile == NULL)  printf("Could not create file-mapping object.\n"); 

 lpMapAddress = MapViewOfFile(hMapFile, 
                              FILE_MAP_COPY, // копирование при записи							    
                              0,0,0);
                              
if (lpMapAddress == NULL) printf("Could not map view of file.\n"); 

 String = (char *)lpMapAddress;
 getchar();
 sprintf(String, "Hello, world");
 printf("%s\n", String);

if (!UnmapViewOfFile(lpMapAddress))  printf("Could not unmap view of file.\n"); 
}

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

Контроль процессом памяти другого процесса

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

Для доступа к памяти процесса нужно получить его описатель. Наиболее естественный способ получения описателя - получение описателя дочернего процесса путем извлечения его из параметра lProcessInformation функции CreateProcess.

Для создания регионов в памяти другого процесса можно использовать функцию VirtualAllocEx, которой нужно передать описатель этого процесса в качестве параметра. Для доступа к памяти другого процесса применяются функции ReadProcessMemory и WriteProcessMemory.

Написание, компиляция и выполнение программы, осуществляющей доступ к памяти дочернего процесса

Рекомендуется самостоятельно написать программу, которая создает регион памяти в адресном пространстве дочернего процесса и записывает в него текстовую строку. Задача дочернего процесса - вывести эту строку на экран.

Заключение

Базовой операцией менеджера памяти является трансляция виртуального адреса в физический с помощью таблицы страниц и ассоциативной (TLB) памяти. В ряде случаев, для реализации разделяемой памяти, интеграции с системой ввода-вывода и др., применяется прототипная таблица страниц, которая является промежуточным звеном между обычной таблицей страниц и физической памятью. Для описания страниц физической памяти поддерживается база данных PFN (page frame number). Локализацию страниц памяти, контроль процессом памяти другого процесса и технику копирования при записи можно отнести к интересным особенностям системы управления памятью ОС Windows.

Страницы:

После знакомства со структурой виртуального адресного пространства перейдем к рассмотрению вопросов связывания виртуальных адресов с физическими адресами в рамках сегментно-страничной модели памяти Windows.

(рис 10.1) Трансляция адреса в страничной виртуальной схеме

На рис 10.1 представлено отображение различных типов виртуальных адресов в физические. Для трансляции виртуального адреса, сгенерированного процессором, нужно определить номер виртуальной страницы, содержащей данный адрес, а также принадлежность страницы к зарезервированному региону. Некоторым виртуальным страницам таблица страниц ставит в соответствие физические страницы (страничные кадры или фреймы) в оперативной памяти. Любопытно, что код режима ядра, например, драйверов, может выполнить обратную трансляцию - получение виртуального адреса по физическому. Тем страницам, которым места в физической памяти не нашлось, соответствуют теневые страницы в системном файле выгрузки. Таблица страниц здесь представлена схематично, ее более точная версия будет продемонстрирована ниже.

Каждая строка в таблице страниц называется PTE (page table entry). В 32-разрядных архитектурах IA-32 PTE занимает 4 байта (см. рис 10.2).

(рис 10.2) Структура строки таблицы страниц (PTE)

Назначение отдельных атрибутов ясно из рисунка. Например, информация об обращении к странице или ее модификации (биты 5 и 6) позволяет собирать статистику обращений к памяти, которую используют алгоритмы выталкивания страниц. С точки зрения процесса трансляции наиболее важную роль играет бит присутствия , PTE с установленным битом V называются "действительными", а соответствующая страница находится в оперативной памяти.

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

В действительности трансляция происходит более сложно (см., например, ). Существенная часть записей PTE кэшируется в ассоциативной памяти (TLB регистрах) процессора. При этом таблицу страниц в силу ее большого объема конструируют из двух уровней: каталог таблиц страниц (page directory) и таблицы страниц (page table), см. рис 10.3.

(рис 10.3) Трансляция адреса с использованием ассоциативной памяти и двухуровневой таблицы страниц

Размер таблицы страниц подобран таким образом, что она целиком заполняет одну страницу оперативной памяти - 4 Кб. Для быстрого нахождения таблицы страниц один из регистров процессора (CR3 в Intel) указывает на каталог таблиц страниц (page directory) данного процесса, который хранится по адресу 0хС0300000. Значение этого регистра входит в контекст процесса. Поэтому, а также потому, что при смене исполняемого потока буфер ассоциативной памяти нуждается в обновлении, несколько увеличивается время переключения контекстов. Эти особенности архитектуры хорошо известны и здесь не будут описываться подробно. В дальнейшем для упрощения в схеме трансляции адреса таблица страниц будет изображаться одномерной.

Разделяемая память

Как уже было сказано в предыдущей лекции, при отображении файла в память образуется регион в виртуальной памяти, а также сопутствующий ему объект-раздел или объект-секция (section object). Как и другие объекты, объекты-разделы управляются диспетчером объектов. В случае возникновения ошибок страниц подкачка осуществляется из страниц проецируемого файла, а не из общесистемного файла выгрузки.

Для таких регионов формируется массив прототипных PTE, описывающих нахождение всех страниц этого фрагмента памяти. PTE таблицы страниц процесса, выполнившего отображение, ссылаются на прототипные PTE, как это показано на рис 10.4, и тоже считаются недействительными. Если при этом другой процесс выполнил отображение этого же файла, то PTE его таблицы тоже будут ссылаться на эти же самые прототипные PTE (см. рис 10.4). Таким образом, физические страницы памяти будут обобществлены.

(рис 10.4) Реализация разделяемого между двумя процессами региона проецируемого в память файла

Изменения, сделанные в данном фрагменте памяти одним процессом, будут сразу же "видны" другому процессу. Таким образом, наличие таблицы прототипных PTE обеспечивает когерентность разделяемой памяти.

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

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

Прогон программы, демонстрирующей передачу информации от одного процесса к другому через разделяемую память

Рассмотрим текст двух программ first.c и second.c

First.c

#include <windows.h>
#include <stdio.h>

void main(void){
HANDLE hMapFile;
LPVOID lpMapAddress;
HANDLE hFile; 
char * String;
 
hFile = CreateFile("MyFile.txt",                     // имя файла
                   GENERIC_READ | GENERIC_WRITE,     // файл для чтения и записи 
                   FILE_SHARE_READ| FILE_SHARE_WRITE,// режим совместного доступа 
                   NULL,                             // защита по умолчанию
                   OPEN_EXISTING,                    // файл должен существовать 
                   FILE_ATTRIBUTE_NORMAL,            // атрибуты файла
                   NULL);                            // файл атрибутов 
 
if (hFile == INVALID_HANDLE_VALUE)   printf("Could not open file\n"); 

hMapFile = CreateFileMapping(hFile,    // описатель отображаемого файла 
    NULL,                              // атрибуты защиты по умолчанию
    PAGE_READWRITE,                    // режим доступа 
    0,                                 // старшее двойное слово размера буфера
    0,                                 // младшее двойное слово размера буфера
   "MyFileObject");                    // имя объекта

if (hMapFile == NULL) printf("Could not create file-mapping object.\n"); 


lpMapAddress = MapViewOfFile(hMapFile,  // описатель отображаемого файла
    FILE_MAP_ALL_ACCESS,                // режимы доступа
    0, 0,                               // отображение файла с начала
    0);                                 // отображение целого файла

if (lpMapAddress == NULL) printf("Could not map view of file.\n"); 

String = (char *)lpMapAddress;
sprintf(String, "Hello, world");
getchar();
}

second.c

#include <windows.h>
#include <stdio.h>

void main(void){

HANDLE hMapFile;
LPVOID lpMapAddress;
HANDLE hFile; 
char * String;
 
hFile = CreateFile("MyFile.txt",                     // имя файла
                   GENERIC_READ | GENERIC_WRITE,     // файл для чтения и записи 
                   FILE_SHARE_READ| FILE_SHARE_WRITE,// режим совместного доступа 
                   NULL,                             // защита по умолчанию
                   OPEN_EXISTING,                    // файл должен существовать 
                   FILE_ATTRIBUTE_NORMAL,            // атрибуты файла
                   NULL);                            // файл атрибутов 

if (hFile == INVALID_HANDLE_VALUE) 
{ 
        printf("Could not open file\n");   // process error 
} 


hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, // разрешение чтения-записи 
                           FALSE,               // описатель не наследуется
                          "MyFileObject");      // имя объекта проецируемого файла 

if (hMapFile == NULL)  printf("Could not open Filemapping\n"); 

lpMapAddress = MapViewOfFile(hMapFile,  // описатель отображаемого файла
    FILE_MAP_ALL_ACCESS,                // режимы доступа
    0, 0,                               // отображение файла с начала
    0);                                 // отображение целого файла

if (lpMapAddress == NULL) printf("Could not map view of file.\n"); 

String = (char *)lpMapAddress;
printf("%s\n", String);
getchar();
}

Программа first создает в своем адресном пространстве буфер разделяемой памяти, а программа second отображает тот же самый буфер в свое адресное пространство. Затем программа first записывает в этот буфер текстовую строку, а программа second выводит ее содержимое на экран. Обе программы должны быть запущены из одного каталога с уже существующим файлом MyFile.txt. Для наглядности рекомендуется, чтобы длина файла была изначально больше длины строки "Hello, world".

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

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

Физическая память

Физическая (в данном случае оперативная) память и внешняя память также описываются соответствующими структурами данных.

ОС Windows поддерживает до 4 Гб (некоторые версии и более) физической памяти. Память более 32 Мб считается "большой". Объем памяти можно посмотреть на вкладке "Быстродействие" диспетчера задач. Информация о состоянии страниц физической памяти и их принадлежности процессам находится в базе данных PFN (page frame number), а использование внешней памяти осуществляется через страничные файлы или файлы выгрузки.

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

Структура системных страничных файлов недокументирована. Известно, что в системе может быть до 16 страничных файлов. Информация о страничных файлах находится в разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PagingFiles реестра, однако управление страничными файлами рекомендуется осуществлять через апплет "система" административной консоли управления. У каждого файла подкачки есть начальный и максимальный размер. С целью уменьшения вероятной фрагментации их создают максимального размера.

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

Рабочие наборы процессов

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

Согласно документации по ОС Windows, рабочим набором процесса называется совокупность физических страниц, выделенных процессу. Размер рабочего набора должен находиться в некоторых пределах, определяемых константами системы в зависимости от суммарного объема физической памяти. Например, если физической памяти достаточно, то рабочий набор процесса должен быть в диапазоне от 50 до 345 страниц. Имея привилегию Increase Scheduling Priority (о привилегиях см. часть V), эти значения можно менять при помощи функции SetProcessWorkingSet.

Если возникает страничная ошибка и рабочий набор процесса не превысил лимита (при слабой загруженности системы допускается даже превышение лимита), система выделяет ему еще один кадр в физической памяти. В противном случае ОС пытается заменять страницы в рабочем наборе этого процесса (локальный алгоритм замещения).

Эволюцию рабочего набора процесса можно "увидеть", наблюдая за счетчиками Working Set и др. в оснастке "Производительность", а также при помощи Диспетчера задач и утилит Pview, Pviewer и ряда других. Важно понимать, что изменение рабочих наборов является следствием страничных нарушений, которые происходят при фактическом обращении к страницам памяти. Простого выделения и передачи памяти здесь недостаточно.

Прогон программы, иллюстрирующей увеличение рабочего набора процесса

Рассмотрим легкую модификацию программы DemoVM, добавив туда операцию записи одного байта на каждую страницу переданной памяти (программа DemoPageFaults.c).

#include <windows.h>
#include <stdio.h>

void main(void)
{

PVOID pMem = NULL;
int nPageSize = 4096;
int nPages = 200;
long SizeCommit = 0;
int i;
char * Ptr;

SizeCommit = nPages * nPageSize;
getchar();
pMem =  VirtualAlloc(0, SizeCommit, MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
Ptr = (char *)pMem;
for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';
getchar();

pMem =  VirtualAlloc(0, SizeCommit, MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
Ptr = (char *)pMem;
for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';
getchar();

pMem =  VirtualAlloc(0, SizeCommit, MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
Ptr = (char *)pMem;
for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';
getchar();

pMem =  VirtualAlloc(0, SizeCommit, MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
Ptr = (char *)pMem;
for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';
getchar();
}

Наращивание объема переданной памяти и размера рабочего набора будет происходить по нажатию клавиши "Enter". Посмотрим на поведение счетчика "Рабочее множество" для процессов DemoVM и DemoPageFaults. Несмотря на одинаковый объем переданной физической памяти, размеры рабочего набора сильно отличаются. У DemoVM он остается близким к нулю, тогда как у процесса DemoPageFaults идет заметное ступенчатое приращение рабочего набора (см. рис 10.5)

(рис 10.5) Наблюдение за изменениями рабочих наборов процессов

Замещение страниц в рабочем наборе процесса - одна из наиболее ответственных операций. Дело в том, что уменьшение частоты page fault`ов является одной из ключевых задач системы управления памятью (например, известно, что вероятности page fault'а ).

В ОС Windows используются алгоритмы FIFO (first input first output) в многопроцессорном варианте и LRU - в однопроцессорном. На самом деле применяется не LRU, а его программная реализация NFU (not frequently used), согласно которой страница характеризуется не давностью, а частотой использования. Однако, согласно документации по ОС Windows, алгоритм, осуществляющий модификацию размера рабочего набора процесса, называется именно LRU. Что касается алгоритма FIFO, несмотря на известные недостатки, его применение упрощает обработку ссылок на страницу от нескольких процессоров.

База данных PFN. Страничные демоны

В процессе функционирования операционной системы в физической памяти располагаются рабочие наборы процессов, системный рабочий набор, свободные фрагменты и многое другое. Для учета состояния физической памяти поддерживается база данных PFN (page frame number). Это таблица записей фиксированной длины. Количество записей в ней совпадает с количеством страничных кадров.

Известно, что подсистема виртуальной памяти работает производительно при наличии резерва свободных страничных кадров. Тогда в случае страничной ошибки требуется только одна дисковая операция (чтение), и свободная страница может быть найдена немедленно. Алгоритмы, обеспечивающие поддержку системы в оптимальном состоянии, реализованы в составе фоновых процессов (их часто называют демонами или сервисами), которые периодически "просыпаются" и инспектируют состояние памяти. Их задача - обеспечивать достаточное количество свободных страниц, поддерживая систему в состоянии наилучшей производительности.

Формально, каждая страница физической памяти должна находиться в составе рабочего набора или входить в один из поддерживаемых базой связных списков страниц. Перемещение страниц между списками и рабочими наборами осуществляется системными потоками-демонами, входящими в состав менеджера памяти (см. ). Параметры настройки демонов хранятся в разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management реестра

Чаще всего для обслуживания ошибки страницы в соответствии с требованиями защиты уровня C2 (см. часть V) требуется обнуленная страница, которая извлекается из соответствующего списка. Список обнуленных страниц пополняется потоком обнуления страниц (zero page thread) в фоновом режиме за счет списка свободных страниц. Иногда, например, для отображения файла, обнуленные страницы не нужны, и можно обойтись свободными страницами. Если у рабочего набора процесса отбирается страница, она попадает в список модифицированных страниц или в список свободных страниц. Подсистема записи модифицированных страниц (modified page writer) записывает их содержание на диск, когда количество таких страниц превышает установленный лимит. Страницы проецируемого файла можно сбросить на диск явным образом (при помощи функции FlushViewOfFile ). После записи модифицированная страница попадает в список свободных страниц.

Общее руководство и реализацию общих правил управления памятью осуществляет диспетчер рабочих наборов (working set manager), который вызывается системным потоком ядра - диспетчером настройки баланса - раз в секунду или при уменьшении объема свободной памяти ниже порогового значения.

Эксперимент. Наблюдение за ошибками страниц

Количество ошибок страниц, генерируемых процессом, можно наблюдать при помощи счетчика "Ошибок страницы". На рис. приведены графики поведения счетчиков "Ошибок страниц" и "Рабочее множество" для процесса DempPageFaults. (см. программу, описанную выше)

(рис 10.6) Наблюдение за размером рабочего набора процесса и количеством страничных ошибок

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

При помощи утилиты Pfmon.exe из ресурсов Windows можно не только "увидеть" общее количество страничных нарушений, но и определить виртуальные адреса, обращения к которым эти нарушения спровоцировали. На примере 10.1 приведен фрагмент результатов работы данной утилиты для процесса DemoPageFaults.

…
SOFT: RtlFillMemoryUlong+0x10 : 0x00232000
SOFT: RtlFillMemoryUlong+0x10 : 0x00233000
SOFT: GetConsoleInputWaitHandle+0x11a : GetConsoleInputWaitHandle+0x119
SOFT: FindFirstFileExA+0x285 : FindFirstFileExA+0x285
SOFT: main+0xe4 : 0x00440000
SOFT: main+0xe4 : 0x00441000
SOFT: main+0xe4 : 0x00442000
SOFT: main+0xe4 : 0x00443000
SOFT: main+0xe4 : 0x00444000
SOFT: main+0xe4 : 0x00445000
SOFT: main+0xe4 : 0x00446000
SOFT: main+0xe4 : 0x00447000
SOFT: main+0xe4 : 0x00448000
SOFT: main+0xe4 : 0x00449000
SOFT: main+0xe4 : 0x0044a000
SOFT: main+0xe4 : 0x0044b000
SOFT: main+0xe4 : 0x0044c000
SOFT: main+0xe4 : 0x0044d000
SOFT: main+0xe4 : 0x0044e000
SOFT: main+0xe4 : 0x0044f000
SOFT: main+0xe4 : 0x00450000
SOFT: main+0xe4 : 0x00451000
SOFT: main+0xe4 : 0x00452000
SOFT: main+0xe4 : 0x00453000
SOFT: main+0xe4 : 0x00454000
SOFT: main+0xe4 : 0x00455000
…

Отдельные аспекты функционирования менеджера памяти

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

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

Локализация страниц в памяти

По умолчанию, процессу разрешается блокировать максимум 30 страниц памяти. Если увеличить рабочее множество процесса при помощи функции SetProcessWorkingSetSize, то, согласно документации, максимальное число страниц, которое процесс может блокировать, равно минимальному размеру его рабочего набора за вычетом 8 страниц.

Локализация страниц в памяти осуществляется при помощи Win32 функции VirtualLock, а освобождение страниц - при помощи VirtualUnlock. Учет локализованных страниц ведется в страничной базе PFN.

Прогон программы, демонстрирующей блокировку страниц в памяти

Приведенный листинг является примером такой программы.

#include <windows.h>
#include <stdio.h>

void main(void)
{

PVOID pMem = NULL;
int nPageSize = 4096;
int nPages = 400;
int nPageLock = 100;
long SizeCommit = 0;
int i;
char * Ptr;
int nMinPages = 200, nMaxPages = 500;
long dwMinimumWorkingSetSize = 0, dwMaximumWorkingSetSize = 0;
HANDLE hProcess;

hProcess = GetCurrentProcess();
dwMinimumWorkingSetSize = nMinPages * nPageSize;
dwMaximumWorkingSetSize = nMaxPages * nPageSize;

i = SetProcessWorkingSetSize(hProcess, dwMinimumWorkingSetSize, dwMaximumWorkingSetSize);
if(i==0) printf("SetProcessWorkingSetSize Error\n");

SizeCommit = nPages * nPageSize;

pMem =  VirtualAlloc(0, SizeCommit, MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");

Ptr = (char *)pMem;
for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';

i = VirtualLock(pMem, nPageLock * nPageSize);
if(i==0) printf("VirtualLock Error\n");

for(i=0; i<nPages; i++) Ptr[i*nPageSize] = '0';

VirtualUnlock(pMem, nPageLock * nPageSize);
VirtualFree(pMem, 0, MEM_RELEASE);
}

Копирование при записи

Другой нюанс в работе менеджера памяти, который можно проиллюстрировать на практике, связан с реализацией алгоритма отложенного выделения памяти - копирование при записи (copy-on-write). Это один из примеров алгоритма отложенной оценки (lazy evaluation), которые усложняют систему, но делают её более эффективной.

Рассмотрим ситуацию, когда некоторая приватная область памяти процесса является точной копией уже существующего в системе фрагмента памяти. Например, память дочернего процесса после вызова функции fork() в Unix является копией памяти родительского процесса. Другой пример - совместное использование динамической библиотеки, до тех пор, пока одна из программ не поменяла ее статические данные. В таких случаях разумно не выделять отдельную область памяти для процесса, а отображать в его адресное пространство уже существующую. Собственно выделение можно осуществить тогда, когда процесс приступит к изменению содержимого этой области. Эта техника называется копированием при записи.

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

Прогон программы, иллюстрирующей отложенное выделение памяти

#include <windows.h>
#include <stdio.h>

void main(void)
{
HANDLE hMapFile;
LPVOID lpMapAddress;
HANDLE hFile; 
char * String;
 
hFile = CreateFile("MyFile.txt",GENERIC_READ | GENERIC_WRITE,
		       FILE_SHARE_READ| FILE_SHARE_WRITE, NULL, OPEN_ALWAYS,
                    FILE_ATTRIBUTE_NORMAL,NULL);
 
if (hFile == INVALID_HANDLE_VALUE) printf("Could not open file\n"); 

hMapFile = CreateFileMapping(hFile, NULL, 
                             PAGE_WRITECOPY,  // копирование при записи                                        
                             0,0,"MyFileObject");
                              
if (hMapFile == NULL)  printf("Could not create file-mapping object.\n"); 

 lpMapAddress = MapViewOfFile(hMapFile, 
                              FILE_MAP_COPY, // копирование при записи							    
                              0,0,0);
                              
if (lpMapAddress == NULL) printf("Could not map view of file.\n"); 

 String = (char *)lpMapAddress;
 getchar();
 sprintf(String, "Hello, world");
 printf("%s\n", String);

if (!UnmapViewOfFile(lpMapAddress))  printf("Could not unmap view of file.\n"); 
}

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

Контроль процессом памяти другого процесса

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

Для доступа к памяти процесса нужно получить его описатель. Наиболее естественный способ получения описателя - получение описателя дочернего процесса путем извлечения его из параметра lProcessInformation функции CreateProcess.

Для создания регионов в памяти другого процесса можно использовать функцию VirtualAllocEx, которой нужно передать описатель этого процесса в качестве параметра. Для доступа к памяти другого процесса применяются функции ReadProcessMemory и WriteProcessMemory.

Написание, компиляция и выполнение программы, осуществляющей доступ к памяти дочернего процесса

Рекомендуется самостоятельно написать программу, которая создает регион памяти в адресном пространстве дочернего процесса и записывает в него текстовую строку. Задача дочернего процесса - вывести эту строку на экран.

Заключение

Базовой операцией менеджера памяти является трансляция виртуального адреса в физический с помощью таблицы страниц и ассоциативной (TLB) памяти. В ряде случаев, для реализации разделяемой памяти, интеграции с системой ввода-вывода и др., применяется прототипная таблица страниц, которая является промежуточным звеном между обычной таблицей страниц и физической памятью. Для описания страниц физической памяти поддерживается база данных PFN (page frame number). Локализацию страниц памяти, контроль процессом памяти другого процесса и технику копирования при записи можно отнести к интересным особенностям системы управления памятью ОС Windows.

Вернуться к учебному плану