virtio
Чтобы смонтировать в QEMU наше виртуальное устройство хранения данных, мы выбрали протокол virtio, поддерживаемый QEMU. При запуске QEMU необходимо добавить опцию:
# Запуск QEMU
qemu: build
@qemu-system-riscv64 \
-machine virt \
-nographic \
-bios default \
-device loader,file=$(BIN_FILE),addr=0x80200000 \
-drive file=$(TEST_IMG),format=raw,id=sfs # Имитация устройства хранения данных
-device virtio-blk-device,drive=sfs # Монтирование в виде virtio Block Device на общую шину virtio
Здесь TEST_IMG
— это образ файловой системы на диске, который мы не будем рассматривать в этом разделе. Здесь можно использовать тестовый образ из каталога.
Virtio берёт своё начало от статьи virtio: Towards a De-Facto Standard For Virtual I/O Devices. Она посвящена абстракции универсальных устройств в полувиртуализированных технологиях.
[info] Полная и полувиртуализация
В полностью виртуализированной среде виртуальная операционная система работает поверх гипервизора на физическом компьютере. Виртуальная операционная система может работать без изменений в этой конфигурации. В полувиртуализации виртуальная операционная система не только знает, что она работает на гипервизоре, но и содержит код для более эффективного перехода к гипервизору.
В режиме полной виртуализации гипервизор должен имитировать аппаратное обеспечение, которое эмулируется на самом низком уровне (например, сетевые драйверы). Хотя моделирование здесь чистое, оно также является самым неэффективным и сложным. В режиме полувиртуализации виртуальная операционная система и гипервизор могут сотрудничать, чтобы сделать моделирование более эффективным. Недостатком полувиртуализации является то, что операционная система знает, что она виртуализирована, и требует модификации для работы.
В частности, архитектура virtio выглядит следующим образом:
На общей шине virtio монтируются дополнительные шины, такие как virtio-blk (блочное устройство), virtio-net (сетевое устройство) и virtio-pci (PCI-устройство). Сама по себе она образует дерево устройств.
В предыдущем разделе мы реализовали обнаружение узлов «virtio,mmio». Теперь мы продолжим различать упомянутые выше устройства virtio:
/// Из узла дерева устройств определить тип конкретного протокола virtio
pub fn virtio_probe(node: &Node) {
// reg атрибут содержит информацию о расположении заголовка устройства
let reg = match node.prop_raw("reg") {
Some(reg) => reg,
_ => return,
};
let pa = PhysicalAddress(reg.as_slice().read_be_u64(0).unwrap() as usize);
let va = VirtualAddress::from(pa);
let header = unsafe { &mut *(va.0 as *mut VirtIOHeader) };
// В настоящее время поддерживается только определённая версия протокола virtio
if !header.verify() {
return;
}
// Определить тип устройства
match header.device_type() {
DeviceType::Block => virtio_blk::add_driver(header),
device => println!("unrecognized virtio device: {:?}", device),
}
}
Из информации reg узла дерева устройств можно прочитать более подробную информацию о местоположении устройства (например, в диапазоне 0x10000000 - 0x10010000). Хотя этот диапазон считается областью памяти, вы помните? Наша физическая память находится только в диапазоне от 0x80000000 до 0x88000000. Откуда взялся этот диапазон? Это так называемая память, отображаемая для чтения и записи MMIO (Memory Mapped I/O), которая представляет собой способ, которым шина передаёт информацию об операции с устройством и отображает её как часть памяти. CPU обрабатывает устройства и доступ к памяти одинаково, но эффекты чтения и записи различны. Вы можете вспомнить доступ к последовательному порту в компьютерной структуре. Это тот же принцип.
Поэтому, чтобы получить доступ к этому диапазону, нам также нужно добавить его в таблицу страниц. Они соответствуют boot_page_table
в os/src/entry.asm
и новому ядру потока в os/src/memory/mapping/memory_set.rs
, чтобы наш поток ядра мог получить к ним доступ.
Мы не будем реализовывать каждый аспект драйвера здесь. Аналогично, мы используем библиотеку virtio_drivers в rCore, которая помогает нам взаимодействовать с устройствами через MMIO. Нам также необходимо предоставить библиотеке некоторые интерфейсы, такие как запрос физической памяти и преобразование физических адресов в виртуальные.
lazy_static! {
/// Используется для размещения физических страниц, используемых для DMA (FrameTracker)
pub static ref TRACKERS: RwLock<BTreeMap<PhysicalAddress, FrameTracker>> =
RwLock::new(BTreeMap::new());
}
/// Для операций DMA выделить непрерывные страницы физической памяти (предоставляется библиотеке virtio_drivers)
///
/// Почему требуется непрерывная физическая память? Операции DMA с оборудованием затрагивают только память и соответствующее оборудование
/// Этот процесс не затрагивает механизм MMU ЦП, поэтому мы можем передавать оборудованию только физические адреса
/// Учитывая наш предыдущий дизайн, который позволяет выделять только одну физическую страницу за раз, здесь мы предполагаем, что выделенные адреса являются смежными
#[no_mangle]
extern "C" fn virtio_dma_alloc(pages: usize) -> PhysicalAddress {
let mut pa: PhysicalAddress = Default::default();
let mut last: PhysicalAddress = Default::default();
for i in 0..pages {
let tracker: FrameTracker = FRAME_ALLOCATOR.lock().alloc().unwrap();
if i == 0 {
pa = tracker.address();
} else {
assert_eq!(last + PAGE_SIZE, tracker.address());
}
last = tracker.address();
TRACKERS.write().insert(last, tracker);
}
return pa;
}
/// Освободить соответствующую ранее выделенную непрерывную физическую страницу для операций DMA (предоставляется библиотеке virtio_drivers)
#[no_mangle]
extern "C" fn virtio_dma_dealloc(pa: PhysicalAddress, pages: usize) -> i32 {
for i in 0..pages {
TRACKERS.write().remove(&(pa + i * PAGE_SIZE));
}
0
}
/// Преобразовать физический адрес в виртуальный (предоставляется библиотеке virtio_drivers)
///
/// Обратите внимание, что мы сопоставили все сегменты в 0xffffffff80200000-0xffffffff88000000
/// Поскольку мы уже поместили все сегменты во внутреннее переназначение при перезагрузке ядра,
/// Физический адрес напрямую добавляется к смещению, чтобы получить виртуальный адрес, доступный через любую внутреннюю таблицу страниц процесса
#[no_mangle]
extern "C" fn
``` virtio_phys_to_virt(pa: PhysicalAddress) -> VirtualAddress {
VirtualAddress::from(pa)
}
/// Преобразование виртуального адреса в физический (для библиотеки `virtio_drivers`)
/// Обратите внимание, что цель реализации этой функции — сообщить DMA конкретный запрос, который будет размещён в стеке в процессе реализации.
/// В нашей реализации стек распределяется в виде фреймов (`Framed`), а не как линейное отображение высокого адреса (`Linear`).
/// Чтобы получить правильный физический адрес и сообщить его DMA-устройству, мы можем только обратиться к таблице страниц.
#[no_mangle]
extern "C" fn virtio_virt_to_phys(va: VirtualAddress) -> PhysicalAddress {
Mapping::lookup(va).unwrap()
}
**Почему необходимо реализовать эти операции?**
Устройства работают на основе технологии **прямого доступа к памяти DMA (Direct Memory Access)**. Процессор должен только указать, какие данные и в какой сегмент физической памяти следует передать, а затем сообщить устройству о запросе. После этого устройство может передавать данные напрямую, без участия процессора. По завершении передачи устройство сообщает процессору об этом с помощью технологии **прерываний IRQ (Interrupt ReQuest)**, и процессор обрабатывает это прерывание.
Для реализации DMA нам нужны запросы и пространство памяти. Например, чтобы диск передал данные в определённый сегмент памяти, нам нужно сообщить устройству физический адрес памяти (он не может быть виртуальным, так как DMA не проходит через MMU процессора). Этот физический адрес должен быть непрерывным.
В то же время мы создаём структуру запроса в стеке, и нам также необходимо сообщить устройству физический адрес этой структуры. Поэтому нам нужны интерфейсы для преобразования виртуальных и физических адресов.
На данный момент наш `FRAME_ALLOCATOR` может выделить только один кадр. Мы вызываем его несколько раз, временно предполагая, что он является непрерывным. Для реализации преобразования виртуальных и физических адресов нам необходимо обратиться к таблице страниц, но, к сожалению, RISC-V не предоставляет удобной инструкции для поиска физического адреса по текущей таблице страниц. Поэтому мы реализовали подобную функцию в файле `os/src/memory/mapping/mapping.rs`.
### Размышления
Почему преобразование физического адреса в виртуальный осуществляется путём линейного отображения, а преобразование виртуального в физический требует обращения к таблице?
Мы используем адреса для доступа к содержимому по этим адресам. Важно отметить, что в диапазоне от 0x80000000 до 0x88000000 физические страницы могут соответствовать двум виртуальным страницам. При запуске или создании нового потока ядра мы включаем такие линейные отображения, как 0xffffffff80000000–0x80000000. Это означает, что внутри потока ядра любой физический адрес, добавленный к смещению, гарантированно приведёт к доступу к соответствующему физическому адресу. Таким образом, преобразование физического адреса в виртуальный путём добавления смещения возможно.
Также стоит учесть, что хотя код и данные потока ядра линейно отображаются, стек ядра выделяется в единицах фрейма (за исключением загрузочного потока, который помещается непосредственно в .bss). Выделение в единицах фреймов означает, что виртуальный адрес может начинаться с нуля. В этом случае для преобразования в физический адрес нельзя использовать линейное смещение, необходимо обращаться к текущей таблице.
Возможно, вы спросите: почему RISC-V позволяет использовать виртуальные адреса для доступа, но мне всё равно приходится вручную создавать таблицу для выполнения этой задачи? Это связано с тем, что RISC-V действительно не имеет инструкции, которая напрямую использует MMU для получения адреса, поэтому мы вынуждены делать это вручную.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )