WARNING本文所有内容均为翻译,原文:Tutorial 03 - Hacky Hello World;原项目:Operating System development tutorials in Rust on the Raspberry Pi
概述
- 引入全局
print!()宏以尽早实现“printf调试”。 - 为了保证本章不至于过长,打印功能目前“滥用”了一个QEMU属性,以允许我们在未正确设置下使用树莓派的
UART。 - 对如
UART等真实硬件的使用,将在后续章节中逐步展开。
需要注意的新增代码
-
src/console.rsintroduces interfaceTraitsfor console commands. -
src/bsp/raspberrypi/console.rsimplements the interface for QEMU’s emulated UART. -
The panic handler makes use of the new
print!()to display user error messages. -
There is a new Makefile target,
make test, intended for automated testing. It boots the compiled kernel inQEMU, and checks for an expected output string produced by the kernel. -
In this tutorial, it checks for the string
Stopping here, which is emitted by thepanic!()at the end ofmain.rs. -
src/console.rs引入的interface中的trait将用于控制台命令行。 -
src/bsp/raspberrypi/console.rs为QEMU虚拟的UART设备实现了接口。 -
panic处理程序使用新的
print!()宏显示用户错误消息。 -
新增了一个Makefile目标——
make test——用于自动化测试。它能够在QEMU中引导编译后的内核,并检查内核能否输出预期的字符串。- 在本章,它会检查由
main.rs末尾的panic!()打印的字符串:Stopping here。
- 在本章,它会检查由
测试运行
至此,QEMU便不再以汇编模式运行了。从本章开始,它将显示console的输出。
$ make qemu[...]Hello from Rust!
Kernel panic: Stopping here.与上一章代码的区别
diff -uNr 02_runtime_init/Cargo.toml 03_hacky_hello_world/Cargo.toml--- 02_runtime_init/Cargo.toml+++ 03_hacky_hello_world/Cargo.toml@@ -1,6 +1,6 @@ [package] name = "mingo"-version = "0.2.0"+version = "0.3.0" authors = ["Andre Richter <andre.o.richter@gmail.com>"] edition = "2018"
diff -uNr 02_runtime_init/Makefile 03_hacky_hello_world/Makefile--- 02_runtime_init/Makefile+++ 03_hacky_hello_world/Makefile@@ -23,7 +23,7 @@ KERNEL_BIN = kernel8.img QEMU_BINARY = qemu-system-aarch64 QEMU_MACHINE_TYPE = raspi3- QEMU_RELEASE_ARGS = -d in_asm -display none+ QEMU_RELEASE_ARGS = -serial stdio -display none OBJDUMP_BINARY = aarch64-none-elf-objdump NM_BINARY = aarch64-none-elf-nm READELF_BINARY = aarch64-none-elf-readelf@@ -34,7 +34,7 @@ KERNEL_BIN = kernel8.img QEMU_BINARY = qemu-system-aarch64 QEMU_MACHINE_TYPE =- QEMU_RELEASE_ARGS = -d in_asm -display none+ QEMU_RELEASE_ARGS = -serial stdio -display none OBJDUMP_BINARY = aarch64-none-elf-objdump NM_BINARY = aarch64-none-elf-nm READELF_BINARY = aarch64-none-elf-readelf@@ -70,17 +70,20 @@ --strip-all \ -O binary
-EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)+EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)+EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb
##------------------------------------------------------------------------------ ## Dockerization ##-------------------------------------------------------------------------------DOCKER_IMAGE = rustembedded/osdev-utils-DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial-DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i+DOCKER_IMAGE = rustembedded/osdev-utils+DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial+DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i+DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common
DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE)+DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE)
@@ -168,3 +171,28 @@ ##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json++++##--------------------------------------------------------------------------------------------------+## Testing targets+##--------------------------------------------------------------------------------------------------+.PHONY: test test_boot++ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board.++test_boot test :+ $(call colorecho, "\n$(QEMU_MISSING_STRING)")++else # QEMU is supported.++##------------------------------------------------------------------------------+## Run boot test+##------------------------------------------------------------------------------+test_boot: $(KERNEL_BIN)+ $(call colorecho, "\nBoot test - $(BSP)")+ @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)++test: test_boot++endif
diff -uNr 02_runtime_init/src/bsp/raspberrypi/console.rs 03_hacky_hello_world/src/bsp/raspberrypi/console.rs--- 02_runtime_init/src/bsp/raspberrypi/console.rs+++ 03_hacky_hello_world/src/bsp/raspberrypi/console.rs@@ -0,0 +1,47 @@+// SPDX-License-Identifier: MIT OR Apache-2.0+//+// Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>++//! BSP console facilities.++use crate::console;+use core::fmt;++//--------------------------------------------------------------------------------------------------+// Private Definitions+//--------------------------------------------------------------------------------------------------++/// A mystical, magical device for generating QEMU output out of the void.+struct QEMUOutput;++//--------------------------------------------------------------------------------------------------+// Private Code+//--------------------------------------------------------------------------------------------------++/// Implementing `core::fmt::Write` enables usage of the `format_args!` macros, which in turn are+/// used to implement the `kernel`'s `print!` and `println!` macros. By implementing `write_str()`,+/// we get `write_fmt()` automatically.+///+/// See [`src/print.rs`].+///+/// [`src/print.rs`]: ../../print/index.html+impl fmt::Write for QEMUOutput {+ fn write_str(&mut self, s: &str) -> fmt::Result {+ for c in s.chars() {+ unsafe {+ core::ptr::write_volatile(0x3F20_1000 as *mut u8, c as u8);+ }+ }++ Ok(())+ }+}++//--------------------------------------------------------------------------------------------------+// Public Code+//--------------------------------------------------------------------------------------------------++/// Return a reference to the console.+pub fn console() -> impl console::interface::Write {+ QEMUOutput {}+}
diff -uNr 02_runtime_init/src/bsp/raspberrypi.rs 03_hacky_hello_world/src/bsp/raspberrypi.rs--- 02_runtime_init/src/bsp/raspberrypi.rs+++ 03_hacky_hello_world/src/bsp/raspberrypi.rs@@ -4,4 +4,5 @@
//! Top-level BSP file for the Raspberry Pi 3 and 4.
+pub mod console; pub mod cpu;
diff -uNr 02_runtime_init/src/console.rs 03_hacky_hello_world/src/console.rs--- 02_runtime_init/src/console.rs+++ 03_hacky_hello_world/src/console.rs@@ -0,0 +1,19 @@+// SPDX-License-Identifier: MIT OR Apache-2.0+//+// Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>++//! System console.++//--------------------------------------------------------------------------------------------------+// Public Definitions+//--------------------------------------------------------------------------------------------------++/// Console interfaces.+pub mod interface {+ /// Console write functions.+ ///+ /// `core::fmt::Write` is exactly what we need for now. Re-export it here because+ /// implementing `console::Write` gives a better hint to the reader about the+ /// intention.+ pub use core::fmt::Write;+}
diff -uNr 02_runtime_init/src/main.rs 03_hacky_hello_world/src/main.rs--- 02_runtime_init/src/main.rs+++ 03_hacky_hello_world/src/main.rs@@ -104,13 +104,17 @@ //! - It is implemented in `src/_arch/__arch_name__/cpu/boot.s`. //! 2. Once finished with architectural setup, the arch code calls `kernel_init()`.
+#![feature(format_args_nl)] #![feature(global_asm)]+#![feature(panic_info_message)] #![no_main] #![no_std]
mod bsp;+mod console; mod cpu; mod panic_wait;+mod print;
/// Early init code. ///@@ -118,5 +122,7 @@ /// /// - Only a single core must be active and running this function. unsafe fn kernel_init() -> ! {- panic!()+ println!("[0] Hello from Rust!");++ panic!("Stopping here.") }
diff -uNr 02_runtime_init/src/panic_wait.rs 03_hacky_hello_world/src/panic_wait.rs--- 02_runtime_init/src/panic_wait.rs+++ 03_hacky_hello_world/src/panic_wait.rs@@ -4,10 +4,16 @@
//! A panic handler that infinitely waits.
-use crate::cpu;+use crate::{cpu, println}; use core::panic::PanicInfo;
#[panic_handler]-fn panic(_info: &PanicInfo) -> ! {+fn panic(info: &PanicInfo) -> ! {+ if let Some(args) = info.message() {+ println!("\nKernel panic: {}", args);+ } else {+ println!("\nKernel panic!");+ }+ cpu::wait_forever() }
diff -uNr 02_runtime_init/src/print.rs 03_hacky_hello_world/src/print.rs--- 02_runtime_init/src/print.rs+++ 03_hacky_hello_world/src/print.rs@@ -0,0 +1,38 @@+// SPDX-License-Identifier: MIT OR Apache-2.0+//+// Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>++//! Printing.++use crate::{bsp, console};+use core::fmt;++//--------------------------------------------------------------------------------------------------+// Public Code+//--------------------------------------------------------------------------------------------------++#[doc(hidden)]+pub fn _print(args: fmt::Arguments) {+ use console::interface::Write;++ bsp::console::console().write_fmt(args).unwrap();+}++/// Prints without a newline.+///+/// Carbon copy from <https://doc.rust-lang.org/src/std/macros.rs.html>+#[macro_export]+macro_rules! print {+ ($($arg:tt)*) => ($crate::print::_print(format_args!($($arg)*)));+}++/// Prints with a newline.+///+/// Carbon copy from <https://doc.rust-lang.org/src/std/macros.rs.html>+#[macro_export]+macro_rules! println {+ () => ($crate::print!("\n"));+ ($($arg:tt)*) => ({+ $crate::print::_print(format_args_nl!($($arg)*));+ })+}
diff -uNr 02_runtime_init/tests/boot_test_string.rb 03_hacky_hello_world/tests/boot_test_string.rb--- 02_runtime_init/tests/boot_test_string.rb+++ 03_hacky_hello_world/tests/boot_test_string.rb@@ -0,0 +1,3 @@+# frozen_string_literal: true++EXPECTED_PRINT = 'Stopping here'