В unix подобных операционных системах в качестве дополнительной памяти используется
Дать общую характеристику управления памятью в UNIX затруднительно, поскольку эта часть системы претерпела наибольшие изменения за долгий период существования UNIX, пройдя путь от управления динамическими разделами физической памяти до современной схемы замещения страниц по требованию, подобной той, которая используется в Windows.
Каждый процесс в UNIX имеет собственное виртуальное адресное пространство, разделяющееся на области программы, данных и стека. В зависимости от архитектуры памяти компьютера, область памяти UNIX может быть реализована как один или несколько сегментов памяти, как набор виртуальных страниц или просто как область в физической памяти. Адреса и размеры областей хранятся как часть контекста процесса.
Область программы, как правило, доступна только для чтения и имеет фиксированный размер. Область данных доступна для чтения и записи, ее размер может изменяться в ходе работы с помощью системного вызова brk. Область стека автоматически увеличивается системой по мере заполнения стека. При запуске нескольких процессов, выполняющих одну и ту же программу, они разделяют общую область программы, но каждый из процессов получает собственные области данных и стека.
Когда процесс выполняет системный вызов exec (т.е. начинает выполнять другую программу), все его области памяти освобождаются и затем выделяются заново.
Ядро системы имеет собственные области памяти. При выполнении системных вызовов ядро работает в контексте вызвавшего процесса, т.е. оно имеет доступ как к собственной памяти, так и к областям памяти процесса.
Эффективное использование ограниченного объема физической памяти обеспечивается работой системных процессов-«демонов», управляющих перемещением информации между памятью и файлом подкачки. В ранних реализациях UNIX, не использовавших механизм виртуальных страниц, была возможна только подкачка и вытеснение целых процессов. Демон подкачки (системный процесс с идентификатором 0) периодически отслеживал состояние процессов и принимал решение о вытеснении отдельных процессов на диск и подкачке в освободившуюся память одного из ранее вытесненных процессов. При этом во внимание принималась длительность пребывания процесса в памяти или на диске, текущее состояние процесса (спящие процессы – более подходящие кандидаты на вытеснение, чем готовые) и его размер (часто таскать туда-сюда большие процессы невыгодно).
В современных реализациях, основанных на страничной организации памяти, значительную роль играет понятие списка свободных страниц, которые могут быть немедленно выделены, если какой-либо процесс обратится к виртуальной странице, отсутствующей в памяти. В этот список заносятся страницы, к которым долго не было обращения со стороны процессов. Поскольку в отношении страниц памяти трудно реализовать алгоритм LRU, обычно используется более простой алгоритм «второго шанса» (его другое название – «алгоритм часов»). Идея заключается в следующем. Для каждой физической страницы в таблице страниц хранятся бит использования и бит модификации. Эти биты устанавливаются аппаратно: бит использования – при каждом обращении к странице, а бит модификации – при записи на страницу. Системный процесс, называемый демоном замещения страниц, активизируется периодически (по таймеру) и следит, не слишком ли мал размер списка свободных страниц. Обычно порог устанавливается равным ¼ общего объема физической памяти. Если число свободных страниц ниже этого порога, демон начинает циклически проверять все физические страницы. Если у страницы установлен бит использования, то этот бит сбрасывается. Если же бит уже был сброшен, то страница включается в список свободных. Таким образом, страница попадает в список свободных, если после последнего обращения к ней страничный демон успел дважды ее опросить. Если у страницы установлен бит модификации (в другой терминологии, если страница «грязная»), то перед ее зачислением в список свободных система сохраняет данные в страничном файле. Зачисление в список свободных страниц не означает немедленной потери данных, и если процесс успеет обратиться к странице до того, как она будет отдана другому процессу, эта страница будет исключена из числа свободных.
Демон подкачки процессов тоже не дремлет. Если страничный демон слишком часто выполняет запись страниц на диск, а число свободных страниц тем не менее остается низким, то демон подкачки выбирает один из процессов и отправляет его целиком на диск, чтобы уменьшить конкуренцию за память.
Здесь описан (весьма приблизительно) лишь один из вариантов управления памятью, реализованных в различных версиях UNIX и в Linux. Поскольку алгоритмы управления физической памятью являются «внутренним делом» системы и не регламентируются никакими стандартами, соответствующие алгоритмы, используемые в разных версиях системы, могут значительно отличаться.
[1] Как вспоминает один из разработчиков первого советского компьютера МЭСМ (Малая(!) Электронная Счетная Машина), когда этого монстра включали, то приходилось в январе месяце открывать все окна, чтобы удержать температуру в машинном зале в пределах 30°.
[2] Источник термина не совсем понятен. Среди значений английского слова «cash» наиболее подходящим кажется «наличные деньги» - та мелочь в кошельке, которая позволяет не обращаться каждый раз в банк ради мелких покупок.
[3] Вспомните, как поступает Windows при очередной загрузке после некорректного выключения компьютера.
[4] Теоретически можно задать число копий FAT, отличное от двух. Это число хранится как один из параметров в BOOT-секторе. На практике всегда используются две копии FAT.
[5] Как вы думаете, в чем разница между перемещением файла в пределах одного диска и перемещением с диска на диск?
[6] Придумано около десятка неудачных вариантов русского эквивалента для термина «handle», среди них – дескриптор, ссылка, логический номер, ключ, манипулятор, описатель, индекс. Выразительнее всего был бы прямой перевод – «рукоятка», но ни у кого не хватает смелости, чтобы ввести такой «несерьезный» термин. Так что давайте удовольствуемся честной транслитерацией «хэндл».
[7] Налицо некоторый разнобой в общепринятой терминологии: в системе с невытесняющей диспетчеризацией вытеснение процесса все-таки возможно, но только по инициативе самого процесса.
[8] Иногда используют также термин «разделение времени» (time sharing). Однако этот термин лучше оставить для обозначения многотерминальных ОС, описанных в п. 1.3.3. К системам с квантованием времени относятся также ОС реального времени, заметно отличающиеся от систем разделения времени.
[9] Но к экзамену вспомнить!
[10] Одно и то же расширение EXE означает различные форматы программ для MS-DOS и для Windows разных версий. Каждый из этих форматов имеет собственную сигнатуру. Например, для EXE-файлов современной Windows используется сигнатура 'PE'.
[11] Использование слова «nice» – «приятный, любезный, благовоспитанный, изящный» в данном контексте обычно объясняют так: этот процесс настолько любезен, что уступает всем дорогу.
[12] Опыт подсказывает, что как только компьютеры с памятью в несколько гигабайт станут обычными, появятся и программы, способные найти более или менее полезное употребление всей этой памяти.
ELF (Executable and Linkage Format) – популярный формат файла исполняемого кода в UNIX и в Linux, основанный на представлении файла в виде совокупности секций.
Ext2FS – файловая система в Linux, основанная на использовании блоков небольшого размера и многоуровневой косвенной адресации .
Linux proc – файловая системы в Linux, основанная на отсутствии явного хранения данных и вычислении данных при выполнении запросов на ввод-вывод.
TTY – традиционное обозначение терминальных устройств в UNIX и Linux.
Конвейер (pipe) - механизм взаимодействия процессов в UNIX и Linux, позволяющий дочернему процессу наследовать коммуникационный канал от процесса-родителя.
Партнерская куча (buddy-heap) - метод распределения физической памяти в Linux, основанный на расщеплении блоков памяти и объединении соседних свободных блоков.
Подключаемые аутентификационные модули (pluggable authentication modules - PAM) – динамически подключаемые модули аутентификации пользователей, используемые в Linux.
Краткие итоги
Система распределения физической памяти в Linux использует механизм партнерской кучи , основанный на расщеплении свободных блоков и на объединении соседних свободных блоков памяти .
Виртуальная память в Linux основана на таблице страниц каждого процесса и логической точке зрения на память как совокупность непересекающихся смежных областей.
При создании нового процесса его адресное пространство пусто и наполняется регионами виртуальной памяти по мере загрузки программ. Системный вызов fork полностью копирует в дочерний процесс адресное пространство процесса -родителя.
Для управления страницами используется механизм откачки и подкачки.
Ядро Linux резервирует один регион виртуальной памяти каждого процесса для его собственных нужд, в частности, для размещения статической памяти.
Linux поддерживает как a.out- , так и ELF -форматы файлов исполняемого кода ; статическую и динамическую линковку.
В Linux реализована виртуальная файловая система (VFS), скрывающая различие между разными системами файлов. Основная файловая система Linux – Ext2fs, основанная на использовании блоков небольшого размера, битовой карты блоков и многоуровневой косвенной адресации . Другая файловая система – Linux proc – не хранит данные явно, а генерирует их при выполнении запросов на ввод-вывод.
Система ввода-вывода Linux использует кэш страниц и буферный кэш . Устройства разбиты на три класса – блочные, символьные и сетевые. Для блочных устройств используется блочный буферный кэш . Для символьных устройств поддерживаются специфические операции ввода-вывода и не поддерживается произвольный доступ к блокам данных. Особым образом организованы драйверы терминальных устройств ( TTY ), для которых ядро поддерживает стандартный интерфейс .
Как и в UNIX , в Linux сигнализация о событиях для пользовательских процессов реализуется с помощью сигналов. Процессы ядра не используют сигналы и взаимодействуют с помощью системных структур планировщика .
Для взаимодействия процессов используются конвейер ( pipe ) и разделяемые объекты в общей памяти.
Сетевая система Linux поддерживает как сетевые протоколы связи UNIX – UNIX , так и протоколы ОС, отличных от UNIX . Реализация сетевой системы Linux имеет три уровня абстракции: сокетный интерфейс , драйверы протоколов и драйверы сетевых устройств. Поддерживается набор протоколов Интернета. Обеспечивается маршрутизация пакетов на любом участке сети. На верхнем уровне протокола маршрутизации поддерживаются протоколы UDP , TCP , ICMP .
Безопасность в Linux реализована на основе динамически подключаемых аутентификационных модулей. Управление доступом , как и в UNIX , осуществляется с помощью уникальных идентификаторов пользователя и группы и масок защиты. Реализована совместимость с POSIX – возможность многократно освобождать и получать uid процесса. Кроме того, реализована возможность выборочно передавать доступ к файлу любому серверному процессу .
Направления развития и использования Linux: новые ОС на основе ядра Linux (например, ОС для мобильных устройств Google Android ) и обучение на основе Linux (российская ОС для школьников Альт Линукс).
Управление памятью позволяет процессам перемещаться между оперативной памятью и жестким диском во время выполнения программы. Более того, этот процесс отслеживает каждую ячейку памяти для корректного выделения процессов и освобождения памяти. Физическая память — это основная память, в которой находятся выполняющиеся в данный момент программы. С другой стороны, виртуальная память увеличивает емкость основной (физической) памяти (за счет жесткого диска) для выполнения программ, размер которых превышает объемы установленной в компьютере физической памяти.
Что такое физическая память?
Запуская и исполняя программы, процессор напрямую обращается к физической памяти. Обычно программы хранятся на жестком диске. Время доступа процессора к диску значительно превышает аналогичное время доступа к физической (оперативной) памяти. Чтобы процессор мог выполнять программы быстрее, они сначала помещаются в физическую (оперативную) память. После завершения своей работы, они возвращаются обратно на жесткий диск. Освобожденная таким образом память может быть выделена новой программе. При выполнении данные программы называются процессами.
Что такое виртуальная память?
Физическая память vs. Виртуальная память
Основное различие между физической и виртуальной памятью заключается в том, что физическая память относится к оперативной памяти компьютера, подключенной непосредственно к его материнской плате. Именно в ней находятся выполняемые в данный момент программы. А виртуальная память — это метод управления, расширяющий при помощи жесткого диска объем физической памяти, благодаря чему у пользователей появляется возможность запускать программы, требование к памяти которых превышает объем установленной в компьютере физической памяти.
Физическая память | Виртуальная память |
Непосредственно установленная в компьютере оперативная память. | Метод управления памятью, с помощью которого для программ создается иллюзия наличия в системе (физической) памяти, гораздо больше реально установленной. |
Работает быстрее. | Работает медленнее. |
Ограничена размером чипа ОЗУ. | Ограничена размером жесткого диска. |
Может напрямую обращаться к процессору. | Не может напрямую обращаться к процессору. |
Использует swapping. | Использует paging. |
Рассмотрим данные пункты:
Физическая память является фактической памятью.
Виртуальная память является логической памятью.
Физическая память быстрее виртуальной памяти.
Физическая память ограничена размером чипа ОЗУ.
Виртуальная память ограничена размером жесткого диска.
Физическая (оперативная) память использует swapping. Swapping — это концепция управления памятью, при которой всякий раз, когда системе для хранения данных некоторого процесса не хватает оперативной (физической) памяти, она берет её из вторичного хранилища (например, жесткого диска), сбрасывая на него временно неиспользуемые данные. В Linux есть специальная программа управления памятью, которая управляет этим процессом. Всякий раз, когда ОЗУ не хватает памяти, программа управления памятью ищет все те неактивные блоки данных (страницы), присутствующие в ОЗУ, которые не использовались в течение длительного времени. Когда она успешно находит подобные блоки, то перемещает их в память подкачки (например, на жесткий диск). Таким образом, освобождается пространство оперативной памяти, и, следовательно, его можно использовать для некоторых других программ, которые нуждаются в срочной обработке.
Виртуальная память использует paging. Paging — это метод выделения памяти, при котором разным несмежным блокам памяти назначается фиксированный размер. Размер обычно составляет 4 КБ. Paging всегда выполняется между активными страницами (pages).
Команды для управления памятью в Linux
Давайте рассмотрим некоторые команды для управления памятью в Linux.
Файл /proc/meminfo
Файл /proc/meminfo содержит всю информацию, связанную с памятью. Для просмотра данного файла используйте команду cat:
Эта команда выводит множество параметров, связанных с памятью. Чтобы получить информацию о физической памяти из файла /proc/meminfo, используйте:
$ grep MemTotal /proc/meminfo
Чтобы получить информацию о виртуальной памяти из файла /proc/meminfo, используйте:
$ grep VmallocTotal /proc/meminfo
Команда top
Команда top позволяет отслеживать процессы и использование системных ресурсов в Linux в режиме реального времени. Когда вы запустите команду, то заметите, что значения в выходных данных продолжают изменяться с некоторым небольшим интервалом:
В верхней части отображается текущая статистика использования системных ресурсов. Нижняя часть содержит информацию о запущенных процессах. Вы можете перемещаться вверх и вниз по списку с помощью клавиш со стрелочками вверх/вниз и использовать q для выхода.
Команда free
Команда free отображает объем свободной и используемой памяти в системе.
Значения для каждого поля указаны в кибибайтах (КиБ).
Чтобы получить вывод в более удобочитаемом формате, используйте:
Команда vmstat
Команда vmstat — это инструмент мониторинга производительности в Linux, который предоставляет полезную информацию о процессах, памяти, операциях ввода-вывода, подкачке, диске и планировании процессора, а также приводит статистику виртуальной памяти вашей системы.
В этой главе мы собираемся добавить к системе страничную организацию памяти. Это режим работы с памятью предназначен для двух целей - защите памяти и реализации виртуальной памяти (которые на практике неразрывно взаимосвязаны).
6.1. Виртуальная память (теория)
Если вы знаете, что такое виртуальная память, вы можете пропустить этот раздел.
Если в linux вы создадите крошечную программу, например,
, откомпилируете ее, а затем запустите 'objdump -f', вы можете увидеть нечто, похожее на следующее:
Обратите внимание на начальный адрес программы 0x80482a0, что эквивалентно приблизительно 128 Мб в адресном пространстве. Может показаться странным, но эта программа будет работать отлично на машинах с объемом оперативной памяти, меньшем 128 Мб.
То, что программа на самом деле «видит», когда она читает из памяти и пишет в память, является виртуальным адресным пространством. Часть виртуального адресного пространства отображается в физическую память, а часть - не отображается. Если вы пытаетесь получить доступ к неотображаемой части, то процессор выдаст сигнал о некорректном обращении к памяти page fault, операционная система ловит его, и в POSIX-системе выдает сигнал SIGSEGV, за которым непосредственно следует сигнал SIGKILL.
Эта абстракция является чрезвычайно полезной. Это означает, что с помощью компиляторов можно создавать программы, которые будут, когда запускаются, размещать код в конкретном месте в памяти. При использовании виртуальной памяти процесс считает, что он, например, находится по адресу 0x080482a0, но на самом деле это может быть место в физической памяти с адресом 0x1000000. В результате процессы не могут случайно (или намеренно) испортить данные других процессов.
Виртуальная память этого типа полностью зависит от поддержки аппаратным обеспечением. Ее нельзя эмулировать программно. К счастью, в архитектуре x86 есть именно такая возможность. Этот модуль называется MMU (Memory Management Unit- Устройство управление памятью) и он обрабатывает все операции по отображению памяти, связанные с сегментацией и страничной организацией памяти, образуя слой между процессором и памятью (на самом деле, это часть процессора, но это всего лишь деталь реализации).
6.2. Страничная организация памяти как конкретная реализация виртуальной памяти
Виртуальная память представляет собой принцип абстракции. Как таковой, он требует конкретизации с помощью некоторых систем/алгоритмов. Как сегментацию памяти (смотрите главу 3), так и страничную организацию памяти можно использовать как способ реализации виртуальной памяти. Однако, как уже упоминалось в главе 3, сегментация становится устаревшим способом. Страничная организация памяти является новой, лучшей альтернативой для архитектуры x86.
Страничная организация представляет собой разделение виртуального адресного пространства.
6.2.1. Запись, описывающая страницу
В каждом процессе обычно имеются различные наборы отображения страниц, так что пространства виртуальной памяти не зависят друг от друга. В архитектуре x86 (32-разрядной) размеры страниц зафиксированы в 4 Кб по размеру. У каждой страницы есть соответствующее слово дескриптора, которое сообщает процессору, какому кадру страница сопоставляется. Обратите внимание, что страницы и кадры должны быть выровнены по границе в 4Кб (4KB будет 0x1000 байтов), при этом младшие 12 битов 32-разрядного слова всегда равны нулю. Это используется архитектурой для хранения в них информации о странице, например, присутствует ли она в памяти в режиме ядра или в пользовательском режиме и т.д. Формат этого слова изображен на рисунке.
Поля этого слова довольно просты, так что давайте быстрее пробежимся по ним.
P - Установлено, если страница находится в памяти.
R/W - Если установлено, то на страницу можно выполнять запись. Если не установлено, то страница доступна только для чтения. Это поле не используется, когда код работает в режиме ядра (если не установлен флаг CR0).
U/S - Если установлено, то это пользовательский режим. В противном случае, это страница в режиме супервизора (ядра). Код пользовательского режима не может выполнять запись на страницы, находящиеся в режиме ядра, или читать из них.
Reserved - Для внутреннего использования процессором и это поле не следует менять.
A - Установлено, если к странице был доступ (выполнялось обращение процессора).
D - Установлено, если на станице выполнялась запись (страница изменена).
AVAIL - Эти три бита не используются и доступны в режиме ядра.
Page frame address - старшие 20 битов адреса фрейма в физической памяти.
6.2.2. Директории / таблицы страниц
Возможно, вы подсчитали на калькуляторе и выяснили, что чтобы создать таблицу, отображающую каждую страницу размером 4KB одним 32-битный дескриптором, для 4 ГБ адресного пространства потребуется 4 Мбайт оперативной памяти. Так или иначе, но это правда.
4 Мб могут показаться большими накладными расходами, и надо быть справедливым, так оно и есть. Если у вас есть 4 ГБ физической памяти, то это не много. Однако, если вы работаете на компьютере, у которого 16 МБ оперативной памяти, вы сразу потеряли четверть доступной памяти! Нам нужно что-нибудь получше, что будет занимать объем, пропорциональный объему имеющейся у вас оперативной памяти.
Да, у нас нет этого. Но в Intel придумали что-то похожее - они используют 2-х уровневую систему. Процессор получает информацию из директория страниц, большой таблицы размером в 4KB, каждая запись которой указывает на таблицу страниц. Таблица страниц опять же имеет размер в 4KB и каждая запись является записью таблицы страниц, приведенной выше.
Таким образом, все адресное пространство в 4 ГБ перекрыто таким образом, что если в таблице страниц нет записей, то ее можно очистить и в директории страниц можно сбросить флаг ее присутствия.
6.2.3. Подключение страничной организации памяти
Подключить страничную организацию памяти исключительно просто.
- Скопируйте место вашего директория страниц в регистр CR3. Это должен быть, конечно, физический адрес.
- Установите бит PG в регистре CR0. Вы можете сделать это с помощью операции OR и операнда 0x80000000.
6.3. Некорректное обращение к памяти - page fault
Когда процесс делает что-то, что не нравится блоку управления памятью, выдается прерывание некорректного обращения к памяти - page fault. К этому могут привести следующие ситуации (список не полный):
- Чтение или запись в область памяти, которая не отображена в физическую память (флаг записи страницы / 'присутствия' таблиц не установлен)
- Процесс находится в пользовательском режиме и пытается записать в страницу, доступную только для чтения страницы.
- Процесс находится в пользовательском режиме и пытается получить доступ к странице, доступной только в режиме ядра.
- Запись страницы, находящаяся в таблице, повреждена - были перезаписаны зарезервированные биты.
Прерывание некорректного обращения к памяти имеет номер 14, и, если обратиться к главе 3, мы видим, что вырабатывается код ошибки. Эта ошибка дает нам довольно много информации о том, что произошло.
Бит 0 - Если установлен, то проблема возникла не из-за того, что страница не присутствовала в физической памяти. Если не установлен, то страница не присутствует в физической памяти.
Бит 1 - Если установлен, то это значит, что операция, из-за которой возникла проблема, была операцией записи, в противном случае это была операция чтения.
Бит 2 - Если установлен, то прерывание произошло, когда процессор работал в пользовательском режиме. В противном случае он работал в режиме ядра.
Бит 3 - Если установлен, то это означает, что проблема была вызвана тем, что были перезаписаны зарезервированные биты.
Бит 4 -Если установлен, то это значит, что проблема возникла в момент выборки команд.
6.4. Применяем все на практике
Мы почти готовы приступить к реализации. Однако, нам сначала потребуются несколько вспомогательных функций, наиболее важными из которых являются функции управления памятью.
6.4.1. Простое управление памятью с помощью команды выделения памяти malloc
Если вы знаете язык C++, вы, возможно, слышали о команде 'placement new' (выделить новую память). Это версия команды new, у которой есть параметр. Вместо вызова команды malloc, которая используется обычно, команда new создает объект по указанному адресу. Мы будем использовать очень похожую концепцию.
Когда ядро уже достаточно загружено, у нас будет активная и функционирующая память типа heap (куча). Однако, для работы с памятью типа куча, как правило, требуется наличие виртуальной памяти. Поэтому прежде, чем можно будет пользоваться памятью типа куча, нужно иметь простую альтернативу для выделения памяти.
Поскольку нам нужно выделять память при загрузке ядра довольно рано, можно предположить, что ничего, кроме команд kmalloc() и kfree() нам не потребуется. Это существенно все упрощает. У нас может быть просто указатель (адрес размещения) для некоторой свободной памяти, который мы передаем обратно в requestee, а затем увеличиваем. Таким образом:
Этого, на самом деле, будет достаточно. Тем не менее, у нас есть еще одно требование. Когда мы размещаем таблицы и директории страниц, они должны быть выровнены по границам страниц. Так что мы можем собрать следующее:
Теперь, к сожалению, у нас есть еще одно требование, и я не могу объяснить вам, почему это необходимо до тех пор, пока позже мы не рассмотрим его в наших руководствах. Оно возникает, когда мы клонируем директорий страниц (когда выполняем операцию fork()). В этот момент уже будет работать страничная организация памяти и kmalloc вернет виртуальный адрес. Но, нам также потребуется (поверьте мне, позже вы будете довольны тем, что мы так сделали) получить физический адрес выделенной памяти. На данный момент примите это на веру - в любом случае для этого потребуется уж не так много кода.
Великолепно. Это все, что нужно для простого управления памятью. В моем коде я, на самом деле, (в эстетических целях) переименовал kmalloc в kmalloc_int (для kmalloc_internal). Еще у меня есть несколько функций-обверток:
Я просто чувствую, этот интерфейс лучше, чем просто указывать 3 параметра при каждом выделении памяти из кучи в ядре! Эти определения должны быть в файлах kheap.h/kheap.c.
6.4.2. Необходимые определения
В файле paging.h должны находиться определения некоторых структур, которые сделают нашу жизнь проще.
Обратите внимание на элементы tablesPhysical и physicalAddr из page_table_t. Что они там делают?
Элемент physicalAddr на самом деле только для того случая, когда мы клонируем директории страниц (объясним позднее в наших руководствах). Запомните, что в этот момент новый директорий будет иметь адрес в виртуальной памяти, который не совпадает с адресом физической памяти. Нам понадобится физический адрес для того, чтобы сообщить его процессору в случае, если мы когда-либо захотим переключать директории.
Элемент tablesPhysical аналогичен. Он решает следующую проблему: Как получить доступ к таблицам страниц? Это сделать, как кажется, просто, но помните, что в директории страниц должны храниться физические адреса, а не виртуальные. А единственный способ, которым мы можем читать из памяти и записывать в память, является использование виртуальных адресов!
Одно из решений этой проблемы состоит в том, чтобы никогда не получать доступ непосредственно к таблицам страниц, а отображать некоторую таблицу страниц обратно в директорий страниц так, что когда осуществляется доступ к памяти по определенному адресу, вы сможете увидеть все ваши таблицы страниц, как если бы они было просто страницами, и все записи в таблицах страниц, как если бы они были обычными числами. Этот способ, на мой взгляд, не совсем интуитивно понятен и для него потребуется еще 256 MB адресного пространства, поэтому я предпочитаю другой способ.
Второй способ заключается в том, для каждого директория страниц имеется 2 массива. В одном хранятся физические адреса его таблицы со страницами (для передачи их в процессор), а в другом хранятся виртуальные адреса (так что мы может их читать и делать в них запись). В результате у нас будет только 4KB дополнительных накладных расходов для каждого директория страниц, что не так много.
6.4.3. Размещение фреймов физической памяти
Если мы хотим отобразить страницу во фрейм, нам нужно каким-то образом найти свободный кадр. Конечно, мы могли бы просто поддерживать огромный массив из единиц и нулей, но это было бы крайне расточительно - нам не нужны 32-бит просто для того, чтобы хранить два значения. Поэтому если мы будем использовать набор битов bitset , то мы потратим памяти в 32 раза меньше!
Надеюсь, что код не должен вызвать слишком много сюрпризов. Это просто упражнения по работе с битами. Затем мы переходим к функциям выделения и освобождения фреймов. Теперь, когда у нас есть эффективная реализация bitset, эти функции будут всего в несколько строк!
Обратите внимание, что макрос PANIC просто вызывает глобальную функцию, которая называется panic, с аргументами __FILE__ и __LINE__. Функция panic печатает их и переходит в бесконечный цикл, останавливая все исполнение.
6.4.4. Наконец-то код для страничной организации памяти
Хорошо, давайте проанализируем код. Прежде всего, функции - утилиты.
Функция switch_page_directory делает именно то, что говорит название (переключение директория страниц). Она берет директорий страниц и переключается на него. Она делает это при помощи перемещения в регистр CR3 адреса элемента номера tablesPhysical этого директория. Помните, что номер tablesPhysical указывает на массив физических адресов. После этого она сначала получает содержимое регистра CR0, затем выполняет операцию OR с битом PG (0x80000000), а затем перезаписывает значение регистра. В результате включается страничная организация памяти, а также осуществляется сброс кэша с директорием страниц.
Функция get_page возвращает указатель запись страницы для конкретного адреса. В функцию также можно передать параметр make. Если make равен 1 и таблица страницы, в которой должна находиться запись о запрашиваемой странице, еще не была создана, то она создается. В противном случае, функция просто вернет 0. Если таблица уже назначена, функция найдет запись о странице и вернет ее. Если ее нет (и make == 1), то будет предпринята попытка создать ее.
Наша функция kmalloc_ap ищет блок памяти, который выровнен по границе страницы, и запоминает место его физического расположения. Физическое расположение сохраняется в 'tablesPhysical' (после того, как несколько битов сообщат процессору, что страница присутствует, в нее можно делать запись и она доступна пользователю), а виртуальное месторасположение запоминается в 'tables'.
Функция initialise_paging, во-первых, создаст bitset для фреймов и с помощью команды memset заполняет его нулями. Затем она для директория страниц выделяет пространство (которое выровнено по краю страницы). После этого, она размещает фреймы так, что доступ к любой странице будет отображаться во фрейм с соответствующим линейным адресом, называемым идентичным отображением. Это сделано для небольшой части адресного пространства, так что код ядра может работать, как обычно. Она регистрирует обработчик прерывания для неверного обращения к странице (смотрите ниже), а затем включает страничную организацию памяти.
6.4.5. Обработчик неверного обращения к странице
6.4.6. Тестирование
Отлично! Теперь у вас есть код, который позволяет включать страничную организацию памяти и обрабатывать неверное обращение к странице! Давайте просто проверим, как это на самом деле работает, не так ли .
Читайте также: