Интерфейсы упаковки и код организации
Использование сервисов, предоставляемых OpenSBI
OpenSBI фактически выполняет не только роль загрузчика, но также предоставляет некоторые базовые системные сервисы для использования при написании ядра, что упрощает реализацию ядра и повышает его способность работать на различных аппаратных конфигурациях. Этот базовый системный сервисный интерфейс называется SBI (Supervisor Binary Interface), который является стандартным интерфейсом соглашения между режимом S Mode операционной системы и средой выполнения M Mode.
Согласно документации OpenSBI, мы обнаруживаем, что она содержит некоторые интерфейсы C-функций, которые мы можем вызывать.
В предыдущем разделе функция console_putchar похожа на вызов следующего интерфейса:
void sbi_console_putchar(int ch)
Фактический процесс выглядит следующим образом: операционная система в режиме S через ecall инициирует запрос вызова SBI, и RISC-V CPU переходит из режима S в режим M, где работает OpenSBI. OpenSBI проверяет номер вызова SBI, отправленный операционной системой, и обрабатывает его, если номер находится в диапазоне от 0 до 8, в противном случае он передаёт обработку средству обработки прерываний (которое ещё не реализовано). Для получения дополнительной информации о системных вызовах с номерами в диапазоне 0–8 обратитесь к документации OpenSBI.
Перед выполнением ecall необходимо указать номер вызова SBI и передать параметры. Обычно a7 (x17) используется для номера вызова SBI, а a0 (x10), a1 (x11) и a2 (x12) используются для параметров вызова SBI:
{% label %}os/src/sbi.rs{% endlabel %}
//! 调用 Machine 层的操作
// 目前还不会用到全部的 SBI 调用,暂时允许未使用的变量或函数
#![allow(unused)]
/// SBI 调用
#[inline(always)]
fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
let ret;
unsafe {
llvm_asm!("ecall"
: "={x10}" (ret)
: "{x10}" (arg0), "{x11}" (arg1), "{x12}" (arg2), "{x17}" (which)
: "memory" // 如果汇编可能改变内存,则需要加入 memory 选项
: "volatile"); // 防止编译器做激进的优化(如调换指令顺序等破坏 SBI 调用行为的优化)
}
ret
}
Для передачи параметров, когда их количество невелико и они являются базовыми типами данных, можно использовать регистры a0–a7 слева направо. Подробные правила см. в спецификации RISC-V Calling Convention.
Что касается настройки регистров и выполнения инструкций по сборке, это выходит за рамки основных возможностей описания языка Rust. Ранее использовавшийся метод global_asm! позволяет вставлять инструкции по сборке в код Rust, но он не очень удобен для взаимодействия кода Rust и кода сборки. Для эффективного взаимодействия между кодом Rust и кодом сборки существует другой способ — встроенный ассемблер (Inline Assembly), который относительно прост в реализации таких требований, как передача u8-типа одиночного символа в a0 в качестве входного параметра. Подробные правила встроенного ассемблера см. в книге «Rust Programming».
В выходной части результат сохраняется в переменной ret, ограничение условия {x10} сообщает компилятору использовать регистр x10 (также известный как a0) в качестве регистра, и знак равенства перед ним указывает, что код сборки изменит этот регистр и будет использоваться в качестве окончательного возвращаемого значения.
Во входной части параметры arg0, arg1, arg2 и which передаются через регистры x10, x11, x12 и x17 (эти четыре регистра также называются a0, a1, a2 и a7), где первые три параметра представляют возможные входные параметры интерфейса, а последний which используется для различения того, какой интерфейс вызывается (SBI Extension ID). Здесь предоставление трёх входных параметров предназначено для охвата всех интерфейсов, и для некоторых интерфейсов некоторые входные параметры являются избыточными, например, поскольку sbi_console_putchar требует только одного входного параметра, он заботится только о значении регистра a0.
Затем используйте функцию sbi_call для реализации соответствующего интерфейса на основе документации OpenSBI и реализуйте функцию выключения через SBI интерфейс:
{% label %}os/src/sbi.rs{% endlabel %}
const SBI_SET_TIMER: usize = 0;
const SBI_CONSOLE_PUTCHAR: usize = 1;
const SBI_CONSOLE_GETCHAR: usize = 2;
const SBI_CLEAR_IPI: usize = 3;
const SBI_SEND_IPI: usize = 4;
const SBI_REMOTE_FENCE_I: usize = 5;
const SBI_REMOTE_SFENCE_VMA: usize = 6;
const SBI_REMOTE_SFENCE_VMA_ASID: usize = 7;
const SBI_SHUTDOWN: usize = 8;
/// 向控制台输出一个字符
///
/// 需要注意我们不能直接使用 Rust 中的 char 类型
pub fn console_putchar(c: usize) {
sbi_call(SBI_CONSOLE_PUTCHAR, c, 0, 0);
}
/// 从控制台中读取一个字符
///
/// 没有读取到字符则返回 -1
pub fn console_getchar() -> usize {
sbi_call(SBI_CONSOЛЕ_GETCHAR, 0, 0, 0)
}
/// 调用 SBI_SHUTDOWN 来关闭操作系统(直接退出 QEMU)
pub fn shutdown() -> ! {
sbi_call(SBI_SHUTDOWN, 0, 0, 0);
unreachable!()
}
Теперь мы более глубоко понимаем, что такое console_putchar. Далее мы используем console_putchar для форматирования вывода, чтобы обеспечить удобство отладки в будущем. Перевод текста на русский язык:
«Реализация вывода строк в виде классов. У нас уже есть готовый макрос format_args!, который может преобразовать входную строку шаблона и список параметров в класс Arguments, например:
format_args!("{} {}", 1, 2).
Поэтому наша идея реализации макроса заключается в следующем:
Чтобы вызвать функцию write_fmt, мы должны реализовать функцию write_str, а её можно реализовать с помощью функции console_putchar.
Наконец, мы можем написать макросы print и println, следуя этой логике. Общая логика кода выглядит следующим образом:»
Далее идёт код на языке Rust.
Конец перевода.
В запросе представлен текст на языке Rust, посвящённый разработке и тестированию программного обеспечения.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )