Виртуальный адрес это адрес оперативной памяти
Виртуальным адресным пространством для процесса является набор адресов виртуальной памяти, который можно использовать. Адресное пространство для каждого процесса является закрытым и не может быть доступно другим процессам, если он не является общим.
Виртуальный адрес не представляет фактическое физическое расположение объекта в памяти; Вместо этого система ведет таблицу страниц для каждого процесса, который представляет собой внутреннюю структуру данных, используемую для преобразования виртуальных адресов в соответствующие физические адреса. Каждый раз, когда поток ссылается на адрес, система преобразует виртуальный адрес в физический адрес.
виртуальное адресное пространство для 32-разрядного Windows имеет размер 4 гигабайта (гб) и делится на две секции: одна для использования процессом, а другая зарезервирована для использования системой. дополнительные сведения о пространстве виртуальных адресов в 64-битном Windows см. в разделе виртуальное адресное пространство в 64-разрядной Windows.
Дополнительные сведения о виртуальной памяти см. в следующих разделах:
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 — это вспомогательные функции. При удалении очередной страницы может выясниться, что директория, её содержащая, могла стать пустой, а значит, её нужно удалить из памяти.
нужны как раз для случая двухуровневой иерархии памяти.
Конечно, данной статьи не достаточно, чтобы с нуля организовать работу с MMU, но, я надеюсь, она хоть немного поможет погрузиться в OSDev тем, кому он кажется слишком сложным.
Существует понятие физической оперативной памяти , с которой собственно и работает процессор, извлекая из неё команды и данные и помещая в неё результаты вычислений. Физическая память представляет собой упорядоченное множество пронумерованных ячеек. Количество ячеек физической памяти ограничено и фиксировано.
Множество всех допустимых значений виртуального адреса для некоторой программы определяет её виртуальное адресное пространство или виртуальную память .
Система программирования осуществляет трансляцию и компоновку программы, используя библиотечные программные модули. В результате полученные виртуальные адреса могут иметь двоичную или символьно-двоичную форму. Для тех программных модулей, адреса которых пока не могут быть определены и имеют по-прежнему символьную форму, окончательная привязка их к физическим ячейкам будет осуществлена на этапе загрузки программы в память перед её непосредственным выполнением.
Размер виртуального адресного пространства программы прежде всего зависит от архитектуры процессора и от системы программирования и практически не зависит от объёма реальной физической памяти.
виртуальная память - это совокупность программно-аппаратных средств, позволяющих пользователям писать программы, размер которых превосходит имеющуюся оперативную память; для этого виртуальная память решает следующие задачи:
- размещает данные в запоминающих устройствах разного типа, например, часть программы в оперативной памяти, а часть на диске;
- перемещает по мере необходимости данные между запоминающими устройствами разного типа, например, подгружает нужную часть программы с диска в оперативную память;
- преобразует виртуальные адреса в физические.
Все эти действия выполняются автоматически, без участия программиста, то есть механизм виртуальной памяти является прозрачным по отношению к пользователю.
Наиболее распространенными реализациями виртуальной памяти является страничное, сегментное и странично-сегментное распределение памяти, а также свопинг.
Суть концепции виртуальной памяти заключается в том, что адреса, к которым обращается выполняющийся процесс, отделяются от адресов, реально существующих в оперативной памяти.
Главной особенностью виртуальной памяти является преобразование (или трансляция) адреса, к которому производится обращение со стороны программы, в реальный адрес, причём это преобразование должно выполняться быстро. Механизмы динамического преобразования адресов (ДПА) преобразуют смежные адреса виртуальной памяти в необязательно смежные адреса реальной памяти. Таким образом, пользователь освобождается от необходимости учитывать размещение своих процедур и данных в реальной памяти. Программа обращается к виртуальной памяти по виртуальному адресу.
Преимущество ВП состоит в том, что объем ОЗУ не может быть увеличено ни
практически, ни теоретически. (Это попросту невозможно ни какими средствами
нельзя оптимизировать или преобразовать ячейки памяти, для того, чтобы, скажем,
помещать туда два бита информации вместо одного).
В настоящее время все множество реализаций виртуальной памяти может быть представлено тремя классами:
- страничная виртуальная память организует перемещение данных между памятью и диском страницами — частями виртуального адресного пространства фиксированного и сравнительно небольшого размера (достоинства — высокая скорость обмена, низкий уровень фрагментации; недостатки — сложно организовать защиту данных, разделенных на части механически);
- сегментная виртуальная память предусматривает перемещение данных сегментами — частями виртуального адресного пространства произвольного размера, полученными с учетом смыслового значения данных (достоинства — «осмысленность» сегментов упрощает их защиту; недостатки — медленное преобразование адреса, высокий уровень фрагментации);
- сегментно-страничная виртуальная память сочетает достоинства обоих предыдущих подходов.
Когда процессор считывает или записывает данные в расположение памяти, он использует виртуальный адрес. В рамках операции чтения или записи процессор преобразует виртуальный адрес в физический адрес.
Доступ к памяти через виртуальный адрес имеет следующие преимущества:
Программа может использовать непрерывный диапазон виртуальных адресов для доступа к большому буферу памяти, который не является непрерывным в физической памяти.
Программа может использовать диапазон виртуальных адресов для доступа к буферу памяти, который больше доступной физической памяти. По мере уменьшения объема физической памяти диспетчер памяти сохраняет страницы физической памяти (обычно 4 килобайта) в файл диска. При необходимости страницы данных или кода перемещаются между физической памятью и диском.
Виртуальные адреса, используемые различными процессами, изолированы друг от друга. Код в одном процессе не может изменить физическую память, используемую другим процессом или операционной системой.
Диапазон виртуальных адресов, доступных процессу, называется виртуальным адресным пространством для процесса. Каждый процесс пользовательского режима имеет собственное частное виртуальное адресное пространство.
Для 32-разрядного процесса виртуальное адресное пространство обычно представляет собой 2-гигабайтовый диапазон 0x00000000 через 0x7FFFFFFF.
Диапазон виртуальных адресов иногда называется диапазоном виртуальной памяти. Дополнительные сведения см. в разделе "Ограничения памяти и адресного пространства".
На этой схеме показаны некоторые ключевые функции виртуальных адресных пространств.
На схеме показаны виртуальные адресные пространства для двух 64-разрядных процессов: Notepad.exe и MyApp.exe. Каждый процесс имеет собственное виртуальное адресное пространство, которое переходит от 0x000 0000000 до 0x7FF'FFFFFFFF. Каждый затененных блок представляет одну страницу (размер 4 килобайта) виртуальной или физической памяти. Обратите внимание, что в процессе Блокнот используются три смежных страницы виртуальных адресов, начиная с 0x7F7'93950000. Но эти три смежных страницы виртуальных адресов сопоставляются с неконтентными страницами в физической памяти. Обратите внимание, что оба процесса используют страницу виртуальной памяти, начиная с 0x7F7'93950000, но эти виртуальные страницы сопоставляются с разными страницами физической памяти.
Настройка виртуального адресного пространства для 32-разрядного Windows
Можно использовать следующую команду, чтобы задать параметр записи загрузки, который настраивает размер раздела, который может использоваться процессом, в диапазоне от 2048 (2 ГБ) до 3072 (3 ГБ):
BCDEdit/Set инкреасеусерва мегабайт
После установки параметра загрузочной записи диапазон памяти для каждой секции выглядит следующим образом.
Диапазон памяти | Использование |
---|---|
Низкая (0x00000000 до мегабайт) | Используется процессом. |
Высокий (в мегабайтах+ 1 – 0xFFFFFFFF) | Используется системой. |
Windows Server 2003: Задайте для параметра /USERVA в boot.ini значение от 2048 до 3072.
Мне периодически приходится объяснять разным людям некоторые аспекты архитектуры Intel® IA-32, в том числе замысловатость системы адресации данных в памяти, которая, похоже, реализовала почти все когда-то придуманные идеи. Я решил оформить развёрнутый ответ в этой статье. Надеюсь, что он будет полезен ещё кому-нибудь.
При исполнении машинных инструкций считываются и записываются данные, которые могут находиться в нескольких местах: в регистрах самого процессора, в виде констант, закодированных в инструкции, а также в оперативной памяти. Если данные находятся в памяти, то их положение определяется некоторым числом — адресом. По ряду причин, которые, я надеюсь, станут понятными в процессе чтения этой статьи, исходный адрес, закодированный в инструкции, проходит через несколько преобразований.
На рисунке — сегментация и страничное преобразование адреса, как они выглядели 27 лет назад. Иллюстрация из Intel 80386 Programmers's Reference Manual 1986 года. Забавно, что в описании рисунка есть аж две опечатки: «80306 Addressing Machanism». В наше время адрес подвергается более сложным преобразованиям, а иллюстрации больше не делают в псевдографике.
Начнём немного с конца — с цели всей цепочки преобразований.
Размер страницы
В реальных (то есть не в учебных) системах используются страницы от 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.
В дальнейшем речь пойдёт о страницах обычного размера.
Аппаратная поддержка
Обращение к памяти хорошо описанно в этой хабростатье. Происходит оно следующим образом:
Процессор подаёт на вход MMU виртуальный адрес
Если MMU выключено или если виртуальный адрес попал в нетранслируемую область, то физический адрес просто приравнивается к виртуальному
Если MMU включено и виртуальный адрес попал в транслируемую область, производится трансляция адреса, то есть замена номера виртуальной страницы на номер соответствующей ей физической страницы (смещение внутри страницы одинаковое):
Если запись с нужным номером виртуальной страницы есть в TLB [Translation Lookaside Buffer], то номер физической страницы берётся из нее же
Если нужной записи в TLB нет, то приходится искать ее в таблицах страниц, которые операционная система размещает в нетранслируемой области ОЗУ (чтобы не было промаха TLB при обработке предыдущего промаха). Поиск может быть реализован как аппаратно, так и программно — через обработчик исключения, называемого страничной ошибкой (page fault). Найденная запись добавляется в TLB, после чего команда, вызвавшая промах TLB, выполняется снова.
Таким образом, при обращении программы к тому или иному участку памяти трансляция адресов производится аппаратно. Программная часть работы с MMU — формирование таблиц страниц и работа с ними, распределение участков памяти, установка тех или иных флагов для страниц, а также обработка page fault, ошибки, которая происходит при отсутствии страницы в отображении.
В тексте статьи в основном будет рассматриваться трёхуровневая модель памяти, но это не является принципиальным ограничением: для получения модели с бóльшим количеством уровней можно действовать аналогичным образом, а особенности работы с меньшим количеством уровней (как, например, в архитектуре x86 — там всего два уровня) будут рассмотрены отдельно.
Линейный адрес
Эффективный адрес — это смещение от начала сегмента — его базы. Если сложить базу и эффективный адрес, то получим число, называемое линейным адресом:
lin_addr = segment.base + eff_addr
Преобразование логический → линейный не всегда может быть успешным, так как при его исполнении проверяется несколько условий на свойства сегмента, записанных в полях его дескриптора. Например, проверяется выход за границы сегмента и права доступа.
Описанное выше верно при включенной сегментации. В 16-битном реальном режиме смысл селекторов другой, они хранят только базу, а преобразование не осуществляет сегментных проверок. Фактически, обозначения CS, DS, FS, GS, ES, SS имеют совершенно разный смысл в этих двух режимах, что добавляет путаницы.
Сегментация была модной на некотором этапе развития вычислительной техники. В настоящее она почти всюду была заменена другими механизмами, и используется только для специфических задач. Так, в режиме IA-32e (64-битном) только два сегмента могут иметь ненулевую базу. Для остальных четырёх в этом режиме всегда линейный адрес == эффективный.
Что такое виртуальный адрес?
В литературе и в документации других архитектур встречается ещё один термин — виртуальный адрес. Он не используется в документации Intel на IA-32, однако встречается, например, в описании Intel® Itanium, в котором сегментация не используется. Можно смело считать, что для IA-32 виртуальный == линейный.
В советской литературе по вычислительной технике этот вид адресов также именовался математическим.
Виртуальное адресное пространство по умолчанию для 32-разрядного Windows
В следующей таблице показан диапазон памяти по умолчанию для каждой секции.
Диапазон памяти | Использование |
---|---|
Низкая 2 ГБ (от 0x00000000 до 0x7FFFFFFF) | Используется процессом. |
Высокий 2 ГБ (от 0x80000000 до 0xFFFFFFFF) | Используется системой. |
Эффективный адрес
Эффективный адрес — это начало пути. Он задаётся в аргументах индивидуальной машинной инструкции, и вычисляется из значений регистров, смещений и масштабирующих коэффициентов, заданных в ней явно или неявно.
Например, для инструкции (ассемблер в AT&T-нотации)
addl %eax, 0x11(%ebp, %edx, 8)
эффективный адрес операнда-назначения будет вычислен по формуле:
eff_addr = EBP + EDX * 8 + 0x11
Устройство 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 бит.
Виртуальный адрес
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).
Программная поддержка
- Выделение физических страниц из некоторого зарезервированного участка памяти
- Внесение соответствующих изменений в таблицы виртуальной памяти
- Сопоставление участков виртуальной памяти с процессами, выделившими их
- Проецирование региона физической памяти на виртуальный адрес
Полная картина
Я попытался собрать все преобразования адреса в одну иллюстрацию. В ней преобразования обозначены стрелками, типы адресов обведены в рамки.
Как уже было сказано выше, каждое из преобразований может вернуть ошибку для адресов, не имеющих представления в следующем по цепочке виде. Устранение подобных проблем — это задача операционных систем и мониторов виртуальных машин, реализующих абстракцию виртуальной памяти.
Заключение
Эволюция, что в природе, что в технике — странная вещь. Она порождает неожиданные структуры, необъяснимые с точки зрения рационального проектирования. Её творения полны атавизмов, правила их поведения иногда почти полностью состоят из исключений. Для того, чтобы понять работу такой системы, часто требуется прокрутить её эволюцию с самого начала, и под нагромождениями всех слоёв найти истину в виде принципа: «ничего не выбрасывать». Я склонен считать архитектуру IA-32 замечательным примером эволюционного развития.
Вскоре после завершения написания этой статьи я натолкнулся на презентацию об архитектуре IBM System z, которая примечательна в том числе своей долгой и интересной историей поддержки виртуализации. В этом документе нашлось перечисление всех типов адресов памяти, используемых в System z:
Привет, Хабрахабр!
В предыдущей статье я рассказал про vfork() и пообещал рассказать о реализации вызова fork() как с поддержкой MMU, так и без неё (последняя, само собой, со значительными ограничениями). Но прежде, чем перейти к подробностям, будет логичнее начать с устройства виртуальной памяти.
Конечно, многие слышали про MMU, страничные таблицы и TLB. К сожалению, материалы на эту тему обычно рассматривают аппаратную сторону этого механизма, упоминая механизмы ОС только в общих чертах. Я же хочу разобрать конкретную программную реализацию в проекте Embox. Это лишь один из возможных подходов, и он достаточно лёгок для понимания. Кроме того, это не музейный экспонат, и при желании можно залезть “под капот” ОС и попробовать что-нибудь поменять.
Любая программная система имеет логическую модель памяти. Самая простая из них — совпадающая с физической, когда все программы имеют прямой доступ ко всему адресному пространству.
При таком подходе программы имеют доступ ко всему адресному пространству, не только могут “мешать” друг другу, но и способны привести к сбою работы всей системы — для этого достаточно, например, затереть кусок памяти, в котором располагается код ОС. Кроме того, иногда физической памяти может просто не хватить для того, чтобы все нужные процессы могли работать одновременно. Виртуальная память — один из механизмов, позволяющих решить эти проблемы. В данной статье рассматривается работа с этим механизмом со стороны операционной системы на примере ОС Embox. Все функции и типы данных, упомянутые в статье, вы можете найти в исходном коде нашего проекта.
Будет приведён ряд листингов, и некоторые из них слишком громоздки для размещения в статье в оригинальном виде, поэтому по возможности они будут сокращены и адаптированы. Также в тексте будут возникать отсылки к функциям и структурам, не имеющим прямого отношения к тематике статьи. Для них будет дано краткое описание, а более полную информацию о реализации можно найти на вики проекта.
- Расширение реального адресного пространства. Часть виртуальной памяти может быть вытеснена на жёсткий диск, и это позволяет программам использовать больше оперативной памяти, чем есть на самом деле.
- Создание изолированных адресных пространств для различных процессов, что повышает безопасность системы, а также решает проблему привязанности программы к определённым адресам памяти.
- Задание различных свойств для разных участков участков памяти. Например, может существовать неизменяемый участок памяти, видный нескольким процессам.
Трансляция виртуального адреса в физический
Как уже писалось выше, при обращении к памяти трансляция адресов производится аппаратно, однако, явный доступ к физическим адресам может быть полезен в ряде случаев. Принцип поиска нужного участка памяти, конечно, такой же, как и в 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.
Физический адрес
Конечный результат всех преобразований других типов адресов, перечисленных далее в этой статье — физический адрес. На нём кончается работа внутри центрального процессора по преобразованию адресов.
На самом деле, легко понять, что это ещё не конец. В платформе, которая должна обработать запрос данных от процессора, может быть несколько чипов DRAM, имеющих собственную структуру разбиения на блоки, а также различные периферийные устройства, отображённые на общее пространство физической памяти. Дальнейший путь транзакции с некоторым физическим адресом будет зависеть от конфигурации нескольких декодеров, находящихся на её пути внутри устройств платформы.
Пространство пользователя и системное пространство
Такие процессы, как Notepad.exe и MyApp.exe выполняются в пользовательском режиме. Основные компоненты операционной системы и многие драйверы выполняются в более привилегированном режиме ядра. Дополнительные сведения о режимах процессора см. в разделе "Режим пользователя" и "Режим ядра".
Каждый процесс пользовательского режима имеет собственное частное виртуальное адресное пространство, но весь код, который выполняется в режиме ядра, использует одно виртуальное адресное пространство, называемое системным пространством. Виртуальное адресное пространство для процесса в пользовательском режиме называется пространством пользователя.
В 32-разрядном Windows общее доступное виртуальное адресное пространство составляет 2^32 байта (4 гигабайта). Обычно для пространства пользователя используются нижние 2 гигабайта, а верхние 2 гигабайта используются для системного пространства.
В 32-разрядной Windows вы можете указать (во время загрузки), что для пользовательского пространства доступно более 2 гигабайта. Следствием является то, что для системного пространства доступно меньше виртуальных адресов. Размер пространства пользователя можно увеличить до 3 гигабайт, в этом случае для системного пространства доступно только 1 гигабайт. Чтобы увеличить размер пользовательского пространства, используйте BCDEdit /set increaseuserva.
В 64-разрядной Windows теоретический объем виртуального адресного пространства составляет 2^64 байта (16 exabytes), но фактически используется только небольшая часть диапазона 16-exabyte.
Код, выполняемый в пользовательском режиме, имеет доступ к пространству пользователя, но не имеет доступа к системным пространствам. Это ограничение предотвращает чтение или изменение защищенных структур данных операционной системы в пользовательском режиме. Код, выполняемый в режиме ядра, имеет доступ как к пространству пользователя, так и к системным пространствам. То есть код, выполняющийся в режиме ядра, имеет доступ к системным пространствам и виртуальному адресном пространству текущего процесса пользовательского режима.
Драйверы, которые выполняются в режиме ядра, должны быть очень осторожны при непосредственном чтении или записи в адреса в пользовательском пространстве. В этом сценарии показано, почему.
Программа пользовательского режима инициирует запрос на чтение некоторых данных с устройства. Программа предоставляет начальный адрес буфера для получения данных.
Подпрограмма драйвера устройства, запущенная в режиме ядра, запускает операцию чтения и возвращает управление вызывающей программе.
Позже устройство прерывает работу любого потока, чтобы сказать, что операция чтения завершена. Прерывание обрабатывается подпрограммами драйвера в режиме ядра, выполняемыми в этом произвольном потоке, который принадлежит произвольному процессу.
На этом этапе драйвер не должен записывать данные в начальный адрес, предоставленный программой пользовательского режима на шаге 1. Этот адрес находится в виртуальном адресном пространстве процесса, который инициировал запрос, который, скорее всего, не совпадает с текущим процессом.
Логический адрес
Без знания номера и параметров сегмента, в котором указан эффективный адрес, последний бесполезен. Сам сегмент выбирается ещё одним числом, именуемым селектором. Пара чисел, записываемая как selector:offset , получила имя логический адрес. Так как активные селекторы хранятся в группе специальных регистров, чаще всего вместо первого числа в паре записывается имя регистра, например, ds:0x11223344.
Здесь обычно у тех, кто столкнулся с этими понятиями впервые, голова начинает идти кругом. Несколько упростить (или усложнить) ситуацию помогает тот факт, что почти всегда выбор селектора (и связанного с ним сегмента) делается исходя из «смысла» доступа. По умолчанию, если в кодировке машинной инструкции не сказано иного, для получения адресов кода используются логические адреса с селектором CS, для данных — с DS, для стека — с SS.
виртуальное адресное пространство для 32-разрядного Windows с 4GT
Если включена Настройка 4 гигабайта (4GT), диапазон памяти для каждой секции выглядит следующим образом.
Диапазон памяти | Использование |
---|---|
С низким 3 ГБ (0x00000000 до 0xBFFFFFFF) | Используется процессом. |
Высокий 1 ГБ (0xC0000000 до 0xFFFFFFFF) | Используется системой. |
После включения 4GT процесс, имеющий установленный в заголовке изображения флаг с большим количеством _ файлов _ _ _ изображений , будет иметь доступ к дополнительному объему памяти (1 ГБ), превышающему младшие 2 ГБ.
Страничное преобразование
Следующее после сегментации преобразование адресов: линейный → физический — имеет множество вариаций в своём алгоритме, в зависимости от того, в каком режиме (32-битном, PAE или 64-битном) находится процессор.
Примечательно, сколько различных бит из разных системных регистров процессора влияют на процесс страничного преобразования в настоящее время. Я просмотрел свежую сентябрьскую редакцию Intel SDM [1], и вот полный список: CR0.WP, CR0.PG, CR4.PSE, CR4.PAE, CR4.PGE, CR4.PCIDE, CR4.SMEP, CR4.SMAP, IA32_EFER.LME, IA32_EFER.NXE, EFLAGS.AC.
Однако общая идея всегда одна и та же: линейный адрес разбивается на несколько частей, каждая из которых служит индексом в одной из системных таблиц, хранящихся в памяти. Записи в таблицах — это адреса начала таблицы следующего уровня или, для последнего уровня — искомая информация о физическом адресе страницы в памяти и её свойствах. Самые младшие биты не преобразуются, а используются для адресации внутри найденной страницы. Например, для режима PAE с размером страниц 4 кбайт преобразование выглядит так:
В разных режимах процессора различается число и ёмкость этих таблиц. Преобразование может завершиться неудачей, если очередная таблица не содержит валидных данных, или права доступа, хранящиеся в последней из них, запрещают доступ к странице; например, при записи в регионы, помеченные как «только для чтения», или попытке чтения памяти ядра из непривилегированного процесса.
Работа с Page Table Entry
Для работы с записей в таблице страниц, а так же с самими таблицами, есть ряд функций:
Эти функции возвращают 1, если у соответствующей структуры установлен бит MMU_PAGE_PRESENT
Гостевой физический
До введения возможностей аппаратной виртуализации в процессорах Intel страничное преобразование было последним в цепочке. Когда же на одной системе работают несколько виртуальных машин, то физические адреса, получаемые в каждой из них, приходится транслировать ещё один раз. Это можно делать программным образом, или же аппаратно, если процессор поддерживает функциональность EPT (англ. Extended Page Table). Адрес, раньше называвшийся физическим, был переименован в гостевой физический для того, чтобы отличать его от настоящего физического. Они связаны с помощью EPT-преобразования. Алгоритм последнего схож с ранее описанным страничным преобразованием: набор связанных таблиц с общим корнем, последний уровень которых определяет, существует ли физическая страница для указанной гостевой физической.
Разграждаемый пул и непагированный пул
В пользовательском пространстве все страницы физической памяти можно вывести на диск при необходимости. В системном пространстве некоторые физические страницы можно вывести, а другие — невозможно. Системное пространство содержит два региона для динамического выделения памяти: разбиение на страницы пула и непагированного пула.
Память, выделенная в пуле страниц, можно вывести на дисковый файл по мере необходимости. Память, выделенная в непакованном пуле, никогда не может быть выгружается в файл диска.
Читайте также: