Что такое виртуальная адресация
Виртуальное адресное пространство (управление памятью)
Виртуальным адресным пространством для процесса является набор адресов виртуальной памяти, который можно использовать. Адресное пространство для каждого процесса является закрытым и не может быть доступно другим процессам, если он не является общим.
Виртуальный адрес не представляет фактическое физическое расположение объекта в памяти; Вместо этого система ведет таблицу страниц для каждого процесса, который представляет собой внутреннюю структуру данных, используемую для преобразования виртуальных адресов в соответствующие физические адреса. Каждый раз, когда поток ссылается на адрес, система преобразует виртуальный адрес в физический адрес.
виртуальное адресное пространство для 32-разрядного Windows имеет размер 4 гигабайта (гб) и делится на две секции: одна для использования процессом, а другая зарезервирована для использования системой. дополнительные сведения о пространстве виртуальных адресов в 64-битном Windows см. в разделе виртуальное адресное пространство в 64-разрядной Windows.
Дополнительные сведения о виртуальной памяти см. в следующих разделах:
Виртуальное адресное пространство по умолчанию для 32-разрядного Windows
В следующей таблице показан диапазон памяти по умолчанию для каждой секции.
Диапазон памяти | Использование |
---|---|
Низкая 2 ГБ (от 0x00000000 до 0x7FFFFFFF) | Используется процессом. |
Высокий 2 ГБ (от 0x80000000 до 0xFFFFFFFF) | Используется системой. |
виртуальное адресное пространство для 32-разрядного Windows с 4GT
Если включена Настройка 4 гигабайта (4GT), диапазон памяти для каждой секции выглядит следующим образом.
Диапазон памяти | Использование |
---|---|
С низким 3 ГБ (0x00000000 до 0xBFFFFFFF) | Используется процессом. |
Высокий 1 ГБ (0xC0000000 до 0xFFFFFFFF) | Используется системой. |
Настройка виртуального адресного пространства для 32-разрядного Windows
Можно использовать следующую команду, чтобы задать параметр записи загрузки, который настраивает размер раздела, который может использоваться процессом, в диапазоне от 2048 (2 ГБ) до 3072 (3 ГБ):
BCDEdit/Set инкреасеусерва мегабайт
После установки параметра загрузочной записи диапазон памяти для каждой секции выглядит следующим образом.
Виртуальный адрес
Виртуальный адрес — это адрес ячейки памяти с точки зрения программиста. Он может как совпадать, так и отличаться от физического адреса.
Виртуальный адрес в 32-разрядном режиме
В большинстве компьютерных архитектур виртуальный адрес является числом от нуля до некоего максимального значения, обычно определяемого разрядностью вычислительной машины, преобразуемым в физический адрес аппаратурой процессора. Архитектура IA-32 является одним из немногих исключений из этого правила. Во-первых, в программах используются сегментированные адреса, состоящие из 16-разрядного селектора сегмента и 32-разрядного смещения, т.е. адрес является не одним, а парой чисел. Во-вторых, фирма Intel не использует термин «виртуальный адрес», называя используемые в программах адреса логическими; таким образом, с точки зрения документации Intel, программист имеет дело именно с логическими адресами, а термин «виртуальный адрес» к архитектуре IA-32 вообще отношения не имеет. В-третьих, все операционные системы для архитектуры IA-32, поддерживающие механизм виртуальной памяти, а следовательно, оперирующие виртуальными адресами, пользуются механизмом сегментации IA-32 лишь в минимально необходимом объёме, предоставляя программам плоское адресное пространство, адресация внутри которого выполняется с помощью смещения, а селекторы сегментов остаются неизменными. Когда документация Windows или Linux говорит о передаче виртуальных адресов различным системным вызовам, во всех случаях передаётся только смещение из состава логического адреса.
Таким образом, можно считать, что термин «виртуальный адрес» применительно к 32-разрядным версиям распространённых ОС для платформы IA-32 является эквивалентом термина Intel «смещение, входящее в состав логического адреса для сегментов DS, SS, ES и CS» или другого, мало распространённого в реальной практике, но также используемого в документации Intel термина «эффективный адрес» (по отношению к тем же сегментам). Поскольку используется плоское адресное пространство с сегментами максимального размера (4 Гбайта), численно значение смещения (виртуального адреса) будет совпадать с линейным адресом, однако проводить знак равенства между терминами «виртуальный адрес» и «линейный адрес» некорректно, поскольку программист прямо работает именно с виртуальными адресами (в частности, передаёт их системным вызовам), а линейные адреса существуют лишь в глубинах процессора и для программиста недоступны в принципе.
Что касается сегментов FS и GS, они используются нестандартным образом. В Windows FS фактически является отправной точкой для доступа к TEB (в пользовательском режиме), т.к. адрес TEB в качестве базового адреса загружен в дескриптор сегмента, селектор которого хранится в FS. Сам TEB является частью «общего» сегмента кода, стека и данных, доступного через сегментные регистры CS, SS, DS и ES, поэтому доступ к нему можно получить и через указанные регистры, указав правильное смещение. Адресация через GS для прикладных программ недоступна вообще.
Виртуальный адрес в 64-разрядном режиме
В 64-разрядном режиме сегментация упразднена, поэтому программист работает с адресами, состоящими лишь из одного 64-разрядного числа и численно совпадающими с линейными адресами. Однако и здесь архитектура IA-32 является исключением из общего правила: линейный, а значит, и виртуальный адрес (логический в документации Intel) адрес фактически является числом со знаком, поскольку требуется, чтобы они были «каноническими», т.е. чтобы все старшие биты адреса были равны либо нулю, либо единице в зависимости от значения самого старшего физически реализованного разряда адреса. Например, в первых 64-разрядных микропроцессорах архитектуры IA-32 физически реализованы не 64-разрядные, а 48-разрядные линейные адреса, которые должны находиться в пределах от 0000_0000_0000_0000h до 0000_7FFF_FFFF_FFFFh или от FFFF_8000_0000_0000h до FFFF_FFFF_FFFF_FFFFh, т.е. как числа со знаком лежать в пределах от –2**47 до +2**47–1.
Организация виртуальной памяти
Привет, Хабрахабр!
В предыдущей статье я рассказал про 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 быстрее получится записать несколько больших страниц, чем много маленьких такого же суммарного размера.
В дальнейшем речь пойдёт о страницах обычного размера.
Устройство Page Table Entry
В реализации проекта Embox тип mmu_pte_t — это указатель.
Каждая запись PTE должна ссылаться на некоторую физическую страницу, а каждая физическая страница должна быть адресована какой-то записью PTE. Таким образом, в mmu_pte_t незанятыми остаются MMU_PTE_SHIFT бит, которые можно использовать для сохранения состояния страницы. Конкретный адрес бита, отвечающего за тот или иной флаг, как и набор флагов в целом, зависит от архитектуры.
Можно установить сразу несколько флагов:
Здесь 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 — это исключение, возникающее при обращении к странице, которая не загружена в физическую память — или потому, что она была вытеснена, или потому, что не была выделена.
В операционных системах общего назначения при обработке этого исключения происходит поиск нужной странице на внешнем носителе (жёстком диске, к примеру).
Выталкивание страниц во внешнюю память и их чтение в случае page fault не реализовано. С одной стороны, это лишает возможности использовать больше физической памяти, чем имеется на самом деле, а с другой — не является актуальной проблемой для встраиваемых систем. Нет никаких ограничений, делающих невозможной реализацию данного механизма, и при желании читатель может попробовать себя в этом деле 🙂
Выделение памяти
Для виртуальных страниц и для физических страниц, которые могут быть использованы при работе с виртуальной памятью, статически резервируется некоторое место в оперативной памяти. Тогда при выделении новых страниц и директорий они будут браться именно из этого места.
Исключением является набор указателей на PGD для каждого процесса (MMU-контексты процессов): этот массив хранится отдельно и используется при создании и разрушении процесса.
Выделение страниц
Итак, выделить физическую страницу можно с помощью vmem_alloc_page
Функция page_alloc() ищет участок памяти из N незанятых страниц и возвращает физический адрес начала этого участка, помечая его как занятый. В приведённом коде virt_page_allocator ссылается на участок памяти, резервированной для выделения физических страниц, а 1 — количество необходимых страниц.
Выделение таблиц
Тип таблицы (PGD, PMD, PTE) не имеет значения при аллокации. Более того, выделение таблиц производится также с помощью функции page_alloc(), только с другим аллокатором (virt_table_allocator).
Участки памяти (Memory Area)
После добавления страниц в соответствующие таблицы нужно уметь сопоставлять участки памяти с процессами, к которым они относятся. У нас в системе процесс представлен структурой task, содержащей всю необходимую информацию для работы ОС с процессом. Все физически доступные участки адресного пространства процесса записываются в специальный репозиторий: task_mmap. Он представляет из себя список дескрипторов этих участков (регионов), которые могут быть отображены на виртуальную память, если включена соответствующая поддержка.
brk — это самый большой из всех физических адресов репозитория, данное значение необходимо для ряда системных вызовов, которые не будут рассматриваться в данной статье.
ctx — это контекст задачи, использование которого обсуждалось в разделе “Виртуальный адрес”.
struct dlist_head — это указатель на начало двусвязного списка, организация которого аналогична организации Linux Linked List.
За каждый выделенный участок памяти отвечает структура marea
Поля данной структуры имеют говорящие имена: адреса начала и конца данного участка памяти, флаги региона памяти. Поле mmap_link нужно для поддержания двусвязного списка, о котором говорилось выше.
Отображение виртуальных участков памяти на физические (Mapping)
Ранее уже рассказывалось о том, как происходит выделение физических страниц, какие данные о виртуальной памяти относятся к задаче, и теперь всё готово для того, чтобы говорить о непосредственном отображении виртуальных участков памяти на физические.
Отображение виртуальных участков памяти на физическую память подразумевает внесение соответствующих изменений в иерархию страничных директорий.
Подразумевается, что некоторый участок физической памяти уже выделен. Для того, чтобы выделить соответствующие виртуальные страницы и привязать их к физическим, используется функция 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.
Освобождение виртуального участка памяти (Unmapping)
После того, как участок виртуальной памяти был отображён на физическую, рано или поздно её придётся освободить: либо в случае ошибки, либо в случае завершения работы процесса.
Изменения, которые при этом необходимо внести в структуру страничной иерархии памяти, производятся с помощью функции vmem_unmap_region().
Все параметры функции, кроме последнего, должны быть уже знакомы. free_pages отвечает за то, должны ли быть удалены страничные записи из таблиц.
try_free_pte, try_free_pmd, try_free_pgd — это вспомогательные функции. При удалении очередной страницы может выясниться, что директория, её содержащая, могла стать пустой, а значит, её нужно удалить из памяти.
нужны как раз для случая двухуровневой иерархии памяти.
Заключение
Конечно, данной статьи не достаточно, чтобы с нуля организовать работу с MMU, но, я надеюсь, она хоть немного поможет погрузиться в OSDev тем, кому он кажется слишком сложным.
Национальная библиотека им. Н. Э. Баумана
Bauman National Library
Персональные инструменты
Управление памятью в операционной системе
С понятием управления паметью в ОС связаны следующие технологии:
Содержание
Функции управления памятью в ОС
Операционная система решает следующие задачи:
Типы адресов
Для идентификации переменных и команд используются символьные имена (метки), виртуальные адреса и физические адреса.
Символьные имена
Символьные имена присваивает пользователь при написании программы.
Виртуальные адреса
Виртуальные адреса вырабатывает компилятор. Так как не известно, в какое место оперативной памяти будет загружена программа, то компилятор присваивает переменным и командам виртуальные (условные) адреса, обычно считая по умолчанию, что программа будет размещена, начиная с нулевого адреса. Совокупность виртуальных адресов процесса называется виртуальным адресным пространством. Каждый процесс имеет собственное виртуальное адресное пространство.
Физические адреса
Физические адреса соответствуют номерам ячеек оперативной памяти, где в действительности расположены или будут расположены переменные и команды. Переход от виртуальных адресов к физическим может осуществляться двумя способами.
Второй способ заключается в том, что программа загружается в память в неизмененном виде в виртуальных адресах, при этом операционная система фиксирует смещение действительного расположения программного кода относительно виртуального адресного пространства. Во время выполнения программы при каждом обращении к оперативной памяти выполняется преобразование виртуального адреса в физический.
Иногда (обычно в специализированных системах) заранее точно известно, в какой области оперативной памяти будет выполняться программа, и компилятор выдает исполняемый код сразу в физических адресах.
Методы распределения памяти в ОС
Выделяют следующие методы распределения памяти:
Большая Энциклопедия Нефти и Газа
Виртуальная адресация
Виртуальная адресация применяется для увеличения адресного пространства ПК при наличии ОП большой емкости ( простая виртуальная адресация) или при организации виртуальной памяти, в которую наряду с ОП включается и часть внешней ( обычно дисковой) памяти. При виртуальной адресации вместо начального адреса сегмента Лсегм в формировании абсолютного адреса Лабс принимает участие многоразрядный адресный код, считываемый из специальных таблиц. [1]
Принцип виртуальной адресации устранил трудности программирования, связанные с ограниченным объемом ОП, так как при виртуальной адресации в адресную область включается не только ОП, но и ВП на НМД. [2]
Управление виртуальной адресацией ( или обращениями к различным объектам) заключается в предоставлении процедурам определенных возможностей, задании имен для ссылок на объекты и проверки полномочий доступа. Указанные функции выполняются вне всякой связи с процессом переадресации. [3]
Микропроцессор 80386 имеет два режима работы: реальной адресации и виртуальной адресации с защитой. [7]
Кроме того, при любом объеме до 124 кслов осуществляется страничная организация с виртуальной адресацией и защитой памяти. [8]
Виртуальная адресация применяется для увеличения адресного пространства ПК при наличии ОП большой емкости ( простая виртуальная адресация ) или при организации виртуальной памяти, в которую наряду с ОП включается и часть внешней ( обычно дисковой) памяти. При виртуальной адресации вместо начального адреса сегмента Лсегм в формировании абсолютного адреса Лабс принимает участие многоразрядный адресный код, считываемый из специальных таблиц. [9]
Принцип виртуальной адресации устранил трудности программирования, связанные с ограниченным объемом ОП, так как при виртуальной адресации в адресную область включается не только ОП, но и ВП на НМД. [10]
Принцип виртуальной адресации устранил трудности программирования, связанные с ограниченным объемом ОП, так как при виртуальной адресации в адресную область включается не только ОП, но и ВП на НМД. [11]
Виртуальная адресация применяется для увеличения адресного пространства ПК при наличии ОП большой емкости ( простая виртуальная адресация) или при организации виртуальной памяти, в которую наряду с ОП включается и часть внешней ( обычно дисковой) памяти. При виртуальной адресации вместо начального адреса сегмента Лсегм в формировании абсолютного адреса Лабс принимает участие многоразрядный адресный код, считываемый из специальных таблиц. [12]