Физические принципы работы памяти
Мы знаем, что обычно физический адрес обращается к блоку DRAM. Мы можем рассматривать его как большой массив байтов, в котором можно найти соответствующую позицию для чтения и записи с помощью физического адреса. Однако физический адрес не только обращается к DRAM, но также может использоваться для доступа к другим периферийным устройствам. Поэтому мы можем считать DRAM разновидностью периферийного устройства, а физический адрес — это абстракция среды хранения данных.
Если для доступа к другому периферийному устройству необходимо использовать разные инструкции (например, x86 предоставляет отдельные инструкции in
и out
для доступа к пространству ввода-вывода, отличному от пространства памяти), это может быть довольно неудобно. Поэтому многие архитектуры набора команд (такие как RISC-V, ARM и MIPS) используют технологию MMIO (Memory Mapped I/O), чтобы отобразить периферийные устройства на определённый диапазон физических адресов. Таким образом, доступ к периферийным устройствам становится таким же, как и доступ к физической памяти.
Теперь давайте рассмотрим физическую память.
Обнаружение физической памяти
Как операционная система узнаёт о физическом адресе, где находится физическая память? В RISC-V это обычно выполняется загрузчиком, например, OpenSBI. Он сканирует все периферийные устройства, включая те, которые находятся внутри системы, и сохраняет результаты сканирования в формате DTB (Device Tree Blob) в определённом месте в физической памяти. Затем OpenSBI сохраняет его адрес в регистре a1
, чтобы мы могли его использовать.
Результаты сканирования описывают информацию обо всех периферийных устройствах, включая физическую память в QEMU-симулированном компьютере RISC-V Virt.
[info] Физическая память в QEMU-симулированном компьютере RISC-V Virt
Изучив определение
virt_memmap[]
в коде QEMU (hw/riscv/virt.c
), можно понять детали физической компоновки памяти в QEMU-симулированном RISC-V Virt компьютере. Можно увидеть, что в физической памяти есть много пустых пространств (то есть областей без отображения), а также множество специфических адресов для периферийных устройств. Сейчас мы не понимаем их значения, но позже мы будем постепенно обращаться к ним. На данный момент нас интересует только последняя область, которая представляет собой пространство DRAM размером 128 МБ, которое будет управляться ОС.
Начальный адрес | Конечный адрес | Значение |
---|---|---|
0x0 | 0x100 | QEMU VIRT_DEBUG |
0x100 | 0x1000 | Неотображённое пространство |
0x1000 | 0x12000 | QEMU MROM |
0x12000 | 0x100000 | Неотображённое пространство |
0x100000 | 0x101000 | QEMU VIRT_TEST |
0x101000 | 0x2000000 | Неотображённое пространство |
0x2000000 | 0x2010000 | QEMU VIRT_CLINT |
0x2010000 | 0x3000000 | Неотображённое пространство |
0x3000000 | 0x3010000 | QEMU VIRT_PCIE_PIO |
0x3010000 | 0xc000000 | Неотображённое пространство |
0xc000000 | 0x10000000 | QEMU VIRT_PLIC |
0x10000000 | 0x10000100 | QEMU VIRT_UART0 |
0x10000100 | 0x10001000 | Неотображённое пространство |
0x10001000 | 0x10002000 | QEMU VIRT_VIRTIO |
0x10002000 | 0x20000000 | Неотображённое пространство |
0x20000000 | 0x24000000 | QEMU VIRT_FLASH |
0x24000000 | 0x30000000 | Неотображённое пространство |
0x30000000 | 0x40000000 | QEMU VIRT_PCIE_ECAM |
0x40000000 | 0x80000000 | QEMU VIRT_PCIE_MMIO |
0x80000000 | 0x88000000 | DRAM по умолчанию размером 128 МБ (размер можно настроить) |
Однако, чтобы упростить задачу, мы не собираемся анализировать этот результат самостоятельно. Мы знаем, что начальный физический адрес DRAM в QEMU составляет 0x80000000. В QEMU размер RAM можно указать с помощью опции -m
, и по умолчанию он равен 128 MB. Следовательно, диапазон физических адресов DRAM по умолчанию составляет [0x80000000, 0x88000000).
Поскольку позже мы обсудим виртуальные адреса, физические страницы и виртуальные страницы, для дальнейшего различения вместо использования типа usize
мы создаём класс PhysicalAddress
. Затем мы реализуем для него ряд операций, таких как сложение, вычитание и вывод. Поскольку эта часть реализации больше ориентирована на Rust, чем на операционную систему, здесь не приводится код. Пожалуйста, обратитесь к файлу os/src/memory/address.rs
.
Затем мы напрямую присваиваем конечный адрес DRAM физической памяти в ядре и одновременно записываем конечный адрес, используемый операционной системой (который указан в linker script как kernel_end
).
lazy_static! {
/// Конечный адрес кода ядра, который можно использовать для выделения памяти
///
/// Из-за ограничений языка Rust мы можем только присвоить его как статическую переменную, вычисляемую во время выполнения, а не как константу
pub static ref KERNEL_END_ADDRESS: PhysicalAddress = PhysicalAddress(kernel_end as usize);
}
extern "C" {
/// Указан в `linker.ld` как конечный адрес кода ядра
///
/// Как переменная существует в [`KERNEL_END_ADDRESS`]
fn kernel_end();
}
Здесь используется макрос lazy_static
, который помогает нам автоматически выполнять эти вычисления при первом использовании макроса lazy_static
.
Наконец, мы добавляем вызовы модулей в различные файлы и пытаемся выполнить вывод в os/src/main.rs
.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )