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

OSCHINA-MIRROR/rcore-os-rCore-Tutorial

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

Реализация внутреннего ядра для переотображения памяти

В предыдущем тексте мы создали простое отображение, которое позволяет ядру работать в виртуальном пространстве. Однако это отображение является довольно грубым.

Мы знаем, что программа обычно состоит из следующих частей:

  • .text сегмент: содержит код, который должен быть читаемым и исполняемым, но не записываемым;
  • .rodata сегмент: содержит только читаемые данные, которые должны быть доступны для чтения, но не для записи или выполнения;
  • .data сегмент: содержит данные, которые были инициализированы и должны быть доступными для чтения и записи;
  • .bss сегмент: содержит неинициализированные данные, которые также должны быть доступными для чтения и записи.

Мы видим, что доступ к различным сегментам имеет разные права доступа. В текущем отображении мы даже можем изменять код в сегменте .text ядра, поскольку мы используем таблицу страниц с флагом W, установленным в 1.

Поэтому мы рассматриваем возможность переотобразить эти сегменты таким образом, чтобы их права доступа были правильно установлены.

Это требование можно абстрагировать как отображение одного сегмента памяти (возможно, нескольких виртуальных страниц) на множество физических страниц, при этом этот сегмент памяти будет иметь унифицированные свойства и более высокий уровень управления.

Например, в коде ядра сегмент .bss может занимать не одну страницу, а несколько страниц. Нам нужно отобразить все эти страницы линейно в одно место. При этом весь этот сегмент памяти будет управляться ядром.

Теперь мы сначала создадим концепцию сегмента памяти.

Сегмент памяти Segment

Как уже было сказано, сегмент памяти представляет собой непрерывный диапазон виртуальных страниц, каждая из которых отображается линейно (с прямым смещением на физическую страницу) или распределяется (каждая виртуальная страница вызывает распределитель физических страниц для выделения физической страницы). Линейное отображение происходит в пространстве ядра; однако, чтобы поддерживать одинаковое виртуальное пространство для каждого пользовательского процесса, мы не можем использовать только линейное отображение, поэтому метод распределения на основе страниц будет использоваться в пользовательских сценариях. Если вы всё ещё не понимаете, вы можете обратиться к разделу «Виртуальный адрес к физическому адресу» в этой главе для просмотра карты отображения rCore без обучения.

Далее мы будем использовать enum и struct для инкапсуляции типов отображения памяти и самого сегмента памяти:

/// Тип отображения
#[derive(Debug)]
pub enum MapType {
    /// Линейное отображение, используемое операционной системой
    Linear,
    /// Отображение с распределением по кадрам
    Framed,
}

/// Один сегмент отображения (соответствует старому tutorial MemoryArea)
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Segment {
    /// Тип отображения
    pub map_type: MapType,
    /// Диапазон отображаемых виртуальных адресов
    pub range: Range<VirtualAddress>,
    /// Флаги разрешений
    pub flags: Flags,
}

Затем верхний уровень должен сделать следующее: отобразить все виртуальные страницы в сегменте, для которого не установлено физическое отображение страниц, на физические страницы и установить отображение (или линейное отображение не требует отображения для виртуальных страниц, а отображение с распределением требует отображения каждой виртуальной страницы на соответствующую физическую страницу).

Таким образом, мы можем реализовать итератор, который требуется для конкретного распределения:

impl Segment {
    /// Перебор соответствующих физических адресов (если возможно)
    pub fn iter_mapped(&self) -> Option<impl Iterator<Item = PhysicalPageNumber>> {
        match self.map_type {
            // Линейное отображение может напрямую преобразовывать виртуальные адреса
            MapType::Linear => Some(self.page_range().into().iter()),
            // Отображение с распределением не может напрямую получить физический адрес, необходимо распределить
            MapType::Framed => None,
        }
    }
}

Отображение Mapping

Имея таблицы страниц и сегменты памяти, мы объединяем и инкапсулируем их, используя операции над таблицами страниц для реализации отображения сегментов памяти или, можно сказать, здесь структура является дальнейшим развитием от одноуровневой до трехуровневой инкапсуляции таблиц страниц из предыдущего раздела, требующей отслеживания корневых таблиц страниц и таблиц страниц, выделенных для контроля того, когда освобождать пространство.

#[derive(Default)]
/// Отображение памяти для одного потока
pub struct Mapping {
    /// Сохраняет все используемые таблицы страниц
    page_tables: Vec<PageTableTracker>,
    /// Физический номер страницы корневой таблицы страниц
    root_ppn: PhysicalPageNumber,
    /// Все выделенные физические номера страниц отображаемой информации
    mapped_pairs: VecDeque<(VirtualPageNumber, FrameTracker)>,
}

impl Mapping {
    /// Создание отображения с корнем
    pub fn new() -> MemoryResult<Mapping> {
        let root_table = PageTableTracker::new(FRAME_ALLOCATOR.lock().alloc()?);
        let root_ppn = root_table.page_number();
        Ok(Mapping {
            page_tables: vec![root_table],
            root_ppn,
            mapped_pairs: VecDeque::new(),
        })
    }
}

Позже мы реализуем поиск в таблице страниц и используем эту функцию для реализации сопоставления виртуальных номеров страниц с физическими номерами страниц:

/// Поиск заданной виртуальной страницы в трёхуровневой таблице страниц
///
/// Если соответствующая таблица страниц не найдена, она будет создана
pub fn find_entry(&mut self, vpn: VirtualPageNumber) -> MemoryResult<&mut PageTableEntry> {
    // Начинаем поиск с корневой таблицы страниц
    let root_table: &mut PageTable = PhysicalAddress::from(self.root_ppn).deref_kernel();
    let mut entry = &mut root_table.entries[vpn.levels()[0]];
    for vpn_slice in &vpn.levels()[1..] {
        if entry.is_empty() {
            // Если таблица страниц отсутствует, необходимо выделить новую таблицу страниц
            let new_table = PageTableTracker::new(FRAME_ALLOCATOR.lock().alloc()?);
            let new_ppn = new_table.page_number();
            // Записываем номер новой страницы в текущую запись таблицы страниц
            *entry = PageTableEntry::new(Some(new_ppn), Flags::VALID);
            // Сохраняем таблицу страниц
            self.page_tables.push(new_table);
        }
        // Переходим к следующей таблице страниц (используя смещение для доступа к физическим адресам)
        entry = &mut entry.get_next_table().entries[*vpn_slice];
    }
    // Теперь entry находится в третьей таблице страниц
    Ok(entry)
}

/// Сопоставление заданного виртуального / физического номера страницы
fn map_one(
    &mut self,
    vpn: VirtualPageNumber,
    ppn: Option<PhysicalPageNumber>,
    flags: Flags,
) -> MemoryResult<()> {
    // Находим запись в таблице страниц
    let entry = self.find_entry(vpn)?;
    assert!(entry.is_empty(), "virtual address is already mapped");
    // Запись в таблице пуста, записываем содержимое
    *entry = PageTableEntry::new(ppn, flags);
    Ok(())
}

С помощью функции map_one для сопоставления виртуальной страницы с физической страницей мы можем реализовать сопоставление всего непрерывного сегмента: Текст запроса:

pub fn map(&mut self, segment: &Segment, init_data: Option<&[u8]>) -> MemoryResult<()> {
    match segment.map_type {
        // 线性映射,直接对虚拟地址进行转换
        MapType::Linear => {
            for vpn in segment.page_range().iter() {
                self.map_one(vpn, Some(vpn.into()), segment.flags)?;
            }
            // 拷贝数据
            if let Some(data) = init_data {
                unsafe {
                    (&mut *slice_from_raw_parts_mut(segment.range.start.deref(), data.len()))
                        .copy_from_slice(data);
                }
            }
        }
        // 需要分配帧进行映射
        MapType::Framed => {
            for vpn in segment.page_range().iter() {
                // 页面的数据,默认为全零
                let mut page_data = [0u8; PAGE_SIZE];
                // 如果提供了数据,则使用这些数据来填充 page_data
                if let Some(init_data) = init_data {
                    if !init_data.is_empty() {
                        // 这里必须进行一些调整,因为传入的数据可能并非按照整页对齐

                        // 拷贝时必须考��sie区间与整页不对齐的情况
                        // start(仅第一页时非零)
                        // |        stop(仅最后一页时非零)
                        // 0    |---data---|          4096
                        // |------------page------------|
                        let page_address = VirtualAddress::from(vpn);
                        let start = if segment.range.start > page_address {
                            segment.range.start - page_address
                        } else {
                            0
                        };
                        let stop = min(PAGE_SIZE, segment.range.end - page_address);
                        // 计算来源和目标区间并进行拷贝
                        let dst_slice = &mut page_data[start..stop];
                        let src_slice = &init_data[(page_address + start - segment.range.start)
                            ..(page_address + stop - segment.range.start)];
                        dst_slice.copy_from_slice(src_slice);
                    }
                };

                // 建立映射
                let mut frame = FRAME_ALLOCATOR.lock().alloc()?;
                // 更新页表
                self.map_one(vpn, Some(frame.page_number()), segment.flags)?;
                // 写入数据
                (*frame).copy_from_slice(&page_data);
                // 保存
                self.mapped_pairs.push_back((vpn, frame));
            }
        }
    }
    Ok(())
}

Перевод текста на русский язык:

Функция map принимает два аргумента: изменяемую ссылку на себя и ссылку на сегмент. Также она принимает параметр init_data, который является опциональным. Функция возвращает результат типа MemoryResult.

В зависимости от значения map_type сегмента выполняется различная логика. Если значение равно Linear, то для каждого виртуального номера страницы (VPN) вызывается функция map_one. В противном случае, если значение равно Framed, то также для каждого VPN вызывается map_one, но перед этим происходит дополнительная логика.

Если значение параметра init_data не равно None, то данные копируются в память. Для этого создаётся срез данных, размер которого равен размеру страницы. Затем проверяется, есть ли данные в init_data. Если данные есть, то они копируются в созданный срез.

Далее, в зависимости от значения параметра map_type, выполняется различная логика. Если map_type равен Linear, то происходит линейное отображение. Если же map_type равен Framed, то необходимо выделить фрейм для отображения.

Для каждого VPN создаётся фрейм с помощью функции alloc. Затем вызывается функция map_one для обновления таблицы страниц. После этого данные из созданного среза копируются во фрейм. Наконец, пара значений (VPN, фрейм) добавляется в список mapped_pairs. Данный текст написан на языке Rust.

Вот его перевод на русский язык:

Далее мы полностью реализуем переназначение ядра, и в конце можно протестировать это в основной функции:

{% label %}os/src/main.rs{% endlabel %}

/// Функция входа в Rust
///
/// После того как _start выполнил для нас ряд подготовительных действий, это первая вызываемая функция Rust
#[no_mangle]
pub extern "C" fn rust_main() -> ! {
    // Инициализируем различные модули
    interrupt::init();
    memory::init();

    let remap = memory::mapping::MemorySet::new_kernel().unwrap();
    remap.activate();

    println!("kernel remapped");

    panic!()
}

Здесь мы запрашиваем переназначение ядра и активируем таблицу страниц, затем выполняем вывод. Хотя кажется, что ничего не изменилось, просто выводится одна строка, но следует отметить, что вся логика, используемая в этой строке, уже основана на новой созданной таблице страниц, а не на грубой boot_page_table. boot_page_table всё ещё используется, она поддерживает создание переназначения, но в конечном итоге мы заменим её более точной таблицей страниц и переназначением, чтобы реализовать более детальное управление и безопасность.

Таким образом, мы реализовали переназначение, и выше мы просто использовали локальную переменную для вызова простого теста этого переназначения. На самом деле позже мы инкапсулируем всю логику выполнения в поток, каждый поток будет иметь MemorySet и существовать в структуре потока, а не в простой локальной переменной. Когда поток уничтожается, вся используемая логика (включая физическую страницу таблицы страниц и другие запрошенные физические страницы) автоматически освобождается ранее разработанным механизмом отслеживания.

Следует признать, что писать этот контент на Rust болезненно (возможно, следующие одна-две главы также будут болезненными в течение некоторого времени), но эти усилия необходимы для полного использования возможностей Rust, и как только мы создадим эту инфраструктуру, последующие процессы будут значительно упрощены. Мы также провели много обсуждений по поводу содержания этих двух глав и сделали много дизайнерских и педагогических компромиссов. Если вы всё ещё не понимаете документ, вы можете прочитать полный код и соответствующие комментарии и попробовать запустить его.

Опубликовать ( 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