Как прочитать память напрямую по адресу (Rust)?
Я изучал работу с голым железом на Rust. Начал я с VGA: Узнал, что могу записывать в него данные вот так:
fn main(){
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) };
}
struct Buffer {
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT]
}
pub struct VgaWriter {
column_position: usize,
buffer: &'static mut Buffer
}
// ...
То есть по сути напрямую укзать: У меня по адресу 0xb8000 лежит VGA буфер, хочу туда писать информацию. И это работает (ну или хотя бы не вылетает) (тестирую в QEMU, собирая как загрузочный образ через crate bootloader).
Теперь я пошел дальше: Я захотел записать что-нибудь в serial port. Я узнал, что в QEMU он находится по адресу 0x3f8.
Теперь я захотел что-нибудь считать с него и с соседних адресов, похожим образом (как я понял, это как бы входы следующих пинов порта):
let a = unsafe { &mut *(0x3f8 as *mut [u8; 25]) };
for i in a{
print!("{}", i);
}
Такой код приводит к циклическому перезапуску образа в QEMU, несколько раз в секунду, даже не выпадая в панику. Я подумал, что настроил что-то не так, но работа с портом через uart_16550 работает отлично:
use uart_16550::SerialPort;
fn main(){
unsafe{ SerialPort::new(0x3f8).write_str("Hello world"); };
}
Посмотрев внутрь, я понял, что где-то в глубине, считывание с адреса происходит примерно следующим образом:
fn main(){
for i in 0..25{
let value: u8;
unsafe {
asm!("in al, dx", out("al") value, in("dx") 0x3f8+i, options(nomem, nostack, preserves_flags));
}
println!("{}", value);
}
}
И да, таким образом я могу получить какую-то информацию на экран (в как раз таки VGA режиме) (пока не знаю как ей пользоваться, но вопрос в другом):
Мой вопрос состоит в следующем: Почему я могу записывать и читать в VGA напрямую порт по адресу 0xb8000, но не могу считывать в адресе 0x3f8, и вообще в других адресах.
И, ещё важнее, почему я всё-таки могу делать это, но только через asm команды?
Как я могу обойти это ограничение? Или я вообще делаю что-то в корне неправильное/
Ответы (1 шт):
Порты ввода-вывода - это порты ввода-вывода. А память это память. Через ассемблерную инструкцию in производится чтение с порта ввода-вывода. Процессор х86 имеет некие фичи, применив которые можно работать какбы с памятью - но реально с портами. Но чтобы это работало, его надо настроить. Так просто работать не будет. В каждой ОС это настраивается по разному. В линуксе ключевые слова ioremap, request_mem_region, request_region выведут вас на нужные статьи.
Кстати говоря, современный процессор частенько переупорядочивает команды, которые выполняет, и компилятор тоже - для ускорения работы. То есть если вы пишите:
a = 4
b = 5
Реальный то порядок присваивания неопределен. Может присвоиться в том порядке, как написано - а может и наоборот: сначала присвоится b и только потом a. И всякие чтения тоже может переупорядочивать. Если это работа с железом, или многопоточное использование, то это может оказаться большой проблемой, и надо делать дополнительные телодвижения, чтобы такого переупорядочивания не было. В двух словах так, дальше - ищите статьи с примерами.

