实验要求
- 实现分支:ch1
- 完成实验指导书中的内容并在裸机上实现
hello world
输出。 - 实现彩色输出宏(只要求可以彩色输出,不要求 log 等级控制,不要求多种颜色)
- 隐形要求
可以关闭内核所有输出。从 lab2 开始要求关闭内核所有输出(如果实现了 log 等级控制,那么这一点自然就实现了)。 - 利用彩色输出宏输出 os 内存空间布局
输出.text
、.data
、.rodata
、.bss
各段位置,输出等级为INFO
。
ANSI 转义字符
echo -e "\x1b[31mhello world\x1b[0m"
参考资源
官方给出如下资源:对于 Rust, 可以使用 crate log ,推荐参考 rCore
crate log 的应用
打开提供的参考链接,可以了解到应用log
这个特质的方式,看Use这一节,
The basic use of the log crate is through the five logging macros: error!
, warn!
, info!
, debug!
and trace!
where error!
represents the highest-priority log messages and trace!
the lowest. The log messages are filtered by configuring the log level to exclude messages with a lower priority. Each of these macros accept format strings similarly to println!
.
在In libraries里可以看到,我们如果要使用log
,则需要使用crate log
,
Libraries should link only to the log
crate, and use the provided macros to log whatever information will be useful to downstream consumers.
例子
参考Examples,创建一个新的工程来测试它,在/homework/homework-1-log
下用cargo new test_log
创建一个测试工程,在test_log/src
下,万万没想到,这个例子竟然成为了最猛的一集,完美展示了不懂一门语言硬学导致的问题.
Rust
的Crate log
的例子中为:
use log::{info, warn};
pub fn shave_the_yak(yak: &mut Yak) {
info!(target: "yak_events", "Commencing yak shaving for {:?}", yak);
loop {
match find_a_razor() {
Ok(razor) => {
info!("Razor located: {}", razor);
yak.shave(razor);
break;
}
Err(err) => {
warn!("Unable to locate a razor: {}, retrying", err);
}
}
}
}
但是这一段是伪代码,运行的时候是会报错的:
Compiling test_log v0.1.0 (/home/winddevil/workspace/homework/homework-1-log/test_log)
error[E0432]: unresolved import `log`
--> src/main.rs:2:5
|
2 | use log::{info, warn};
| ^^^ use of undeclared crate or module `log`
error[E0412]: cannot find type `Yak` in this scope
--> src/main.rs:3:32
|
3 | pub fn shave_the_yak(yak: &mut Yak)
| ^^^ not found in this scope
error[E0425]: cannot find function `find_a_razor` in this scope
--> src/main.rs:9:15
|
9 | match find_a_razor()
| ^^^^^^^^^^^^ not found in this scope
Some errors have detailed explanations: E0412, E0425, E0432.
For more information about an error, try `rustc --explain E0412`.
error: could not compile `test_log` (bin "test_log") due to 3 previous errors
这就是我们对于Rust
的一种比丁真还要纯真的认识导致的,这时候我们把这个报错error[E0432]: unresolved import 'log'
喂给GPT
,人猿相揖别。只几个石头磨过,小儿时节。
善于使用工具正是一个懒狗所需要的,
遇到错误 error[E0432]: unresolved import 'log'
表示Rust编译器无法找到你尝试引入的log
库。这通常是因为以下几个原因:
- 未添加
log
库为依赖:确保你的Cargo.toml
文件中已经添加了log
库作为依赖。如果没有,你需要在[dependencies]
部分添加它。对于最常见的使用场景,添加如下内容:[dependencies] log = "0.4" # 或者其它适用的版本号
然后在项目目录下运行 cargo update
来下载依赖。
2. Cargo特性(gated features):如果你的项目或某个依赖启用了特定的Cargo特性,并且这些特性影响了log
的使用,确保正确配置了这些特性。检查Cargo.toml
中的[features]
部分和相关依赖的特性要求。
3. 路径问题:如果你的项目结构比较特殊,可能需要使用相对路径或绝对路径来正确引入log
。但这通常不是导致上述错误的原因,因为log
通常作为crate直接引入。
4. 编译目标或环境问题:确保你的编译目标和环境配置没有排除log
库的编译或链接。这在交叉编译时可能会成为问题。
5. Cargo.lock的干扰:有时候,旧的Cargo.lock
文件可能锁定了不兼容的依赖版本,导致新添加的依赖无法正确解析。尝试删除Cargo.lock
文件(如果有),然后重新运行cargo build
或cargo run
让Cargo重新生成它。
解决方法通常是先检查并修正Cargo.toml
,确保log
库已经被正确添加为依赖,然后更新依赖并重新编译项目。如果问题依旧存在,检查是否有其他依赖或项目配置干扰了log
的正确解析。
What can I say?
答案是Cargo
没学会,这时候我们修改Cargo.toml
,访问log包的文档,可以看到当前的最新版本为0.4.21
,在文件中添加这一段,
[dependencies]
log = "0.4.21"
接下来我们解决error[E0412]: cannot find type 'Yak' in this scope
的问题,这个问题说明Yak
并不是std
里原本有的一个东西,当然也倒逼我们一定要看清楚源代码,这次我们直接读和理解这个报错,说的是在当前scope
即作用域中找不到Yak
,考虑到这个幺蛾子文档的习惯,我们直接在刚才文档的右上角搜索Yak
,
![[Pasted image 20240628015106.png]]
这里我们看到shave it
意为给它剃毛
,考虑原本的代码中的find_a_razor
函数即寻找剃刀
也报错了error[E0425]: cannot find function
find_a_razor in this scope
,那么就很好理解了,Yak
可能是被制造出来的一个例子库,我们尝试在Cargo.toml
中依赖它,
[dependencies]
yak = "0.1.0"
但是很悲剧的是,这个函数并不存在,也就是这段代码纯是一段伪代码,那么我们直接看这段伪代码:
use log::{info, warn};
pub fn shave_the_yak(yak: &mut Yak) {
info!(target: "yak_events", "Commencing yak shaving for {:?}", yak);
loop {
match find_a_razor() {
Ok(razor) => {
info!("Razor located: {}", razor);
yak.shave(razor);
break;
}
Err(err) => {
warn!("Unable to locate a razor: {}, retrying", err);
}
}
}
}
假设Yak
是一个struct
,可以它有一个对应的impl
,是shave
,而还有一个函数find_a_razor
是可以返回一个razor
类型或者一个err
类型的函数.那么我们可以手动补全这段代码:
use std::error::Error;
use std::fmt;
use std::sync::Mutex;
#[derive(Debug)]
pub struct Yak
{
yak_name: String,
is_shaved: bool
}
pub struct razor
{
used_time: u32
}
#[derive(Debug)]
pub struct err
{
context: String
}
impl Error for err
{
}
impl fmt::Display for err
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
{
write!(f, "Error: {}", self.context)
}
}
impl Yak {
pub fn new(yak_name: String) -> Self {
Yak {
yak_name,
is_shaved: false,
}
}
pub fn get_yak_name(&self) -> &str {
&self.yak_name
}
pub fn set_yak_name(&mut self, new_name: String) {
self.yak_name = new_name;
}
pub fn is_shaved(&self) -> bool {
self.is_shaved
}
pub fn shave(&mut self, mut razor: razor)
{
self.is_shaved = true;
razor.used_time+=1;
}
}
impl fmt::Display for razor
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
{
write!(f, "Razor used time: {}", self.used_time)
}
}
pub fn find_a_razor() -> Result<razor,err>
{
let mut one_razor = razor{used_time:0};
if one_razor.used_time<100
{
one_razor.used_time+=1;
Ok(one_razor)
}
else
{
let the_err:err = err{context:"No razor found".to_string()};
Err(the_err)
}
}
那么这段代码里就含有几个概念没有搞定,补全过程中极尽折磨:
- 枚举类型的泛型
- 分支结构
- Debug输出
枚举类型
这段代码中,Result<razor,err>
是用到了枚举和泛型.
首先Rust
的枚举更像是C
的枚举的增强版本,C
的枚举的几个成员是一个整数,可以按照默认的0-N
来用,一般和switch
结合增强状态机的可读性.但是很明显这样的成员很难满足Rust
的灵活性的需求,尤其是这个场景,又要返回Ok(razor)
类型,又要返回Err(err)
类型.
那么可以理解Rust
的枚举,它不只是为了用一系列代号代表这个类型的所有的可能,而且这个类型可能对应很多种基础类型,也可以被代表.
这样这一段可以理解了,Result<T,E>
是一个枚举类型,源代码我们可以在这里找到:
pub enum Result<T, E> {
Ok(T),
Err(E),
}
可能还是比较难理解,因为这里掺杂了泛型:
pub enum OBJ
{
A(u32),
B(u64)
}
上边这段代码代表OBJ
只可能是A
或者B
,两种情况,但是A
实际上包裹
---可以理解为A
是u32
类型的一个代号,但是不是直接的u32
而是A
携带了这样一个变量,B
也同理.
考虑到使用了泛型,这就意味着我们可以自定义Ok
和Err
各自包裹着什么.
分支结构
这里主要卡住人的是一点,rust
的if
后边的条件是没有括号的.
Debug输出
如果使用了{:?}
这种输出,则需要为这个需要输出的类实现一个Debug
特性,或者使用#[derive(Debug)]
.
Cargo的输出等级
编辑main.rs
之后发现不能输出:
mod yak;
use log::{info, warn};
use yak::{Yak,find_a_razor};
pub fn shave_the_yak(yak: &mut Yak)
{
info!(target: "yak_events", "Commencing yak shaving for {:?}", yak);
loop
{
match find_a_razor()
{
Ok(razor) =>
{
info!("Razor located: {}", razor);
yak.shave(razor);
break;
}
Err(err) =>
{
warn!("Unable to locate a razor: {}, retrying", err);
}
}
}
}
fn main()
{
env_logger::init();
let mut yak = Yak::new("Fred".to_string());
shave_the_yak(&mut yak);
println!("Hello, world!");
}
这里是存在两个问题:
- 错误信息没有输出到工作台
Cargo
的输出没有到info
这个级别
输出到工作台
编辑Cargo.toml
增加一个依赖:
[package]
name = "test_log"
version = "0.1.0"
edition = "2021"
[dependencies]
log = "0.4.21"
env_logger = "0.8"
编辑main.rs
,使用env_logger::init();
即可.
修改cargo
的debug
等级
使用这个方法即可:
RUST_LOG=info cargo run
给os
工程适配log
我们首先看题目中给的参考代码:
use core::fmt;
use lazy_static::lazy_static;
use log::{self, Level, LevelFilter, Log, Metadata, Record};
use crate::sync::SpinNoIrqLock as Mutex;
lazy_static! {
static ref LOG_LOCK: Mutex<()> = Mutex::new(());
}
pub fn init() {
static LOGGER: SimpleLogger = SimpleLogger;
log::set_logger(&LOGGER).unwrap();
log::set_max_level(match option_env!("LOG") {
Some("error") => LevelFilter::Error,
Some("warn") => LevelFilter::Warn,
Some("info") => LevelFilter::Info,
Some("debug") => LevelFilter::Debug,
Some("trace") => LevelFilter::Trace,
_ => LevelFilter::Off,
});
}
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ({
$crate::logging::print(format_args!($($arg)*));
});
}
#[macro_export]
macro_rules! println {
($fmt:expr) => (print!(concat!($fmt, "\n")));
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
}
/// Add escape sequence to print with color in Linux console
macro_rules! with_color {
($args: ident, $color_code: ident) => {{
format_args!("\u{1B}[{}m{}\u{1B}[0m", $color_code as u8, $args)
}};
}
fn print_in_color(args: fmt::Arguments, color_code: u8) {
use crate::arch::io;
let _guard = LOG_LOCK.lock();
io::putfmt(with_color!(args, color_code));
}
pub fn print(args: fmt::Arguments) {
use crate::arch::io;
let _guard = LOG_LOCK.lock();
io::putfmt(args);
}
struct SimpleLogger;
impl Log for SimpleLogger {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
if !self.enabled(record.metadata()) {
return;
}
/*
if let Some(tid) = processor().tid_option() {
print_in_color(
format_args!(
"[{:>5}][{},{}] {}\n",
record.level(),
crate::arch::cpu::id(),
tid,
record.args()
),
level_to_color_code(record.level()),
);
} else {
*/
print_in_color(
format_args!(
"[{:>5}][{},-] {}\n",
record.level(),
crate::arch::cpu::id(),
record.args()
),
level_to_color_code(record.level()),
);
//}
}
fn flush(&self) {}
}
fn level_to_color_code(level: Level) -> u8 {
match level {
Level::Error => 31, // Red
Level::Warn => 93, // BrightYellow
Level::Info => 34, // Blue
Level::Debug => 32, // Green
Level::Trace => 90, // BrightBlack
}
}
我们可以看到参考代码自己实现了一个SimpleLogger
类型,并且给它实现了Log
这个特性,这是起初很让人难以理解的,但是这让我们想到一个细节,即我们实际上使用了env_logger
这个库,用一句看似简单的初始化完成了我们的任务,即把info
等宏和硬件输出联系在一起,所以因此我们也需要自己去实现一个带有Log
特性的SimpleLogger
类,由于我们有了参考源码,实际上的工作反而没有那么困难.
[[012 彩色化LOG#^bf99b4|这里]]实际上又进行了一个简单的Log
特质的实现,因为思路先后问题,写到了下一节.
简单的Log特质的实现
^bf99b4
可以在这里看到Log
特质实现的时候需要实现的方法为enabled
,log
,flush
.并且可以在这里看到一个小的样例,
use log::{Record, Level, Metadata};
struct SimpleLogger;
impl log::Log for SimpleLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= Level::Info
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
println!("{} - {}", record.level(), record.args());
}
}
fn flush(&self) {}
}
可以根据在在这里看到Log
特质实现的时候需要实现的方法,
enabled
方法是传入一个Metadata
的引用返回一个bool
的函数,用于判断当前的这个数据是不是应该被logger
给记录下来,样例里简单地写成了只需要<=Level::Info
即可.log
方法是传入一个Record
,因为Rcord
里存在很多信息,然后对它进行判定,然后按照我们需要的方式来进行日志记录.这里就是直接判断等级是不是够了,然后输出其level
和args
flush
方法:- 在Rust的日志记录系统中,特别是使用
log
库和相关的宏如info!
,warn!
,error!
等进行日志记录时,这些宏会调用日志框架来记录消息。通常情况下,当你的应用程序正常退出时,日志框架会自动处理缓冲区中的日志记录,确保所有未提交的日志条目都被写入到日志目的地,比如控制台或文件。 - 然而,有时候应用程序可能在异常情况下终止,或者日志框架的默认行为可能不足以满足你的需求。在这种情况下,日志框架通常会提供一个方法,允许你显式地触发日志的刷新,以确保所有日志记录都被正确处理。这个方法通常称为
flush
。 - 在这个例子里,因为我们的日志是直接输出到命令行的,因此只需要实现为空即可.
- 在Rust的日志记录系统中,特别是使用
为一个Logger
类实现Log
特质之后,我们可以直接使用info!
等宏来实现输出.
参照参考代码写出来的Logger
如下:
use core::fmt;
use log::{self,Level,LevelFilter,Metadata,Log,Record};
struct SimpleLogger;
impl Log for SimpleLogger
{
fn enabled(&self, metadata: &Metadata) -> bool
{
true
}
fn log(&self, record: &Record)
{
if !self.enabled(record.metadata())
{
return;
}
print_in_color(
format_args!(
"[{:>5}] {}\n",
record.level(),
record.args()
),
level_to_color_code(record.level()),
);
}
fn flush(&self) {}
}
fn print_in_color(args: fmt::Arguments, color_code: u8)
{
println!("\u{1B}[{}m{}\u{1B}[0m", color_code, args);
}
fn level_to_color_code(level: Level) -> u8 {
match level {
Level::Error => 31, // Red
Level::Warn => 93, // BrightYellow
Level::Info => 34, // Blue
Level::Debug => 32, // Green
Level::Trace => 90, // BrightBlack
}
}
pub fn init()
{
static LOGGER: SimpleLogger = SimpleLogger;
log::set_logger(&LOGGER).unwrap();
// log::set_max_level(LevelFilter::Trace);
log::set_max_level(match option_env!("LOG") {
Some("error") => LevelFilter::Error,
Some("warn") => LevelFilter::Warn,
Some("info") => LevelFilter::Info,
Some("debug") => LevelFilter::Debug,
Some("trace") => LevelFilter::Trace,
_ => LevelFilter::Off,
});
}
写出来的main.rs
如下:
// os/src/main.rs
#![no_std]
#![no_main]
#![feature(panic_info_message)]
use core::{arch::global_asm};
use delay::sleep;
use log::{debug, info, error};
#[macro_use]
mod console;
mod sbi;
mod lang_items;
mod delay;
mod logging;
//use sbi::shutdown;
global_asm!(include_str!("entry.asm"));
#[no_mangle]
pub fn rust_main() -> ! {
extern "C" {
fn stext(); // begin addr of text segment
fn etext(); // end addr of text segment
fn srodata(); // start addr of Read-Only data segment
fn erodata(); // end addr of Read-Only data ssegment
fn sdata(); // start addr of data segment
fn edata(); // end addr of data segment
}
clear_bss();
logging::init();
println!("Hello World");
info!(".text [{:#x}, {:#x})", stext as usize, etext as usize);
debug!(".rodata [{:#x}, {:#x})", srodata as usize, erodata as usize);
error!(".data [{:#x}, {:#x})", sdata as usize, edata as usize);
panic!("Shutdown machine!");
}
fn clear_bss() {
extern "C" {
fn sbss();
fn ebss();
}
(sbss as usize..ebss as usize).for_each(|a| {
unsafe { (a as *mut u8).write_volatile(0) }
});
}
这里只讲关键点:
\u{1B}[{}m{}\u{1B}[0m
里的{}
为占位符,第一个{}
对应的是输出的颜色,第二个是输出的内容format_args!
这个宏是一个非常神奇的宏,可以实现传入一个字符串,然后把占位符用后边传入的参数实现,就像C
里边printf
的格式化和python
里边f"string{var}".
- 在运行
cargo
时,增加env LOG="info"
,可以有效传入环境变量参数,注意不要搞错大小写什么的以至于贻笑大方,这里注意不是在运行qemu
的时候这么做.