Skip to main content

tg_console/
lib.rs

1//! 提供可定制实现的 `print!`、`println!` 和 `log::Log`。
2//!
3//! 教程阅读建议:
4//!
5//! 1. 先看 [`Console`] trait:理解“真正输出设备”如何被抽象;
6//! 2. 再看 `init_console`:理解全局单例如何注入;
7//! 3. 最后看 `_print` 与 `Logger::log`:理解格式化输出和日志分级路径。
8
9#![no_std]
10#![deny(warnings, missing_docs)]
11
12use core::{
13    fmt::{self, Write},
14    str::FromStr,
15};
16use spin::Once;
17
18/// 向用户提供 `log`。
19pub extern crate log;
20
21/// 这个接口定义了向控制台“输出”这件事。
22pub trait Console: Sync {
23    /// 向控制台放置一个字符。
24    fn put_char(&self, c: u8);
25
26    /// 向控制台放置一个字符串。
27    ///
28    /// 如果使用了锁,覆盖这个实现以免反复获取和释放锁。
29    #[inline]
30    fn put_str(&self, s: &str) {
31        for c in s.bytes() {
32            self.put_char(c);
33        }
34    }
35}
36
37/// 库找到输出的方法:保存一个对象引用,这是一种单例。
38///
39/// 各章节会在启动阶段调用 `init_console(&ConsoleImpl)`,
40/// 后续所有 `print!`/`println!` 都会走到这个单例。
41static CONSOLE: Once<&'static dyn Console> = Once::new();
42
43/// 打印缓冲区大小。
44const BUFFER_SIZE: usize = 64;
45
46/// 打印缓冲区,用于收集格式化输出后一次性输出,避免抢占导致输出交错。
47///
48/// 教学意义:在内核场景中,逐字输出非常容易被抢占打断,
49/// 同一行日志可能被多个任务交叉写入;先拼接再一次输出可显著改善可读性。
50struct PrintBuffer {
51    buffer: [u8; BUFFER_SIZE],
52    pos: usize,
53}
54
55impl PrintBuffer {
56    /// 创建一个新的打印缓冲区。
57    const fn new() -> Self {
58        Self {
59            buffer: [0u8; BUFFER_SIZE],
60            pos: 0,
61        }
62    }
63
64    /// 刷新缓冲区,将内容输出到控制台。
65    fn flush(&mut self) {
66        if self.pos > 0 {
67            if let Some(console) = CONSOLE.get() {
68                // SAFETY: buffer 中的内容是从 str 写入的,保证是有效的 UTF-8
69                let s = unsafe { core::str::from_utf8_unchecked(&self.buffer[..self.pos]) };
70                console.put_str(s);
71            }
72            self.pos = 0;
73        }
74    }
75
76    /// 写入字符串到缓冲区,必要时刷新。
77    fn write(&mut self, s: &str) {
78        let bytes = s.as_bytes();
79        let mut offset = 0;
80
81        while offset < bytes.len() {
82            let remaining_buffer = BUFFER_SIZE - self.pos;
83            let remaining_input = bytes.len() - offset;
84            let to_copy = remaining_buffer.min(remaining_input);
85
86            self.buffer[self.pos..self.pos + to_copy]
87                .copy_from_slice(&bytes[offset..offset + to_copy]);
88            self.pos += to_copy;
89            offset += to_copy;
90
91            // 缓冲区满了,刷新
92            if self.pos == BUFFER_SIZE {
93                self.flush();
94            }
95        }
96    }
97}
98
99impl Write for PrintBuffer {
100    fn write_str(&mut self, s: &str) -> fmt::Result {
101        self.write(s);
102        Ok(())
103    }
104}
105
106/// 用户调用这个函数设置输出的方法。
107pub fn init_console(console: &'static dyn Console) {
108    // 初始化输出后,也顺带注册 log 框架入口(Logger)。
109    CONSOLE.call_once(|| console);
110    log::set_logger(&Logger).unwrap();
111}
112
113/// 根据环境变量设置日志级别。
114pub fn set_log_level(env: Option<&str>) {
115    use log::LevelFilter as Lv;
116    log::set_max_level(env.and_then(|s| Lv::from_str(s).ok()).unwrap_or(Lv::Trace));
117}
118
119/// 打印一些测试信息。
120pub fn test_log() {
121    println!(
122        r"
123   ______                       __
124  / ____/___  ____  _________  / /__
125 / /   / __ \/ __ \/ ___/ __ \/ / _ \
126/ /___/ /_/ / / / (__  ) /_/ / /  __/
127\____/\____/_/ /_/____/\____/_/\___/
128===================================="
129    );
130    log::trace!("LOG TEST >> Hello, world!");
131    log::debug!("LOG TEST >> Hello, world!");
132    log::info!("LOG TEST >> Hello, world!");
133    log::warn!("LOG TEST >> Hello, world!");
134    log::error!("LOG TEST >> Hello, world!");
135    println!();
136}
137
138/// 打印。
139///
140/// 给宏用的,用户不会直接调它。
141/// 使用栈上缓冲区收集格式化输出,然后一次性输出到控制台。
142#[doc(hidden)]
143#[inline]
144pub fn _print(args: fmt::Arguments) {
145    // 注意:这里故意不直接调用底层 put_char,而是经由 PrintBuffer 聚合输出。
146    let mut buffer = PrintBuffer::new();
147    buffer.write_fmt(args).unwrap();
148    buffer.flush();
149}
150
151/// 格式化打印。
152#[macro_export]
153macro_rules! print {
154    ($($arg:tt)*) => {
155        $crate::_print(core::format_args!($($arg)*));
156    }
157}
158
159/// 格式化打印并换行。
160#[macro_export]
161macro_rules! println {
162    () => ($crate::print!("\n"));
163    ($($arg:tt)*) => {{
164        $crate::_print(core::format_args!($($arg)*));
165        $crate::println!();
166    }}
167}
168
169/// 这个 Unit struct 是 `core::fmt` 要求的。
170struct Logger;
171
172/// 实现 [`Write`] trait,格式化的基础。
173impl Write for Logger {
174    #[inline]
175    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
176        let _ = CONSOLE.get().unwrap().put_str(s);
177        Ok(())
178    }
179}
180
181/// 实现 `log::Log` trait,提供分级日志。
182impl log::Log for Logger {
183    #[inline]
184    fn enabled(&self, _metadata: &log::Metadata) -> bool {
185        true
186    }
187
188    #[inline]
189    fn log(&self, record: &log::Record) {
190        use log::Level::*;
191        // ANSI 颜色仅用于教学调试体验,不影响日志语义。
192        let color_code: u8 = match record.level() {
193            Error => 31,
194            Warn => 93,
195            Info => 34,
196            Debug => 32,
197            Trace => 90,
198        };
199        println!(
200            "\x1b[{color_code}m[{:>5}] {}\x1b[0m",
201            record.level(),
202            record.args(),
203        );
204    }
205
206    fn flush(&self) {}
207}