// 用 volatile 以防止我们针对内存的操作被优化掉 // 因为我们是通过对内存地址 0xb8000 写入来操作 VGA Buffer 的 // 这实际上是通过内存操作的副作用来实现的 // 但由于我们从未访问过这段内存,所以它可能会在未来的 Rust 编译器中被优化掉 // 为了告诉编译器,这部分针对内存的操作是我们故意的,就需要用到 volatile crate use core::fmt; use lazy_static::lazy_static; use spin::Mutex; use volatile::Volatile; #[allow(dead_code)] // 允许未使用的代码 #[derive(Debug, Clone, Copy, PartialEq, Eq)] // 启用 copy 语义 #[repr(u8)] // 每个 enum variant 都以 u8 存储 pub enum Color { Black = 0, Blue = 1, Green = 2, Cyan = 3, Red = 4, Magenta = 5, Brown = 6, LightGray = 7, DarkGray = 8, LightBlue = 9, LightGreen = 10, LightCyan = 11, LightRed = 12, Pink = 13, Yellow = 14, White = 15, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(transparent)] // 为了保证 ColorCode 和 u8 有同样的数据布局 struct ColorCode(u8); impl ColorCode { fn new(foreground: Color, background: Color) -> ColorCode { ColorCode((background as u8) << 4 | (foreground as u8)) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(C)] // 使数据结构的布局和 C 一样,这样才能保证每个成员的顺序一致 struct ScreenChar { ascii_character: u8, color_code: ColorCode, } const BUFFER_HEIGHT: usize = 25; const BUFFER_WIDTH: usize = 80; #[repr(transparent)] struct Buffer { chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], } pub struct Writer { column_position: usize, // 当前光标所在列 color_code: ColorCode, // 当前颜色代码 buffer: &'static mut Buffer, // VGA Buffer 的引用,生命周期是 'static } impl Writer { pub fn write_byte(&mut self, byte: u8) { match byte { // 如果碰到 '\n' 则创建新行 b'\n' => self.new_line(), byte => { // 如果列数超出限定的阈值,则创建新行 if self.column_position >= BUFFER_WIDTH { self.new_line(); } let row = BUFFER_HEIGHT - 1; let col = self.column_position; let color_code = self.color_code; // 将 ScreenChar 写入到 Buffer 的相应位置 self.buffer.chars[row][col].write(ScreenChar { ascii_character: byte, color_code, }); self.column_position += 1; } } } fn new_line(&mut self) { // 将所有字符上移一行 for row in 1..BUFFER_HEIGHT { for col in 0..BUFFER_WIDTH { let character = self.buffer.chars[row][col].read(); self.buffer.chars[row - 1][col].write(character); } } // 清空最下面的一行 self.clear_row(BUFFER_HEIGHT - 1); // 将当前列的位置设为 0 self.column_position = 0; } fn clear_row(&mut self, row: usize) { let blank = ScreenChar { ascii_character: b' ', color_code: self.color_code, }; for col in 0..BUFFER_WIDTH { self.buffer.chars[row][col].write(blank); } } pub fn write_string(&mut self, s: &str) { for byte in s.bytes() { // Rust 字符串是以 UTF-8 编码的,然而 VGA Buffer 只支持 ASCII字符,所以有可能存在无法打印的字符 match byte { // 当在 ASCII 字符范围内时,打印它 0x20..=0x7e | b'\n' => self.write_byte(byte), // 否则打印 ■ _ => self.write_byte(0xfe), } } } } impl fmt::Write for Writer { fn write_str(&mut self, s: &str) -> fmt::Result { self.write_string(s); Ok(()) } } // 将 Writer 设置为静态以便调用 lazy_static! { // 内置的 static 会用到标准库里的一些东西,所以这里用 lazy_static pub static ref WRITER: Mutex = Mutex::new(Writer { // 用互斥锁来防止冲突 column_position: 0, color_code: ColorCode::new(Color::Yellow, Color::Black), buffer: unsafe { &mut *(0xb8000 as *mut Buffer) } }); } // 将宏导出到 crate 的根,这样我们就可以用 // use crate::println // 来使用它 #[macro_export] macro_rules! print { // print!() ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); } #[macro_export] macro_rules! println { // println!() () => ($crate::print!("\n")); ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); } // 由于宏需要从外部调用这个函数,所以它必须是公开的 // 用 #[doc(hidden)] 来阻止它生成文档 #[doc(hidden)] pub fn _print(args: fmt::Arguments) { use core::fmt::Write; use x86_64::instructions::interrupts; interrupts::without_interrupts(|| { WRITER.lock().write_fmt(args).unwrap(); }); } #[test_case] fn test_println_simple() { // 测试单行 println! println!("test_println_simple output"); } #[test_case] fn test_println_many() { // 测试多行 println! for _ in 0..10 { println!("test_println_many output"); } } #[test_case] fn test_println_output() { // 测试字符是否真的打印到了屏幕上 use core::fmt::Write; use x86_64::instructions::interrupts; let s = "Some test string that fits on a single line"; interrupts::without_interrupts(|| { let mut writer = WRITER.lock(); writeln!(writer, "\n{}", s).expect("writeln failed"); for (i, c) in s.chars().enumerate() { let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read(); assert_eq!(char::from(screen_char.ascii_character), c); } }); }