Функционирование менеджера памяти
Прогон программы, демонстрирующей передачу информации от одного процесса к другому через разделяемую память
Рассмотрим текст двух программ 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(); }10.1.
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(); }10.2.
Программа 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)
Замещение страниц в рабочем наборе процесса - одна из наиболее ответственных операций. Дело в том, что уменьшение частоты page fault`ов является одной из ключевых задач системы управления памятью (например, известно, что вероятности page fault'а 5*10-7 оказывается достаточно, чтобы снизить производительность страничной схемы управления памятью на 10%.). Решение этой задачи связано с разумным выбором алгоритма замещения страниц. Если стратегия замещения выбрана правильно, то в оперативной памяти остается только самая актуальная информация, которая может понадобиться в недалеком будущем и которая не нуждается в замещении (на эту тему написано много книг, см., например, [ Карпов ] ).
В ОС 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), который вызывается системным потоком ядра - диспетчером настройки баланса - раз в секунду или при уменьшении объема свободной памяти ниже порогового значения.