1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/rcore-os-rCore-Tutorial

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
part-3.md 12 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 29.11.2024 21:14 7f8a4fd

Управление физической памятью

Физическая страница

Обычно при распределении физической памяти мы используем не байты, а физические страницы (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 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/rcore-os-rCore-Tutorial.git
git@api.gitlife.ru:oschina-mirror/rcore-os-rCore-Tutorial.git
oschina-mirror
rcore-os-rCore-Tutorial
rcore-os-rCore-Tutorial
master