Управление физической памятью
Обычно при распределении физической памяти мы используем не байты, а физические страницы (Frame) размером 4 КБ. Мы будем представлять физическую страницу с помощью номера физической страницы (Physical Page Number, PPN), который будет обозначать диапазон физических адресов от $$[\text{PPN}\times 4\text{KB},(\text{PPN}+1)\times 4\text{KB})$$ .
Очевидно, что существует взаимно однозначное соответствие между физическими страницами и их номерами. Чтобы использовать номера страниц для представления физических страниц, необходимо, чтобы начальный адрес каждой физической страницы был кратен 4 КБ. Однако это также даёт нам преимущество: для любого физического адреса его деление на 4096 (или сдвиг вправо на 12 бит) даст номер физической страницы, которой принадлежит этот физический адрес.
Мы также создадим структуру для инкапсуляции физических страниц. Это делается для того, чтобы отличать физические страницы от других типов адресов, и для обеспечения возможности преобразования между страницами и адресами. Код для этой структуры можно найти в os/src/memory/address.rs
.
Кроме того, нам нужно добавить соответствующие настройки в os/src/memory/config.rs
:
/// Размер страницы / кадра, должен быть степенью двойки
pub const PAGE_SIZE: usize = 4096;
/// Начальный адрес доступной памяти
pub const MEMORY_START_ADDRESS: PhysicalAddress = PhysicalAddress(0x8000_0000);
/// Конечный адрес доступной памяти
pub const MEMORY_END_ADDRESS: PhysicalAddress = PhysicalAddress(0x8800_0000);
Чтобы эффективно управлять всеми физическими страницами, нам нужен распределитель, который может выполнять операции распределения и освобождения. Прежде чем мы создадим такой распределитель, мы должны сначала инкапсулировать концепцию физических страниц. Обратите внимание, что физическая страница фактически представляет собой непрерывный блок памяти, и здесь мы просто инкапсулируем начальный физический адрес страницы в структуре FrameTracker
.
В процессе разработки операционной системы мы часто сталкиваемся с ситуациями, когда нам нужно выделить определённую область памяти для определённой цели. В таких случаях мы говорим, что эта область памяти доступна, но поскольку она не находится в стеке, компилятор Rust не знает, что это такое, поэтому нам приходится использовать небезопасное преобразование её в форму &'static mut T
(обычно можно опустить 'static
).
Однако, например, если мы используем блок памяти в качестве таблицы страниц, и когда эта таблица больше не нужна, мы должны освободить эту память. На самом деле, нам нужен механизм, похожий на создание объекта с жизненным циклом. Поэтому мы можем использовать тип Tracker
для инкапсуляции ссылки &'static mut
. Использование Tracker
похоже на использование интеллектуального указателя. Если требуется подсчёт ссылок, можно обернуть его в Arc
.
Здесь мы реализовали структуру FrameTracker
, которая отличается от фактического размера «Frame» в 4 КБ в памяти. Наша цель при разработке FrameTracker
— предоставить распределителю ссылку на FrameTracker
в качестве идентификатора страницы при выделении, и автоматически освобождать страницу при необходимости.
Наконец, мы создаём распределитель физических страниц. Для соответствия более строгим стандартам проектирования Rust, этот распределитель не будет включать конкретные алгоритмы. Конкретные алгоритмы будут реализованы с использованием Rust trait под названием Allocator
, а наш FrameAllocator
будет зависеть от конкретной реализации trait.
Этот распределитель будет основан на структуре данных, называемой «линейное дерево», для распределения и освобождения физических страниц. Он будет инициализирован диапазоном номеров физических страниц с помощью ленивой статической переменной.
FrameAllocator
имеет два поля: start_ppn
, которое является начальным номером физической страницы доступного диапазона, и allocator
, который является конкретным алгоритмом распределения.
При создании FrameAllocator
мы передаём ему диапазон доступных номеров физических страниц. Затем он создаёт объект FrameAllocator
, который содержит начальный номер физической страницы и конкретный алгоритм распределения.
Метод alloc
пытается получить доступный номер физической страницы из алгоритма распределения и возвращает его вместе с начальным номером страницы. Метод dealloc
добавляет освобождённую страницу в конец списка свободных страниц.
Для реализации конкретных алгоритмов распределения мы создали Rust trait под названием Allocator
. Этот trait определяет методы для создания распределителя, выделения и освобождения элементов.
Существует два варианта реализации этого trait: на основе стека и на основе линейного дерева. lazy_static! и Mutex для защиты данных
lazy_static! и Mutex используются для защиты данных, которые могут быть изменены. Для статических данных типа static mut операции изменения являются небезопасными. Все потоки имеют доступ к таким данным. Если один поток обращается к этим данным, а другой поток также пытается получить к ним доступ, то может возникнуть конфликт, который приведёт к непредсказуемым результатам. В следующих разделах мы подробно рассмотрим концепции потоков и Mutex.
Поэтому мы используем spin::Mutex, чтобы защитить эти данные с помощью блокировки. Один поток пытается получить доступ к внутренним данным через lock(). Если ключ занят другим потоком, первый поток будет заблокирован до тех пор, пока второй поток не завершит доступ к данным и не освободит блокировку. После освобождения блокировки первый поток сможет получить ключ, открыть блокировку и получить доступ к внутренним данным.
Мы используем spin::Mutex и должны добавить зависимость в os/Cargo.toml. К счастью, он не требует поддержки операционной системы (поддерживает no_std), поэтому мы можем использовать его без опасений.
Наконец, мы загружаем новый модуль и проводим простой тест в функции main:
/// Rust 的入口函数
///
/// 在 `_start` 为我们进行了一系列准备之后,这是第一个被调用的 Rust 函数
#[no_mangle]
pub extern "C" fn rust_main() -> ! {
// 初始化 различных модулей
interrupt::init();
memory::init();
// Распределение физических страниц
for _ in 0..2 {
let frame_0 = match memory::frame::FRAME_ALLOCATOR.lock().alloc() {
Result::Ok(frame_tracker) => frame_tracker,
Result::Err(err) => panic!("{}", err)
};
let frame_1 = match memory::frame::FRAME_ALLOCATOR.lock().alloc() {
Result::Ok(frame_tracker) => frame_tracker,
Result::Err(err) => panic!("{}", err)
};
println!("{} and {}", frame_0.address(), frame_1.address());
}
panic!()
}
Можно увидеть следующий вывод:
Вывод программы
PhysicalAddress(0x80a14000) and PhysicalAddress(0x80a15000)
PhysicalAddress(0x80a14000) and PhysicalAddress(0x80a15000)
Мы видим, что frame_0 и frame_1 автоматически уничтожаются и освобождаются, и во второй раз выделяются те же адреса.
Запустите следующий код:
/// Rust 的入口函数
///
/// 在 `_start` 为我们进行了一系列准备之后,这 является первым вызываемым Rust-функцией
#[no_mangle]
pub extern "C" fn rust_main() -> ! {
// Инициализация различных модулей
interrupt::init();
memory::init();
// Распределение физических страниц
match memory::frame::FRAME_ALLOCATOR.lock().alloc() {
Result::Ok(frame_tracker) => frame_tracker,
Result::Err(err) => panic!("{}", err)
};
panic!()
Подумайте, чем этот код отличается от предыдущего, и есть ли какие-либо синтаксические недостатки в нашей конструкции?
Здесь переменная frame_tracker будет уничтожена внутри блока match. Однако внешняя функция lock() ещё не сняла блокировку, что может привести к взаимоблокировке.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )