Прикладной процесс использовать системную часть виртуальной памяти

Обновлено: 07.07.2024

Работа приложений с виртуальной памятью Архитектура интерфейсов управления памятью Файлы, отображаемые в память Кучи Заключение Литература Virtual memory API Работа приложений с виртуальной памятью Архитектура интерфейсов управления памятью Составной частью ядра операционной системы является VMM.

Работа приложений с виртуальной памятью

Архитектура интерфейсов управления памятью

Составной частью ядра операционной системы является VMM. Приложения не могут получить к VMM прямой доступ, поэтому для управления памятью им предоставляются различные программные интерфейсы (API). Их архитектура приведена на рис. 1.

Одни интерфейсы построены на использовании других. Их взаимосвязь изображена на рисунке стрелками. Ниже приведен список интерфейсов с комментариями:

Virtual Memory API - набор функций, позволяющих приложению работать с виртуальным адресным пространством. Приложение может назначать физические страницы блоку адресов и освобождать их, а также устанавливать атрибуты защиты (см. врезку "Virtual Memory API");

Memory Mapped File API - набор функций использования файлов, отображаемых в память. Этот новый с точки зрения классического устройства ОС механизм предоставляется Win32 API для работы с файлами и взаимодействия процессов между собой;

Heap Memory API - набор функций для управления динамически распределяемыми областями памяти (кучами). Интерфейс построен с помощью Virtual Memory API;

Local, Global Memory API - программный интерфейс для работы с памятью, совместимый с 16-разрядной Windows (лучше его не использовать);

CRT Memory API - функции стандартной библиотеки времени исполнения языка Cи (C Run Time library).

Два последних набора функций в данной статье не рассматриваются.

Файлы, отображаемые в память

Файлы, отображаемые в память, - это один из самых замечательных сервисов, которые Win32 предоставляет программисту. Его существование стирает для программиста грань между оперативной и дисковой памятью. Действительно, с точки зрения классической теории кэш, оперативная память и дисковое пространство - это три вида памяти, отличающиеся скоростью доступа и размером. Но если заботу о перемещении данных между кэшем и оперативной памятью берут на себя процессор и операционная система, то перемещение данных между оперативной памятью и диском обычно выполняет прикладной процесс с использованием функций read() и write(). Win32 действует иначе: операционная система берет на себя заботу о перемещении страниц адресного пространства процесса, находящихся в файле подкачки, причем в качестве файла подкачки может быть использован любой файл. Иначе говоря, страницы виртуальной памяти любого процесса могут быть помечены как выгруженные, а в качестве места, куда они выгружены, может быть указан файл. Теперь при обращении к такой странице VMM произведет ее загрузку, используя стандартный механизм свопинга. Это позволяет работать с произвольным файлом как с регионом памяти. Данный механизм имеет в Win32 три применения:

  • для запуска исполняемых файлов (EXE) и динамически связываемых библиотек (DLL);
  • для работы с файлами;
  • для совместного использования одной области данных двумя процессами.

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

Рассмотрим механизм запуска программы на выполнение более подробно. При исполнении функции CreateProcess система обращается к VMM для выполнения следующих действий:

  1. Создать адресное пространство процесса.
  2. Зарезервировать в адресном пространстве процесса регион размером, достаточным для размещения исполняемого файла. Начальный адрес региона берется из заголовка EXE-модуля. Обычно он равен 0x00400000, но может быть изменен при построении файла заданием параметра /BASE компоновщика.
  3. Отобразить исполняемый файл на зарезервированное адресное пространство. Тем самым VMM распределяет физические страницы не из файла подкачки, а непосредственно из EXE-модуля.
  4. Отобразить в адресное пространство процесса необходимые ему динамически связываемые библиотеки. Информация о необходимых библиотеках читается из заголовка EXE-модуля. Желательное расположение региона адресов описано внутри отображаемых библиотек. Visual C++, например, по умолчанию устанавливает для своей библиотеки адрес 0x10000000. Этот адрес может тоже изменяться параметром /BASE компоновщика. Если при загрузке выясняется, что данный регион занят, то система попытается переместить библиотеку в другой регион адресов, согласуя это действие с настроечной информацией, содержащейся в DLL-модуле. Однако эта операция снижает эффективность системы, и кроме того, если при компоновке библиотеки настроечная информация удалена (параметр/FIXED), то загрузка становится вообще невозможной. Интересно, что все стандартные библиотеки Windows имеют фиксированный адрес загрузки, и каждая свой собственный.

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

Мы уже обсуждали выше, что Win32, используя технологию lazy evaluation, откладывает решение этой проблемы на максимально возможный срок. Все страницы адресного пространства процесса, на которые отображен EXE-файл, получают атрибут защиты PAGE_WRITECOPY. При попытке записи в такую страницу возникает исключение нарушения защиты, и VMM копирует страницу для обратившегося процесса. В дальнейшем эта страница всегда будет выгружаться в файл подкачки. После копирования происходит повторный старт команды, вызвавшей исключение.

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

Для открепления файла от адресного пространства процесса используется функция UnmapViewOfFile(), а для уничтожения объектов "файл" и "отображаемый файл" - функция CloseHandle.

Общая методика работы с отображаемыми файлами такова:

Общая область данных может быть создана не только путем проецирования файла, но и путем проецирования части файла подкачки. Для этого в функцию CreateFileMapping() необходимо передать в качестве параметра не дескриптор ранее открытого файла, а константу 1. В этом случае необходимо задать размеры выделяемой области. Кроме того, в параметре lpName можно задать имя глобального объекта в системе. Если это имя задается в системе впервые, то процессу выделяется новая область данных, а если имя было уже задано, то именованная область данных предоставляется для совместного использования.

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

Кучи (heaps) - это динамически распределяемые области данных. При порождении процесса ему предоставляется куча размером 1 Мбайт по умолчанию. Ее размер может изменяться параметром /HEAP при построении исполняемого модуля. Функции библиотеки времени исполнения компилятора (malloc(), free() и т. д.) используют возможности куч.

Для работы с кучей предназначены следующие функции:

HANDLE GetProcessHeap( VOID ) - для получения дескриптора кучи по умолчанию;

LPVOID HeapAlloc( HANDLE hHeap, DWORD dwFlags, DWORD dwSize ) - выделяющая блок памяти заданного размера из кучи и возвращающая указатель на этот блок;

LPVOID HeapReAlloc( HANDLE hHeap, DWORD dwFlags, LPVOID lpOldBlock, DWORD dwSize) - изменяющая размер выделенного блока памяти, при этом она может перемещать блок, если нет достаточного места для простого расширения;

BOOL HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem ) - освобождает выделенный блок памяти кучи.

Иногда имеет смысл пользоваться дополнительными кучами, создание которых производится функцией HANDLE HeapCreate(DWORD dwFlags, DWORD dwInitialSize, DWORD dwMaximumSize) . Целесообразно использовать дополнительные кучи для защиты друг от друга различных структур данных, для повышения эффективности управления памятью и др. В системах со страничной организацией отсутствует проблема фрагментации физической памяти, однако существует проблема фрагментации адресного пространства. В 4-Гбайт адресном пространстве эта проблема не актуальна, но она имеет значение в куче размером 1 Мбайт. Если элементы какой-либо структуры имеют один размер, а элементы другой структуры - другой, то полезно размещать эти структуры в разных кучах. Кроме того, дополнительные кучи могут быть применены и для уменьшения рабочего множества процесса. В соответствии с принципом локальности работа с разными структурами чаще всего происходит не одновременно. Границы элементов разных структур не выравниваются на границу страницы, поэтому обращение к элементам одной структуры вызывает подкачку всей страницы, а значит, и элементов другой структуры. Это увеличивает рабочее множество процесса.

Заключение

Автор этих строк читает студентам лекции по курсу "Системное программное обеспечение". Саму дисциплину назвать новой никак нельзя. Теория организации вычислительного процесса сложилась уже к началу 70-х. Существовавшие в то время операционные системы давали массу примеров, позволяющих скрасить сухое академическое изложение. И сегодня по-прежнему излюбленной операционной системой для университетов является Unix, на которой воспитано не одно поколение студентов (в том числе и ваш покорный слуга). Никоим образом не умаляя достоинств Unix, можно с уверенностью утверждать, что Windows NT является ничуть не менее "классической" операционной системой в том смысле, что она доставляет примеры удачной реализации во всех разделах теории. Это не удивительно, ведь инженеры, создававшие Windows NT, были очень хорошо знакомы с такими системами, как Unix и Open VMS. При создании Windows NT было найдено много интереснейших технических решений, ряд из которых рассмотрен в данной статье, и название NT - New Technologies - можно считать вполне оправданным.

Андрей Федоров
- генеральный директор Digital Design Microsoft.

Литература

  1. Дейтел Г. Введение в операционные системы. М.: Мир.
  2. Донован Дж. Системное программирование. М.: Мир, 1975.
  3. Changes and Additions to the Alpha Architecture Definition. September 18, 1996.
  4. Randy Kath. The Virtual-Memory Manager in Windows NT. MSDN. Created: December 21, 1992.
  5. Pentium Pro Family Developer's Manual. Volume 3: Operating System Writer's Guide.
  6. How Windows NT Provides 4 Gigabytes of Memory. MSDN Knowledge Base. Article ID: Q99707. Creation Date: 06-JUN-1993. Revision Date: 17-JAN-1995.
  7. Рихтер Д. Windows для профессионалов. М.: изд. отд. "Русская редакция" ТОО Channel Trading Ltd., 1995.
  8. Working Set Size, Nonpaged Pool, and VirtualLock(). MSDN Knowledge Base. Article ID: Q108449. Creation Date: 12-DEC-1993. Revision Date: 02-NOV-1995.

Virtual memory API

Блок адресов в адресном пространстве процесса может находиться в одном из трех состояний:

  • выделен (committed) - блоку адресов назначена физическая память либо часть файла подкачки;
  • зарезервирован (reserved) - блок адресов помечен как занятый, но физическая память не распределена;
  • свободен (free) - блок адресов не выделен и не зарезервирован.

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

Для резервирования региона памяти в адресном пространстве процесса или ее выделения используется функция VirtualAlloc(), а для освобождения - функция VirtualFree():

Эта функция возвращает адрес выделенного региона, а в случае неудачи возвращает NULL. Параметры функции:

lpAddress - адрес, по которому надо зарезервировать или выделить память. Если этот параметр равен NULL, то система самостоятельно выбирает место в адресном пространстве процесса;

dwSize - размер выделяемого региона;

flAllocationType - тип распределения памяти;

flProtect - тип защиты доступа выделяемого региона:

PAGE_READONLY - допускается только чтение;

PAGE_READWRITE - допускается чтение и запись;

PAGE_EXECUTE - допускается только выполнение;

PAGE_EXECUTE_READ - допускается исполнение и чтение;

PAGE_EXECUTE_READWRITE - допускается выполнение, чтение и запись;

PAGE_GUARD - дополнительный флаг защиты, который комбинируется с другими флагами. При первом обращении к странице этот флаг сбрасывается и возникает исключение STATUS_GUARD_PAGE. Этот флаг используется для контроля размеров стека с возможностью его динамического расширения;

PAGE_NOCACHE - запрещает кэширование страниц. Может быть полезен при разработке драйверов устройств (например, данные в видеобуфер должны переписываться сразу, без кэширования).

Возвращает TRUE в случае успеха и FALSE в случае неудачи. Параметры:

lpAddress - адрес региона, который надо освободить;

dwSize - размер освобождаемого региона;

dwFreeType - тип освобождения.

Параметр flAllocationType может принимать следующие значения:

MEM_RESERVE - резервирует блок адресов без выделения памяти;

MEM_COMMIT - отображает ранее зарезервированный блок адресов на физическую память или файл подкачки, выделяя при этом память. Может комбинироваться с флагом MEM_RESERVE для одновременного резервирования и выделения;

MEM_TOP_DOWN - выделяет память по наибольшему возможному адресу. Имеет смысл только при lpAddress = NULL. В Windows 95 игнорируется.

MEM_DECOMMIT - освободить выделенную память;

MEM_RELEASE - освободить зарезервированный регион. При использовании этого флага параметр dwSize должен быть равен нулю.

Выделенные страницы можно заблокировать в памяти, т. е. запретить их вытеснение в файл подкачки. Такие страницы остаются в составе рабочего множества процесса до того момента, как будут разблокированы. Для этих целей служит пара функций VirtualLock() и VirtualUnlock(). Процессу не разрешается блокировать более 30 страниц. Для настройки рабочего множества процесса может использоваться и функция SetProcessWorkingSetSize() [8]. Формально она не входит в состав Virtual Memory API, но тесно с ним связана. Например, использование этой функции снимет барьер 30 страниц для функции VirtualLock().

Для изменения атрибутов защиты регионов используются функции VirtualProtect() и VirtualProtectEx(). Причем первая позволяет изменять атрибуты защиты в адресном пространстве текущего процесса, а вторая - произвольного.

Функции VirtualQuery() и VirtualQueryEx() позволяют определить статус указанного региона адресов.


Привет, Хабрахабр!

В предыдущей статье я рассказал про vfork() и пообещал рассказать о реализации вызова fork() как с поддержкой MMU, так и без неё (последняя, само собой, со значительными ограничениями). Но прежде, чем перейти к подробностям, будет логичнее начать с устройства виртуальной памяти.

Конечно, многие слышали про MMU, страничные таблицы и TLB. К сожалению, материалы на эту тему обычно рассматривают аппаратную сторону этого механизма, упоминая механизмы ОС только в общих чертах. Я же хочу разобрать конкретную программную реализацию в проекте Embox. Это лишь один из возможных подходов, и он достаточно лёгок для понимания. Кроме того, это не музейный экспонат, и при желании можно залезть “под капот” ОС и попробовать что-нибудь поменять.

Любая программная система имеет логическую модель памяти. Самая простая из них — совпадающая с физической, когда все программы имеют прямой доступ ко всему адресному пространству.
При таком подходе программы имеют доступ ко всему адресному пространству, не только могут “мешать” друг другу, но и способны привести к сбою работы всей системы — для этого достаточно, например, затереть кусок памяти, в котором располагается код ОС. Кроме того, иногда физической памяти может просто не хватить для того, чтобы все нужные процессы могли работать одновременно. Виртуальная память — один из механизмов, позволяющих решить эти проблемы. В данной статье рассматривается работа с этим механизмом со стороны операционной системы на примере ОС Embox. Все функции и типы данных, упомянутые в статье, вы можете найти в исходном коде нашего проекта.

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

  • Расширение реального адресного пространства. Часть виртуальной памяти может быть вытеснена на жёсткий диск, и это позволяет программам использовать больше оперативной памяти, чем есть на самом деле.
  • Создание изолированных адресных пространств для различных процессов, что повышает безопасность системы, а также решает проблему привязанности программы к определённым адресам памяти.
  • Задание различных свойств для разных участков участков памяти. Например, может существовать неизменяемый участок памяти, видный нескольким процессам.

Аппаратная поддержка

Обращение к памяти хорошо описанно в этой хабростатье. Происходит оно следующим образом:

Процессор подаёт на вход MMU виртуальный адрес
Если MMU выключено или если виртуальный адрес попал в нетранслируемую область, то физический адрес просто приравнивается к виртуальному
Если MMU включено и виртуальный адрес попал в транслируемую область, производится трансляция адреса, то есть замена номера виртуальной страницы на номер соответствующей ей физической страницы (смещение внутри страницы одинаковое):
Если запись с нужным номером виртуальной страницы есть в TLB [Translation Lookaside Buffer], то номер физической страницы берётся из нее же
Если нужной записи в TLB нет, то приходится искать ее в таблицах страниц, которые операционная система размещает в нетранслируемой области ОЗУ (чтобы не было промаха TLB при обработке предыдущего промаха). Поиск может быть реализован как аппаратно, так и программно — через обработчик исключения, называемого страничной ошибкой (page fault). Найденная запись добавляется в TLB, после чего команда, вызвавшая промах TLB, выполняется снова.

Таким образом, при обращении программы к тому или иному участку памяти трансляция адресов производится аппаратно. Программная часть работы с MMU — формирование таблиц страниц и работа с ними, распределение участков памяти, установка тех или иных флагов для страниц, а также обработка page fault, ошибки, которая происходит при отсутствии страницы в отображении.

В тексте статьи в основном будет рассматриваться трёхуровневая модель памяти, но это не является принципиальным ограничением: для получения модели с бóльшим количеством уровней можно действовать аналогичным образом, а особенности работы с меньшим количеством уровней (как, например, в архитектуре x86 — там всего два уровня) будут рассмотрены отдельно.

Программная поддержка

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

Виртуальный адрес

Page Global Directory (далее — PGD) — таблица (здесь и далее — то же самое, что директория) самого высокого уровня, каждая запись в ней — ссылка на Page Middle Directory (PMD), записи которой, в свою очередь, ссылаются на таблицу Page Table Entry (PTE). Записи в PTE ссылаются на реальные физические адреса, а также хранят флаги состояния страницы.


То есть, при трёхуровневой иерархии памяти виртуальный адрес будет выглядеть так:

Значения полей PGD, PMD и PTE — это индексы в соответствующих таблицах (то есть сдвиги от начала этих таблиц), а offset — это смещение адреса от начала страницы.

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

Для обеспечения переносимости мы задали границы этих полей с помощью констант: MMU_PGD_SHIFT, MMU_PMD_SHIFT, MMU_PTE_SHIFT, которые в приведённой выше схеме равны 24, 18 и 12 соответственно их определение дано в заголовочном файле src/include/hal/mmu.h. В дальнейшем будет рассматриваться именно этот пример.

На основании сдвигов PGD, PMD и PTE вычисляются соответствующие маски адресов.


Эти макросы даны в том же заголовочном файле.

Для работы с виртуальной таблицами виртуальной памяти в некоторой области памяти хранятся указатели на все PGD. При этом каждая задача хранит в себе контекст struct mmu_context, который, по сути, является индексом в этой таблице. Таким образом, к каждой задаче относится одна таблица PGD, которую можно определить с помощью mmu_get_root(ctx).

Размер страницы

В реальных (то есть не в учебных) системах используются страницы от 512 байт до 64 килобайт. Чаще всего размер страницы определяется архитектурой и является фиксированным для всей системы, например — 4 KiB.

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

С другой стороны, чем меньше размер страницы, тем больше размер страничных таблиц. Более того, при отгрузке на HDD и при чтении страниц с HDD быстрее получится записать несколько больших страниц, чем много маленьких такого же суммарного размера.

Отдельного внимания заслуживают так называемые большие страницы: huge pages и large pages [вики] .

Платформа Размер обычной страницы Размер страницы максимально возможного размера
x86 4KB 4MB
x86_64 4KB 1GB
IA-64 4KB 256MB
PPC 4KB 16GB
SPARC 8KB 2GB
ARMv7 4KB 16MB

Действительно, при использовании таких страниц накладные расходы памяти повышаются. Тем не менее, прирост производительности программ в некоторых случаях может доходить до 10% [ссылка] , что объясняется меньшим размером страничных директорий и более эффективной работой TLB.

В дальнейшем речь пойдёт о страницах обычного размера.

Устройство Page Table Entry

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

  • MMU_PAGE_WRITABLE — Можно ли менять страницу
  • MMU_PAGE_SUPERVISOR — Пространство супер-пользователя/пользователя
  • MMU_PAGE_CACHEABLE — Нужно ли кэшировать
  • MMU_PAGE_PRESENT — Используется ли данная запись директории

Можно установить сразу несколько флагов:

Здесь vmem_page_flags_t — 32-битное значение, и соответствующие флаги берутся из первых MMU_PTE_SHIFT бит.

Трансляция виртуального адреса в физический

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

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

Пояснения к коду функции.
mmu_paddr_t — это физический адрес страницы, назначение mmu_ctx_t уже обсуждалось выше в разделе “Виртуальный адрес”.

С помощью функции vmem_get_idx_from_vaddr() находятся сдвиги в таблицах PGD, PMD и PTE.




Работа с Page Table Entry

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

Эти функции возвращают 1, если у соответствующей структуры установлен бит MMU_PAGE_PRESENT

Page Fault

Page fault — это исключение, возникающее при обращении к странице, которая не загружена в физическую память — или потому, что она была вытеснена, или потому, что не была выделена.
В операционных системах общего назначения при обработке этого исключения происходит поиск нужной странице на внешнем носителе (жёстком диске, к примеру).

В нашей системе все страницы, к которым процесс имеет доступ, считаются присутствующими в оперативной памяти. Так, например, соответствующие сегменты .text, .data, .bss; куча; и так далее отображаются в таблицы при инициализации процесса. Данные, связанные с потоками (например, стэк), отображаются в таблицы процесса при создании потоков.

Выталкивание страниц во внешнюю память и их чтение в случае page fault не реализовано. С одной стороны, это лишает возможности использовать больше физической памяти, чем имеется на самом деле, а с другой — не является актуальной проблемой для встраиваемых систем. Нет никаких ограничений, делающих невозможной реализацию данного механизма, и при желании читатель может попробовать себя в этом деле :)

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

Функция page_alloc() ищет участок памяти из N незанятых страниц и возвращает физический адрес начала этого участка, помечая его как занятый. В приведённом коде virt_page_allocator ссылается на участок памяти, резервированной для выделения физических страниц, а 1 — количество необходимых страниц.
Выделение таблиц
Тип таблицы (PGD, PMD, PTE) не имеет значения при аллокации. Более того, выделение таблиц производится также с помощью функции page_alloc(), только с другим аллокатором (virt_table_allocator).

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

brk — это самый большой из всех физических адресов репозитория, данное значение необходимо для ряда системных вызовов, которые не будут рассматриваться в данной статье.
ctx — это контекст задачи, использование которого обсуждалось в разделе “Виртуальный адрес”.
struct dlist_head — это указатель на начало двусвязного списка, организация которого аналогична организации Linux Linked List.

За каждый выделенный участок памяти отвечает структура marea


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

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

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

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


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

Основную работу на себя берёт do_map_region(). Она возвращает 0 при удачном выполнении и код ошибки — в ином случае. Если во время маппирования произошла ошибка, то часть страниц, которые успели выделиться, нужно откатить сделанные изменения с помощью функции vmem_unmap_region(), которая будет рассмотрена позднее.

Рассмотрим функцию do_map_region() подробнее.

Макросы GET_PTE и GET_PMD нужны для лучшей читаемости кода. Они делают следующее: если в таблице памяти нужный нам указатель не ссылается на существующую запись, нужно выделить её, если нет — то просто перейти по указателю к следующей записи.

В самом начале необходимо проверить, выровнены ли под размер страницы размер региона, физический и виртуальный адреса. После этого определяется PGD, соответствующая указанному контексту, и извлекаются сдвиги из виртуального адреса (более подробно это уже обсуждалось выше).
Затем последовательно перебираются виртуальные адреса, и в соответствующих записях PTE к ним привязывается нужный физический адрес. Если в таблицах отсутствуют какие-то записи, то они будут автоматически сгенерированы при вызове вышеупомянутых макросов GET_PTE и GET_PMD.

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

Все параметры функции, кроме последнего, должны быть уже знакомы. free_pages отвечает за то, должны ли быть удалены страничные записи из таблиц.

try_free_pte, try_free_pmd, try_free_pgd — это вспомогательные функции. При удалении очередной страницы может выясниться, что директория, её содержащая, могла стать пустой, а значит, её нужно удалить из памяти.

Исходный код функций try_free_pte, try_free_pmd, try_free_pgd


нужны как раз для случая двухуровневой иерархии памяти.

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

На рис. 5.29 приведена обобщенная схема работы кэш-памяти. Большая часть ветвей этой схемы уже была подробно рассмотрена выше, поэтому остановимся здесь только на некоторых особых случаях.

Из схемы видно, что когда выполняется запись, кэш просматривается только с целью согласования содержимого кэша и основной памяти. Если происходит промах, то запросы на запись не вызывают никаких изменений содержимого кэша. В некоторых же реализациях кэш-памяти при отсутствии данных в кэше они копируются туда из основной памяти независимо от того, выполняется за­прос на чтение или на запись.

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

При выполнении запросов к оперативной памяти во многих вычислительных системах используется двухуровневое кэширование (рис. 5.30).


Кэш первого уровня имеет меньший объем и более высокое быстродействие, чем кэш второго уровня. Кэш второго уровня играет роль основной памяти по отношению к кэшу первого уровня.

На рис. 5.31 показана схема выполнения запроса на чтение в системе с двухуров­невым кэшем.



Сначала делается попытка обнаружить данные в кэше первого уровня. Если произошел промах, поиск продолжается в кэше второго уровня. Если же нужные Данные отсутствуют и здесь, тогда происходит считывание дан­ных из основной памяти. Понятно, что время доступа к данным оказывается ми­нимальным, когда кэш-попадание происходит уже на первом уровне, несколько большим — при обнаружении данных на втором уровне и обычным временем доступа к оперативной памяти, если нужных данных нет ни в том, ни в другом кэше. При считывании данных из оперативной памяти происходит их копирова­ние в кэш второго уровня, а если данные считываются из кэша второго уровня, то они копируются в кэш первого уровня.

При работе такой иерархической организованной памяти необходимо обеспечить непротиворечивость данных на всех уровнях. Кэши разных уровней могут согла­совывать данные разными способами. Пусть, например, кэш первого уровня ис­пользует сквозную запись, а кэш второго уровня — обратную запись. (Именно такая комбинация алгоритмов согласования применена в процессоре Pentium при одном из возможных вариантов его работы.)

На рис. 5.32 приведена схема выполнения запроса на запись в такой системе.

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

Если данные обнаружены в кэше первого уровня, то вступает в силу алгоритм сквозной записи: выполняется запись в кэш первого уровня и передается запрос на запись в кэш второго уровня, играющий в данном случае роль основной памя­ти. Запись в кэш второго уровня в соответствии с алгоритмом обратной записи, принятом на данном уровне, сопровождается установкой признака модификации, при этом никакой записи в оперативную память не производится.


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

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

Выводы

· отслеживание наличия свободной и занятой памяти;

· выделение памяти процессам и освобождение памяти при завершении процессов;

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

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

· защита памяти процессов от взаимного вмешательства.

· страничная виртуальная память организует перемещение данных между памятью и диском страницами — частями виртуального адресного простран­ства фиксированного и сравнительно небольшого размера (достоинства — высокая скорость обмена, низкий уровень фрагментации; недостатки — сложно организовать защиту данных, разделенных на части механически);

· сегментная виртуальная память предусматривает перемещение данных сег­ментами — частями виртуального адресного пространства произвольного размера, полученными с учетом смыслового значения данных (достоинст­ва — «осмысленность» сегментов упрощает их защиту; недостатки — мед­ленное преобразование адреса, высокий уровень фрагментации);

· сегментно-страничная виртуальная память сочетает достоинства обоих пре­дыдущих подходов.

· значение элемента данных;

· адрес, который этот элемент данных имеет в основной памяти;

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

Задачи и упражнения

1. Чем ограничивается максимальный размер физической памяти, которую мож­но установить в компьютере определенной модели?

2. Чем ограничивается максимальный размер виртуального адресного простран­ства, доступного приложению?

3. Может ли прикладной процесс использовать системную часть виртуальной памяти?

4. Какое из этих двух утверждений верно?

а) все виртуальные адреса заменяются на физические во время загрузки программы в оперативную память;

в) виртуальные адреса заменяются на физические во время выполнения программы в момент обращения по данному виртуальному адресу.

5. В каких случаях транслятор создает объектный код программы не в вирту­альных, а в физических адресах?

6. Что такое виртуальная память? Какой из следующих методов распределения памяти может рассматриваться как частный случай виртуальной памяти?

а) распределение фиксированными разделами;

в) распределение динамическими разделами;

с) страничное распределение;

d) сегментное распределение;

е) сегментно-страничное распределение.

7. Распределение памяти перемещаемыми разделами основано на применении процедуры сжатия. Имеет ли смысл использовать данную процедуру при страничном распределении? А при сегментном?

8. Поясните разные значения термина «свопинг».

9. Как величина файла подкачки влияет на производительность системы?

10.Почему размер страницы выбирается равным степени двойки? Можно ли принять такое же ограничение для сегмента?

11.На что влияет размер страницы? Каковы преимущества и недостатки боль­шого размера страницы?

12.Пусть в некоторой программе, работающей в системе со страничной органи­зацией памяти, произошло обращение по виртуальному адресу 0123568. Преоб­разуйте этот адрес в физический, учитывая, что размер страницы равен 2 14 байт и что таблица страниц данного процесса содержит следующий фрагмент:

Номер виртуальной страницы Номер физической страницы
0000омер физической страницыыых из ______________

13.Где хранятся таблицы страниц и таблицы сегментов?

14.Чем определяется количество таблиц сегментов, имеющихся в операционной системе в произвольный момент времени?

15.Какие характеристики содержит таблица сегментов и таблица страниц при сегментно-страничной организации памяти?

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

17.В кэше хранятся данные, которые наиболее активно используются в послед­нее время. Каким образом система определяет, какие данные должны быть за­гружены в кэш?

18. Пусть программа циклически обрабатывает данные, то есть в некотором диа­пазоне адресов идет последовательное обращение к данным, а затем следует возврат в начало и т. д. В системе имеется кэш, объем которого меньше объема обрабатываемых программой данных. Какой алгоритм вытеснения данных из кэша в данном случае будет эффективнее?

а) выгружаются данные, которые не использовались дольше остальных;

в) выгружаются данные, выбранные случайным образом.

19. Почему загрузка и выгрузка данных из кэш-памяти производится блоками?

20. Как обеспечивается согласование данных в кэше с помощью методов обрат­ной и сквозной записи?

21. Известно, что с помощью программных конвейеров данными могут обмени­ваться только процессы-родственники. В то же время все процессы в UNIX являются родственниками, так как все они — потомки специального процес­са, инициализирующего систему. Почему же механизм программных конвей­еров не работает для двух произвольных процессов?

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

Виртуальная и физическая память Windows

Для каждого нового процесса, в операционной системе Windows, выделяется некоторый объём оперативной памяти. Процесс не обязательно должен использовать весь выделенный объем памяти, он может занять всего лишь часть. Этот объем памяти называется виртуальным адресным пространством.

Выделенная память для процесса

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

Виртуальную память так назвали, потому что процесс думает что он в операционной системе один. Процесс видит только выделенный ему объём памяти (своё виртуальное адресное пространство) и не знает сколько в системе реально физической памяти.

Соотношение виртуальной памяти с физической

В общем работу виртуальной и физической памяти можно представить, таким образом:

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

Размер виртуального адресного пространства теоретически ограничивается архитектурой компьютера. Но операционная система накладывает дополнительные ограничения.

Архитектура Теоретический пределРеальный предел для
системных компонентов Windows
Реальный предел для виртуального
адресного пространства процесса
32-разрядная4 ГБ2 ГБ2 ГБ
64-разрядная16 ЭБ =
= 17600000000 ГБ
128 ТБ =
= 128000 ГБ
128 ТБ =
= 128000 ГБ

Вы можете спросить, куда девается остальная память на 64-разрядной Windows? Она просто игнорируется, так как пока сложно себе представить такой объём оперативной памяти.

У физической памяти тоже есть лимит и он намного меньше чем лимиты для виртуальной памяти и составляет 24 ТБ.

Надеюсь вам стало понятнее зачем нужна виртуальная и физическая память Windows.

Читайте также: